import type { ApiClient, FetchResponse, HttpMethod, MaybeOptionalInit, MediaType, PathsWithMethod, RequiredKeysOf } from '@archon-research/http-client-core'; import type { DataTag, QueryClient, QueryFilters, QueryKey, SkipToken, UseMutationOptions, UseQueryOptions } from '@tanstack/react-query'; import { HttpRequestError } from './errors.js'; import { type QueryApiMiddleware } from './middleware.js'; import { type QueryApiKey } from './query-key.js'; /** * The shape an `openapi-typescript`-generated `paths` type has: every path maps * to an object carrying the HTTP methods that endpoint implements, plus the * path-level `parameters`. This is the constraint on `createQueryApi` and on * every option type below, and it is what makes `TPaths[TPath][TMethod]` legal. * * Every method is *optional* and typed `any` for two reasons that come straight * from the generated output. `openapi-typescript` emits absent operations as * `put?: never`, so a required key rejects real generated paths; and the * operation payload has to widen to `any`, because narrowing it makes * TypeScript resolve `TPaths[TPath][TMethod]` to ` | undefined`, which * then fails `FetchResponse`'s own `Record` constraint. * Nothing is lost by that: the operation types the api actually reports are * read back off the caller's own `TPaths`, never off this shape. * * What it therefore does *not* do is reject a badly typed client. If the paths * type behind `client` is not a paths map, inference finds no candidate for * `TPaths` and falls back to this constraint, so `createQueryApi` still returns * — as `QueryApi`, on which no `queryOptions` call typechecks. * The rejection lands on the calls rather than on the construction. */ export type QueryApiPaths = Record; /** * Methods `queryOptions` accepts: the safe, bodyless, cacheable ones. A * POST-backed read (`POST /search`) is deliberately out of v1 — see DESIGN.md. */ export type QueryApiQueryMethod = Extract; /** Methods `mutationOptions` accepts. */ export type QueryApiMutationMethod = Extract; /** * What a failed query or mutation rejects with. A request that reached the * server rejects with {@link HttpRequestError} (status plus parsed error body); * a transport failure or a middleware — response validation, for instance — * rejects with whatever it threw. Narrow with `isHttpRequestError` before * reading `status`. */ export type QueryApiError = HttpRequestError | Error; type InitWithUnknowns = TInit & { [key: string]: unknown; }; type InferSelectReturnType = TSelect extends (data: TData) => infer TSelected ? TSelected : TData; /** Per-call options for `queryOptions`: react-query's, plus cache tags. */ export type QueryApiCallOptions = Omit, 'queryKey' | 'queryFn'> & { /** Tags this endpoint belongs to, for mutation-driven invalidation. */ tags?: readonly TTag[]; /** Middleware appended to the instance chain for this call only. */ middleware?: readonly QueryApiMiddleware[]; }; /** * A tag to invalidate on mutation success, either fixed or derived from the * mutation's own result and variables. */ export type MutationInvalidation = TTag | ((data: TData, variables: TVariables) => TTag | readonly TTag[]); /** Per-call options for `mutationOptions`: react-query's, plus `invalidates`. */ export type QueryApiMutationCallOptions = Omit, 'mutationKey' | 'mutationFn'> & { invalidates?: readonly MutationInvalidation[]; /** Middleware appended to the instance chain for this call only. */ middleware?: readonly QueryApiMiddleware[]; }; export type QueryApiKeyFn = , TInit extends MaybeOptionalInit, TResponse extends Required>>(method: TMethod, path: TPath, ...[init]: RequiredKeysOf extends never ? [init?: InitWithUnknowns] : [init: InitWithUnknowns]) => NoInfer, TResponse['data'], QueryApiError>>; export type QueryApiQueryOptionsFn = , TInit extends MaybeOptionalInit, TResponse extends Required>, TOptions extends QueryApiCallOptions, InferSelectReturnType, QueryApiKey, TTag>>(method: TMethod, path: TPath, ...[init, options]: RequiredKeysOf extends never ? [init?: InitWithUnknowns, options?: TOptions] : [init: InitWithUnknowns, options?: TOptions]) => NoInfer, InferSelectReturnType, QueryApiKey>, 'queryKey' | 'queryFn'> & { queryKey: DataTag, TResponse['data'], QueryApiError>; queryFn: Exclude, InferSelectReturnType, QueryApiKey>['queryFn'], SkipToken | undefined>; }>; export type QueryApiMutationOptionsFn = , TInit extends MaybeOptionalInit, TResponse extends Required>, TOnMutateResult = unknown>(method: TMethod, path: TPath, options?: QueryApiMutationCallOptions, InitWithUnknowns, TOnMutateResult, TTag>) => NoInfer, InitWithUnknowns, TOnMutateResult>, 'mutationKey' | 'mutationFn'> & { mutationKey: readonly [method: TMethod, path: TPath]; mutationFn: NonNullable, InitWithUnknowns, TOnMutateResult>['mutationFn']>; }>; /** Instance-level configuration for {@link createQueryApi}. */ export type QueryApiOptions = { /** * The tag vocabulary. Passing it both infers `TTag` (so `tags` and * `invalidates` are checked against a closed set) and makes an unknown tag * throw at runtime, which is what catches typos from untyped call sites. */ tags?: readonly TTag[]; /** Middleware applied to every request from this instance, outermost first. */ middleware?: readonly QueryApiMiddleware[]; }; export type QueryApi = { /** * The derived key for an operation, for targeting `getQueryData`, * `setQueryData`, or `invalidateQueries` without a hand-built key factory. * Slice it to `[method, path]` to target every cached variant of an endpoint. */ queryKey: QueryApiKeyFn; queryOptions: QueryApiQueryOptionsFn; mutationOptions: QueryApiMutationOptionsFn; /** A react-query filter matching every query registered under `tag`. */ tagFilter: (tag: TTag) => QueryFilters; invalidateTags: (client: QueryClient, tags: readonly TTag[]) => Promise; /** The `${method} ${path}` tokens currently registered under `tag`. */ taggedEndpoints: (tag: TTag) => readonly string[]; }; /** * Binds a TanStack Query surface to an `openapi-fetch` client. * * The generated `TPaths` type is the only endpoint definition: methods, paths, * params, request bodies, and response types are all read off it, and both * `TPaths` and the tag vocabulary are inferred from the arguments — prefer * `createQueryApi(client, { tags: [...] })` over passing type arguments * explicitly, since naming one disables inference for the rest. */ export declare function createQueryApi(client: ApiClient, options?: QueryApiOptions): QueryApi; export {};