feat(canary): TanStack Query hooks for token lifecycle

Four hooks wired through the canary api/client envelope helpers.
Module-augmented defaultError surfaces ApiError everywhere.

- useTokenTypes: GET /tokens/types, parsed via typeDescriptorListSchema.
  Uses QUERY_STRATEGIES.static (Infinity stale, no refetch on focus)
  since the discoverable types list is build-time stable.
- useCreateToken: POST /tokens (CreateTokenInput -> CreateTokenResponse).
  On success invalidates ['canary','manage', manage_id] so a freshly
  created token's manage page hydrates with the new row on first hit.
- useManageToken: GET /m/{id} with cursor pagination. URLSearchParams
  builds ?cursor=&limit= only for defined values; manageId is
  URI-encoded. QUERY_STRATEGIES.frequent enables 30s polling so the
  events list feels live. enabled=false when manageId is empty.
- useDeleteToken: DELETE /m/{id}. On success removes the cached
  manage data entirely (no point keeping it after a delete).
This commit is contained in:
CarterPerez-dev 2026-05-17 12:56:03 -04:00
parent 2d13e649a6
commit f6194d87b9
5 changed files with 119 additions and 0 deletions

View File

@ -2,3 +2,8 @@
// ©AngelaMos | 2026
// index.ts
// ===================
export * from './useCreateToken'
export * from './useDeleteToken'
export * from './useManageToken'
export * from './useTokenTypes'

View File

@ -0,0 +1,28 @@
// ===================
// ©AngelaMos | 2026
// useCreateToken.ts
// ===================
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { apiPost } from '../client'
import {
type CreateTokenInput,
type CreateTokenResponse,
createTokenResponseSchema,
} from '../types/token'
const CREATE_TOKEN_PATH = '/tokens'
const MANAGE_KEY_PREFIX = ['canary', 'manage'] as const
export function useCreateToken() {
const queryClient = useQueryClient()
return useMutation<CreateTokenResponse, Error, CreateTokenInput>({
mutationFn: (body) =>
apiPost(CREATE_TOKEN_PATH, body, createTokenResponseSchema),
onSuccess: (data) => {
queryClient.invalidateQueries({
queryKey: [...MANAGE_KEY_PREFIX, data.token.manage_id],
})
},
})
}

View File

@ -0,0 +1,23 @@
// ===================
// ©AngelaMos | 2026
// useDeleteToken.ts
// ===================
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { apiDelete } from '../client'
const MANAGE_PATH_PREFIX = '/m/'
const MANAGE_KEY_PREFIX = ['canary', 'manage'] as const
export function useDeleteToken() {
const queryClient = useQueryClient()
return useMutation<void, Error, string>({
mutationFn: (manageId) =>
apiDelete(`${MANAGE_PATH_PREFIX}${encodeURIComponent(manageId)}`),
onSuccess: (_data, manageId) => {
queryClient.removeQueries({
queryKey: [...MANAGE_KEY_PREFIX, manageId],
})
},
})
}

View File

@ -0,0 +1,43 @@
// ===================
// ©AngelaMos | 2026
// useManageToken.ts
// ===================
import { useQuery } from '@tanstack/react-query'
import { QUERY_STRATEGIES } from '@/core/api'
import { apiGet } from '../client'
import { manageResponseSchema } from '../types/event'
const MANAGE_PATH_PREFIX = '/m/'
const MANAGE_KEY_PREFIX = ['canary', 'manage'] as const
export type UseManageTokenParams = {
cursor?: string
limit?: number
}
function buildSearch(params: UseManageTokenParams | undefined): string {
if (!params) {
return ''
}
const sp = new URLSearchParams()
if (params.cursor !== undefined && params.cursor.length > 0) {
sp.set('cursor', params.cursor)
}
if (params.limit !== undefined) {
sp.set('limit', String(params.limit))
}
const query = sp.toString()
return query.length > 0 ? `?${query}` : ''
}
export function useManageToken(manageId: string, params?: UseManageTokenParams) {
const search = buildSearch(params)
const path = `${MANAGE_PATH_PREFIX}${encodeURIComponent(manageId)}${search}`
return useQuery({
queryKey: [...MANAGE_KEY_PREFIX, manageId, params ?? null],
queryFn: () => apiGet(path, manageResponseSchema),
enabled: manageId.length > 0,
...QUERY_STRATEGIES.frequent,
})
}

View File

@ -0,0 +1,20 @@
// ===================
// ©AngelaMos | 2026
// useTokenTypes.ts
// ===================
import { useQuery } from '@tanstack/react-query'
import { QUERY_STRATEGIES } from '@/core/api'
import { apiGet } from '../client'
import { typeDescriptorListSchema } from '../types/token'
const TOKEN_TYPES_PATH = '/tokens/types'
const TOKEN_TYPES_KEY = ['canary', 'token-types'] as const
export function useTokenTypes() {
return useQuery({
queryKey: TOKEN_TYPES_KEY,
queryFn: () => apiGet(TOKEN_TYPES_PATH, typeDescriptorListSchema),
...QUERY_STRATEGIES.static,
})
}