diff --git a/app/api/client.ts b/app/api/client.ts index b44cf5349..b1b6bd380 100644 --- a/app/api/client.ts +++ b/app/api/client.ts @@ -5,27 +5,21 @@ * * Copyright Oxide Computer Company */ -import { QueryClient } from '@tanstack/react-query' +import { QueryClient, useQuery, type UseQueryOptions } from '@tanstack/react-query' import { Api } from './__generated__/Api' +import { type ApiError } from './errors' import { + ensurePrefetched, getApiQueryOptions, getListQueryOptionsFn, getUseApiMutation, - getUseApiQueries, getUseApiQuery, getUseApiQueryErrorsAllowed, getUsePrefetchedApiQuery, wrapQueryClient, } from './hooks' -export { - ensurePrefetched, - usePrefetchedQuery, - PAGE_SIZE, - type PaginatedQuery, -} from './hooks' - export const api = new Api({ // unit tests run in Node, whose fetch implementation requires a full URL host: process.env.NODE_ENV === 'test' ? 'http://testhost' : '', @@ -42,7 +36,6 @@ export const apiq = getApiQueryOptions(api.methods) */ export const getListQFn = getListQueryOptionsFn(api.methods) export const useApiQuery = getUseApiQuery(api.methods) -export const useApiQueries = getUseApiQueries(api.methods) /** * Same as `useApiQuery`, except we use `invariant(data)` to ensure the data is * already there in the cache at request time, which means it has been @@ -53,6 +46,9 @@ export const usePrefetchedApiQuery = getUsePrefetchedApiQuery(api.methods) export const useApiQueryErrorsAllowed = getUseApiQueryErrorsAllowed(api.methods) export const useApiMutation = getUseApiMutation(api.methods) +export const usePrefetchedQuery = (options: UseQueryOptions) => + ensurePrefetched(useQuery(options), options.queryKey) + // Needs to be defined here instead of in app so we can use it to define // `apiQueryClient`, which provides API-typed versions of QueryClient methods export const queryClient = new QueryClient({ diff --git a/app/api/hooks.ts b/app/api/hooks.ts index 363ac8681..8cac13b43 100644 --- a/app/api/hooks.ts +++ b/app/api/hooks.ts @@ -9,14 +9,11 @@ import { hashKey, queryOptions, useMutation, - useQueries, useQuery, - type DefaultError, type FetchQueryOptions, type InvalidateQueryFilters, type QueryClient, type QueryKey, - type UndefinedInitialDataOptions, type UseMutationOptions, type UseQueryOptions, type UseQueryResult, @@ -29,17 +26,12 @@ import { invariant } from '~/util/invariant' import type { ApiResult } from './__generated__/Api' import { processServerError, type ApiError } from './errors' import { navToLogin } from './nav-to-login' -import { type ResultsPage } from './util' /* eslint-disable @typescript-eslint/no-explicit-any */ -export type Params = F extends (p: infer P) => any ? P : never -export type Result = F extends (p: any) => Promise> ? R : never -export type ResultItem = - Result extends { items: (infer R)[] } - ? R extends Record - ? R - : never - : never +type Params = F extends (p: infer P) => any ? P : never +type Result = F extends (p: any) => Promise> ? R : never + +export type ResultsPage = { items: TItem[]; nextPage?: string } type ApiClient = Record Promise>> /* eslint-enable @typescript-eslint/no-explicit-any */ @@ -92,17 +84,17 @@ Error message: ${error.message.replace(/\n/g, '\n' + ' '.repeat('Error message: * `queryKey` and `queryFn` are always constructed by our helper hooks, so we * only allow the rest of the options. */ -type UseQueryOtherOptions = Omit< - UndefinedInitialDataOptions, - 'queryKey' | 'queryFn' +type UseQueryOtherOptions = Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' | 'initialData' > /** * `queryKey` and `queryFn` are always constructed by our helper hooks, so we * only allow the rest of the options. */ -type FetchQueryOtherOptions = Omit< - FetchQueryOptions, +type FetchQueryOtherOptions = Omit< + FetchQueryOptions, 'queryKey' | 'queryFn' > @@ -111,7 +103,7 @@ export const getApiQueryOptions = ( method: M, params: Params, - options: UseQueryOtherOptions, ApiError> = {} + options: UseQueryOtherOptions> = {} ) => queryOptions({ queryKey: [method, params], @@ -163,7 +155,7 @@ export const getListQueryOptionsFn = >( method: M, params: Params, - options: UseQueryOtherOptions, ApiError> = {} + options: UseQueryOtherOptions> = {} ): PaginatedQuery> => { // We pull limit out of the query params rather than passing it in some // other way so that there is exactly one way of specifying it. If we had @@ -190,7 +182,7 @@ export const getUseApiQuery = ( method: M, params: Params, - options: UseQueryOtherOptions, ApiError> = {} + options: UseQueryOtherOptions> = {} ) => useQuery(getApiQueryOptions(api)(method, params, options)) @@ -199,7 +191,7 @@ export const getUsePrefetchedApiQuery = ( method: M, params: Params, - options: UseQueryOtherOptions, ApiError> = {} + options: UseQueryOtherOptions> = {} ) => { const qOptions = getApiQueryOptions(api)(method, params, options) return ensurePrefetched(useQuery(qOptions), qOptions.queryKey) @@ -232,9 +224,6 @@ export function ensurePrefetched( return result as SetNonNullable } -export const usePrefetchedQuery = (options: UseQueryOptions) => - ensurePrefetched(useQuery(options), options.queryKey) - const ERRORS_ALLOWED = 'errors-allowed' /** Result that includes both success and error so it can be cached by RQ */ @@ -289,35 +278,6 @@ export const getUseApiMutation = ...options, }) -/** - * Our version of `useQueries`, but with the key difference that all queries in - * a given call are using the same API method, and therefore all have the same - * request and response (`Params` and `Result`) types. Otherwise the types would - * be (perhaps literally) impossible. - */ -export const getUseApiQueries = - (api: A) => - ( - method: M, - paramsArray: Params[], - options: UseQueryOtherOptions, ApiError> = {} - ) => { - return useQueries({ - queries: paramsArray.map( - (params) => - ({ - queryKey: [method, params], - queryFn: ({ signal }) => - api[method](params, { signal }).then(handleResult(method)), - throwOnError: (err: ApiError) => err.statusCode === 404, - ...options, - // Add params to the result for reassembly after the queries are returned - select: (data) => ({ ...data, params }), - }) satisfies UseQueryOptions & { params: Params }, ApiError> - ), - }) - } - export const wrapQueryClient = (api: A, queryClient: QueryClient) => ({ /** * Note that we only take a single argument, `method`, rather than allowing @@ -340,7 +300,7 @@ export const wrapQueryClient = (api: A, queryClient: QueryC fetchQuery: ( method: M, params: Params, - options: FetchQueryOtherOptions, ApiError> = {} + options: FetchQueryOtherOptions> = {} ) => queryClient.fetchQuery({ queryKey: [method, params], @@ -350,7 +310,7 @@ export const wrapQueryClient = (api: A, queryClient: QueryC prefetchQuery: ( method: M, params: Params, - options: FetchQueryOtherOptions, ApiError> = {} + options: FetchQueryOtherOptions> = {} ) => queryClient.prefetchQuery({ queryKey: [method, params], diff --git a/app/api/index.ts b/app/api/index.ts index e85abc534..bb79dc0a5 100644 --- a/app/api/index.ts +++ b/app/api/index.ts @@ -21,6 +21,6 @@ export type { ApiTypes } export * as PathParams from './path-params' -export type { Params, Result, ResultItem } from './hooks' +export { ensurePrefetched, PAGE_SIZE, type PaginatedQuery, type ResultsPage } from './hooks' export type { ApiError } from './errors' export { navToLogin } from './nav-to-login' diff --git a/app/api/util.ts b/app/api/util.ts index d552aa4f3..954cd77b0 100644 --- a/app/api/util.ts +++ b/app/api/util.ts @@ -23,8 +23,6 @@ import type { VpcFirewallRuleUpdate, } from './__generated__/Api' -export type ResultsPage = { items: TItem[]; nextPage?: string } - // API limits encoded in https://github.com/oxidecomputer/omicron/blob/main/nexus/src/app/mod.rs export const MAX_NICS_PER_INSTANCE = 8