openapi-typesafe-react-query 1.0.0-rc.3 → 1.0.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { UseQueryOptions, DataTag, SkipToken, QueryClient, UseQueryResult, UseSuspenseQueryOptions, UseSuspenseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
1
+ import { UseQueryOptions, DefaultError, DataTag, SkipToken, QueryClient, DefinedUseQueryResult, UseQueryResult, UseSuspenseQueryOptions, UseSuspenseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
2
2
  import { MaybeOptionalInit, FetchResponse, PathOperation, Client } from 'openapi-typesafe-fetch';
3
- import { HttpMethod, PathsWithMethod, MediaType, RequiredKeysOf } from 'openapi-typesafe-helpers';
3
+ import { HttpMethod, PathsWithMethod, MediaType, RequiredKeysOf } from 'openapi-typesafe-fetch/helpers';
4
4
 
5
5
  type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
6
6
  /**
@@ -8,6 +8,40 @@ type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => in
8
8
  * `null` in the query, as `queryFn` resolves it (upstream #2234, #2464).
9
9
  */
10
10
  type QueryData<Data> = undefined extends Data ? Exclude<Data, undefined> | null : Data;
11
+ /**
12
+ * What the hooks throw when a request fails and the response has no error body: a non-OK status
13
+ * with an empty body, a JSON `null` body, or one whose operation declares no content for it. The
14
+ * fetch client gives `error: undefined` (or `null`) then; throwing that left `error` empty in
15
+ * TanStack Query while its type said there was an error body. Tell it apart with
16
+ * `isResponseError(error)`: `error instanceof Error` also matches a network or parse error. The
17
+ * message names the status, not the URL, which can carry secrets.
18
+ */
19
+ interface ResponseError extends Error {
20
+ readonly name: "ResponseError";
21
+ /** The response that failed. */
22
+ readonly response: Response;
23
+ /** Its HTTP status, as `response.status`. */
24
+ readonly status: number;
25
+ }
26
+ /**
27
+ * The error of a hook: the error body of a failed response, a `ResponseError` when it has none, or
28
+ * what the request threw before there was a response to read: a network error (`TypeError`), an
29
+ * abort, a body that is not JSON (`SyntaxError`) or a middleware error. TanStack Query types that
30
+ * last part as `DefaultError` (`Error` unless `Register` says otherwise; a registered `unknown`
31
+ * makes the whole type `unknown`, as for TanStack's own hooks). `undefined` and `null` are left
32
+ * out: a failed response without an error body throws a `ResponseError`.
33
+ */
34
+ type QueryError<ErrorBody> = Exclude<ErrorBody, undefined | null> | ResponseError | DefaultError;
35
+ /**
36
+ * An object whose `queryKey` is tagged with the query's data and error types: the type that
37
+ * TanStack Query 5.102 exports as `QueryKeyWithDataTag`, for the whole peer range. A named object
38
+ * type, so the declarations of an exported `queryOptions` result print it by name. Spelled out,
39
+ * the tag prints TanStack's `dataTagSymbol` keys, which a declaration file cannot import, and
40
+ * the library's users get TS2304, or `any` cached data with `skipLibCheck`.
41
+ */
42
+ type QueryKeyWithDataTag<Key, Data, Error> = {
43
+ queryKey: DataTag<Key, Data, Error>;
44
+ };
11
45
  type InitWithUnknowns<Init> = Init & {
12
46
  [key: string]: unknown;
13
47
  };
@@ -20,20 +54,44 @@ type InitWithUnknowns<Init> = Init & {
20
54
  * so `Parameters<typeof $api.queryOptions>` still requires it then (upstream #2178).
21
55
  */
22
56
  type InitArguments<Init, Rest extends unknown[]> = RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, ...Rest] : [InitWithUnknowns<Init>, ...Rest];
57
+ /**
58
+ * The key of a query: the method, the path and the request. Its init is the init of the call with
59
+ * the headers and the body as plain values, and the client's `keyScope`, or its `baseUrl` when the
60
+ * init has none, so two requests never share a key; it is left out when nothing is left. Read a
61
+ * query's key from `queryOptions(...).queryKey` rather than writing it out.
62
+ */
23
63
  type QueryKey<Paths extends {}, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init = MaybeOptionalInit<Paths[Path], Method>> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
24
64
  /**
25
65
  * The last element of an infinite query's key. `useQuery` and `useInfiniteQuery` cache different
26
66
  * data (`TData` and `InfiniteData<TData>`), so the same request must not share one key (upstream
27
- * #2184, ADR-022). A prefix such as `[method, path]` still matches both. Not exported: a runtime
28
- * named export next to the default export would change what `require()` returns.
67
+ * #2184, ADR-022). A prefix such as `[method, path]` still matches both. Not exported: it is not
68
+ * needed outside the package, and a named export is public API; `InfiniteQueryKey` types the key.
29
69
  */
30
70
  declare const INFINITE_QUERY_KEY_MARKER = "infinite";
31
71
  type InfiniteQueryKey<Paths extends {}, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>> = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];
32
- type QueryOptionsFunction<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseQueryOptions<QueryData<Response["data"]>, Response["error"], InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, path: Path, ...[init, options]: InitArguments<Init, [Options?]>) => NoInfer<Omit<UseQueryOptions<QueryData<Response["data"]>, Response["error"], InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryFn" | "queryKey"> & {
33
- queryKey: DataTag<QueryKey<Paths, Method, Path>, QueryData<Response["data"]>, Response["error"]>;
34
- queryFn: Exclude<UseQueryOptions<QueryData<Response["data"]>, Response["error"], InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>["queryFn"], SkipToken | undefined>;
72
+ /**
73
+ * Whether the options give an `initialData` that is never `undefined`: a value, or a function that
74
+ * never returns `undefined`. Then `data` is never `undefined` either, as for TanStack Query's own
75
+ * `useQuery` with `DefinedInitialDataOptions`.
76
+ */
77
+ type HasDefinedInitialData<Options> = Options extends {
78
+ initialData: infer Initial;
79
+ } ? Initial extends (...args: never) => infer Returned ? undefined extends Returned ? false : true : undefined extends Initial ? false : true : false;
80
+ /** The result of `useQuery`: its `data` is defined when the options give defined initial data. */
81
+ type QueryResult<Data, Error, Options> = HasDefinedInitialData<Options> extends true ? DefinedUseQueryResult<Data, Error> : UseQueryResult<Data, Error>;
82
+ /**
83
+ * A defined `initialData` of the options, in the type `queryOptions` returns, so TanStack Query's
84
+ * `useQuery` takes the result as options with defined initial data. It is typed as the query data,
85
+ * as TanStack's `DefinedInitialDataOptions` types it: the type of a literal such as `[{ id: 1 }]`
86
+ * lacks the optional properties of the data, and TanStack's overload rejected it.
87
+ */
88
+ type InitialDataOf<Options, Data> = HasDefinedInitialData<Options> extends true ? {
89
+ initialData: Data | (() => Data);
90
+ } : unknown;
91
+ type QueryOptionsFunction<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseQueryOptions<QueryData<Response["data"]>, QueryError<Response["error"]>, InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, path: Path, ...[init, options]: InitArguments<Init, [Options?]>) => NoInfer<Omit<UseQueryOptions<QueryData<Response["data"]>, QueryError<Response["error"]>, InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryFn" | "queryKey"> & QueryKeyWithDataTag<QueryKey<Paths, Method, Path>, QueryData<Response["data"]>, QueryError<Response["error"]>> & InitialDataOf<Options, QueryData<Response["data"]>> & {
92
+ queryFn: Exclude<UseQueryOptions<QueryData<Response["data"]>, QueryError<Response["error"]>, InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>["queryFn"], SkipToken | undefined>;
35
93
  }>;
36
- type UseQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseQueryOptions<QueryData<Response["data"]>, Response["error"], InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>) => UseQueryResult<InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, Response["error"]>;
94
+ type UseQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseQueryOptions<QueryData<Response["data"]>, QueryError<Response["error"]>, InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>) => QueryResult<InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryError<Response["error"]>, Options>;
37
95
  /**
38
96
  * The type of the page params: the initial one and the ones `getNextPageParam` and
39
97
  * `getPreviousPageParam` return (upstream #2458). It was `unknown`. It stays `unknown` when a
@@ -65,12 +123,12 @@ interface PageParamSources<Initial, Next, Previous> {
65
123
  * `unknown` there and failed against the typed page param.
66
124
  */
67
125
  type InfinitePlaceholderData<Data, Error, Paths extends {}, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>> = UseInfiniteQueryOptions<Data, Error, InfiniteData<Data, unknown>, InfiniteQueryKey<Paths, Method, Path>, unknown>["placeholderData"];
68
- type UseInfiniteQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseInfiniteQueryOptions<Response["data"], Response["error"], InferSelectReturnType<InfiniteData<Response["data"], PageParam<Initial, Next, Previous>>, Options["select"]>, InfiniteQueryKey<Paths, Method, Path>, PageParam<Initial, Next, Previous>>, "queryKey" | "queryFn" | "placeholderData"> & {
69
- placeholderData?: InfinitePlaceholderData<Response["data"], Response["error"], Paths, Method, Path>;
126
+ type UseInfiniteQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseInfiniteQueryOptions<Response["data"], QueryError<Response["error"]>, InferSelectReturnType<InfiniteData<Response["data"], PageParam<Initial, Next, Previous>>, Options["select"]>, InfiniteQueryKey<Paths, Method, Path>, PageParam<Initial, Next, Previous>>, "queryKey" | "queryFn" | "placeholderData"> & {
127
+ placeholderData?: InfinitePlaceholderData<Response["data"], QueryError<Response["error"]>, Paths, Method, Path>;
70
128
  pageParamName?: string;
71
- }, Initial = unknown, Next = never, Previous = NoPreviousPageParam>(method: Method, url: Path, init: InitWithUnknowns<Init>, options: PageParamSources<Initial, Next, Previous> & Options, queryClient?: QueryClient) => UseInfiniteQueryResult<InferSelectReturnType<InfiniteData<Response["data"], PageParam<Initial, Next, Previous>>, Options["select"]>, Response["error"]>;
72
- type UseSuspenseQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseSuspenseQueryOptions<QueryData<Response["data"]>, Response["error"], InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>) => UseSuspenseQueryResult<InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, Response["error"]>;
73
- type UseMutationMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, TOnMutateResult = unknown>(method: Method, url: Path, options?: Omit<UseMutationOptions<Response["data"], Response["error"], Init, TOnMutateResult>, "mutationKey" | "mutationFn">, queryClient?: QueryClient) => UseMutationResult<Response["data"], Response["error"], Init, TOnMutateResult>;
129
+ }, Initial = unknown, Next = never, Previous = NoPreviousPageParam>(method: Method, url: Path, init: InitWithUnknowns<Init>, options: PageParamSources<Initial, Next, Previous> & Options, queryClient?: QueryClient) => UseInfiniteQueryResult<InferSelectReturnType<InfiniteData<Response["data"], PageParam<Initial, Next, Previous>>, Options["select"]>, QueryError<Response["error"]>>;
130
+ type UseSuspenseQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseSuspenseQueryOptions<QueryData<Response["data"]>, QueryError<Response["error"]>, InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>) => UseSuspenseQueryResult<InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryError<Response["error"]>>;
131
+ type UseMutationMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, TOnMutateResult = unknown>(method: Method, url: Path, options?: Omit<UseMutationOptions<Response["data"], QueryError<Response["error"]>, Init, TOnMutateResult>, "mutationKey" | "mutationFn">, queryClient?: QueryClient) => UseMutationResult<Response["data"], QueryError<Response["error"]>, Init, TOnMutateResult>;
74
132
  interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
75
133
  queryOptions: QueryOptionsFunction<Paths, Media>;
76
134
  useQuery: UseQueryMethod<Paths, Media>;
@@ -81,11 +139,28 @@ interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaTy
81
139
  type MethodResponse<CreatedClient extends OpenapiQueryClient<any, any>, Method extends HttpMethod, Path extends CreatedClient extends OpenapiQueryClient<infer Paths, infer _Media> ? PathsWithMethod<Paths, Method> : never, Options = object> = CreatedClient extends OpenapiQueryClient<infer Paths extends {
82
140
  [key: string]: any;
83
141
  }, infer Media extends MediaType> ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]> : never;
142
+ /**
143
+ * Whether `error` is the `ResponseError` of a failed response without an error body. It narrows a
144
+ * hook's `error`, where `error instanceof Error` also matches network and parse errors. It checks
145
+ * the `name`, not the class, so a `ResponseError` of another copy of this package (its CommonJS and
146
+ * ES module builds loaded side by side) is one too. An error body from JSON is never an `Error`.
147
+ */
148
+ declare function isResponseError(error: unknown): error is ResponseError;
149
+ /** Options of `createClient`. */
150
+ interface CreateClientOptions {
151
+ /**
152
+ * The name of the API in every query key, in place of the fetch client's `baseUrl`. Give the
153
+ * same name where the server and the browser reach one API by different URLs, so the browser
154
+ * reads the data the server rendered with; give different names to clients of one base URL that
155
+ * fetch different data, such as one per tenant header.
156
+ */
157
+ keyScope?: string;
158
+ }
84
159
  /**
85
160
  * Hooks and query options for a fetch client. A `QueryClient` other than the one from the React
86
161
  * context is passed to each hook as its last argument; `createClient` does not take one.
87
162
  */
88
- declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>): OpenapiQueryClient<Paths, Media>;
163
+ declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>, { keyScope }?: CreateClientOptions): OpenapiQueryClient<Paths, Media>;
89
164
 
90
- export { createClient as default };
91
- export type { InfiniteQueryKey, MethodResponse, OpenapiQueryClient, QueryKey, QueryOptionsFunction, UseInfiniteQueryMethod, UseMutationMethod, UseQueryMethod, UseSuspenseQueryMethod };
165
+ export { createClient as default, isResponseError };
166
+ export type { CreateClientOptions, InfiniteQueryKey, MethodResponse, OpenapiQueryClient, QueryError, QueryKey, QueryKeyWithDataTag, QueryOptionsFunction, ResponseError, UseInfiniteQueryMethod, UseMutationMethod, UseQueryMethod, UseSuspenseQueryMethod };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { UseQueryOptions, DataTag, SkipToken, QueryClient, UseQueryResult, UseSuspenseQueryOptions, UseSuspenseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
1
+ import { UseQueryOptions, DefaultError, DataTag, SkipToken, QueryClient, DefinedUseQueryResult, UseQueryResult, UseSuspenseQueryOptions, UseSuspenseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
2
2
  import { MaybeOptionalInit, FetchResponse, PathOperation, Client } from 'openapi-typesafe-fetch';
3
- import { HttpMethod, PathsWithMethod, MediaType, RequiredKeysOf } from 'openapi-typesafe-helpers';
3
+ import { HttpMethod, PathsWithMethod, MediaType, RequiredKeysOf } from 'openapi-typesafe-fetch/helpers';
4
4
 
5
5
  type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
6
6
  /**
@@ -8,6 +8,40 @@ type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => in
8
8
  * `null` in the query, as `queryFn` resolves it (upstream #2234, #2464).
9
9
  */
10
10
  type QueryData<Data> = undefined extends Data ? Exclude<Data, undefined> | null : Data;
11
+ /**
12
+ * What the hooks throw when a request fails and the response has no error body: a non-OK status
13
+ * with an empty body, a JSON `null` body, or one whose operation declares no content for it. The
14
+ * fetch client gives `error: undefined` (or `null`) then; throwing that left `error` empty in
15
+ * TanStack Query while its type said there was an error body. Tell it apart with
16
+ * `isResponseError(error)`: `error instanceof Error` also matches a network or parse error. The
17
+ * message names the status, not the URL, which can carry secrets.
18
+ */
19
+ interface ResponseError extends Error {
20
+ readonly name: "ResponseError";
21
+ /** The response that failed. */
22
+ readonly response: Response;
23
+ /** Its HTTP status, as `response.status`. */
24
+ readonly status: number;
25
+ }
26
+ /**
27
+ * The error of a hook: the error body of a failed response, a `ResponseError` when it has none, or
28
+ * what the request threw before there was a response to read: a network error (`TypeError`), an
29
+ * abort, a body that is not JSON (`SyntaxError`) or a middleware error. TanStack Query types that
30
+ * last part as `DefaultError` (`Error` unless `Register` says otherwise; a registered `unknown`
31
+ * makes the whole type `unknown`, as for TanStack's own hooks). `undefined` and `null` are left
32
+ * out: a failed response without an error body throws a `ResponseError`.
33
+ */
34
+ type QueryError<ErrorBody> = Exclude<ErrorBody, undefined | null> | ResponseError | DefaultError;
35
+ /**
36
+ * An object whose `queryKey` is tagged with the query's data and error types: the type that
37
+ * TanStack Query 5.102 exports as `QueryKeyWithDataTag`, for the whole peer range. A named object
38
+ * type, so the declarations of an exported `queryOptions` result print it by name. Spelled out,
39
+ * the tag prints TanStack's `dataTagSymbol` keys, which a declaration file cannot import, and
40
+ * the library's users get TS2304, or `any` cached data with `skipLibCheck`.
41
+ */
42
+ type QueryKeyWithDataTag<Key, Data, Error> = {
43
+ queryKey: DataTag<Key, Data, Error>;
44
+ };
11
45
  type InitWithUnknowns<Init> = Init & {
12
46
  [key: string]: unknown;
13
47
  };
@@ -20,20 +54,44 @@ type InitWithUnknowns<Init> = Init & {
20
54
  * so `Parameters<typeof $api.queryOptions>` still requires it then (upstream #2178).
21
55
  */
22
56
  type InitArguments<Init, Rest extends unknown[]> = RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, ...Rest] : [InitWithUnknowns<Init>, ...Rest];
57
+ /**
58
+ * The key of a query: the method, the path and the request. Its init is the init of the call with
59
+ * the headers and the body as plain values, and the client's `keyScope`, or its `baseUrl` when the
60
+ * init has none, so two requests never share a key; it is left out when nothing is left. Read a
61
+ * query's key from `queryOptions(...).queryKey` rather than writing it out.
62
+ */
23
63
  type QueryKey<Paths extends {}, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init = MaybeOptionalInit<Paths[Path], Method>> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
24
64
  /**
25
65
  * The last element of an infinite query's key. `useQuery` and `useInfiniteQuery` cache different
26
66
  * data (`TData` and `InfiniteData<TData>`), so the same request must not share one key (upstream
27
- * #2184, ADR-022). A prefix such as `[method, path]` still matches both. Not exported: a runtime
28
- * named export next to the default export would change what `require()` returns.
67
+ * #2184, ADR-022). A prefix such as `[method, path]` still matches both. Not exported: it is not
68
+ * needed outside the package, and a named export is public API; `InfiniteQueryKey` types the key.
29
69
  */
30
70
  declare const INFINITE_QUERY_KEY_MARKER = "infinite";
31
71
  type InfiniteQueryKey<Paths extends {}, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>> = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];
32
- type QueryOptionsFunction<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseQueryOptions<QueryData<Response["data"]>, Response["error"], InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, path: Path, ...[init, options]: InitArguments<Init, [Options?]>) => NoInfer<Omit<UseQueryOptions<QueryData<Response["data"]>, Response["error"], InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryFn" | "queryKey"> & {
33
- queryKey: DataTag<QueryKey<Paths, Method, Path>, QueryData<Response["data"]>, Response["error"]>;
34
- queryFn: Exclude<UseQueryOptions<QueryData<Response["data"]>, Response["error"], InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>["queryFn"], SkipToken | undefined>;
72
+ /**
73
+ * Whether the options give an `initialData` that is never `undefined`: a value, or a function that
74
+ * never returns `undefined`. Then `data` is never `undefined` either, as for TanStack Query's own
75
+ * `useQuery` with `DefinedInitialDataOptions`.
76
+ */
77
+ type HasDefinedInitialData<Options> = Options extends {
78
+ initialData: infer Initial;
79
+ } ? Initial extends (...args: never) => infer Returned ? undefined extends Returned ? false : true : undefined extends Initial ? false : true : false;
80
+ /** The result of `useQuery`: its `data` is defined when the options give defined initial data. */
81
+ type QueryResult<Data, Error, Options> = HasDefinedInitialData<Options> extends true ? DefinedUseQueryResult<Data, Error> : UseQueryResult<Data, Error>;
82
+ /**
83
+ * A defined `initialData` of the options, in the type `queryOptions` returns, so TanStack Query's
84
+ * `useQuery` takes the result as options with defined initial data. It is typed as the query data,
85
+ * as TanStack's `DefinedInitialDataOptions` types it: the type of a literal such as `[{ id: 1 }]`
86
+ * lacks the optional properties of the data, and TanStack's overload rejected it.
87
+ */
88
+ type InitialDataOf<Options, Data> = HasDefinedInitialData<Options> extends true ? {
89
+ initialData: Data | (() => Data);
90
+ } : unknown;
91
+ type QueryOptionsFunction<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseQueryOptions<QueryData<Response["data"]>, QueryError<Response["error"]>, InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, path: Path, ...[init, options]: InitArguments<Init, [Options?]>) => NoInfer<Omit<UseQueryOptions<QueryData<Response["data"]>, QueryError<Response["error"]>, InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryFn" | "queryKey"> & QueryKeyWithDataTag<QueryKey<Paths, Method, Path>, QueryData<Response["data"]>, QueryError<Response["error"]>> & InitialDataOf<Options, QueryData<Response["data"]>> & {
92
+ queryFn: Exclude<UseQueryOptions<QueryData<Response["data"]>, QueryError<Response["error"]>, InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>["queryFn"], SkipToken | undefined>;
35
93
  }>;
36
- type UseQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseQueryOptions<QueryData<Response["data"]>, Response["error"], InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>) => UseQueryResult<InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, Response["error"]>;
94
+ type UseQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseQueryOptions<QueryData<Response["data"]>, QueryError<Response["error"]>, InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>) => QueryResult<InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryError<Response["error"]>, Options>;
37
95
  /**
38
96
  * The type of the page params: the initial one and the ones `getNextPageParam` and
39
97
  * `getPreviousPageParam` return (upstream #2458). It was `unknown`. It stays `unknown` when a
@@ -65,12 +123,12 @@ interface PageParamSources<Initial, Next, Previous> {
65
123
  * `unknown` there and failed against the typed page param.
66
124
  */
67
125
  type InfinitePlaceholderData<Data, Error, Paths extends {}, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>> = UseInfiniteQueryOptions<Data, Error, InfiniteData<Data, unknown>, InfiniteQueryKey<Paths, Method, Path>, unknown>["placeholderData"];
68
- type UseInfiniteQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseInfiniteQueryOptions<Response["data"], Response["error"], InferSelectReturnType<InfiniteData<Response["data"], PageParam<Initial, Next, Previous>>, Options["select"]>, InfiniteQueryKey<Paths, Method, Path>, PageParam<Initial, Next, Previous>>, "queryKey" | "queryFn" | "placeholderData"> & {
69
- placeholderData?: InfinitePlaceholderData<Response["data"], Response["error"], Paths, Method, Path>;
126
+ type UseInfiniteQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseInfiniteQueryOptions<Response["data"], QueryError<Response["error"]>, InferSelectReturnType<InfiniteData<Response["data"], PageParam<Initial, Next, Previous>>, Options["select"]>, InfiniteQueryKey<Paths, Method, Path>, PageParam<Initial, Next, Previous>>, "queryKey" | "queryFn" | "placeholderData"> & {
127
+ placeholderData?: InfinitePlaceholderData<Response["data"], QueryError<Response["error"]>, Paths, Method, Path>;
70
128
  pageParamName?: string;
71
- }, Initial = unknown, Next = never, Previous = NoPreviousPageParam>(method: Method, url: Path, init: InitWithUnknowns<Init>, options: PageParamSources<Initial, Next, Previous> & Options, queryClient?: QueryClient) => UseInfiniteQueryResult<InferSelectReturnType<InfiniteData<Response["data"], PageParam<Initial, Next, Previous>>, Options["select"]>, Response["error"]>;
72
- type UseSuspenseQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseSuspenseQueryOptions<QueryData<Response["data"]>, Response["error"], InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>) => UseSuspenseQueryResult<InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, Response["error"]>;
73
- type UseMutationMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, TOnMutateResult = unknown>(method: Method, url: Path, options?: Omit<UseMutationOptions<Response["data"], Response["error"], Init, TOnMutateResult>, "mutationKey" | "mutationFn">, queryClient?: QueryClient) => UseMutationResult<Response["data"], Response["error"], Init, TOnMutateResult>;
129
+ }, Initial = unknown, Next = never, Previous = NoPreviousPageParam>(method: Method, url: Path, init: InitWithUnknowns<Init>, options: PageParamSources<Initial, Next, Previous> & Options, queryClient?: QueryClient) => UseInfiniteQueryResult<InferSelectReturnType<InfiniteData<Response["data"], PageParam<Initial, Next, Previous>>, Options["select"]>, QueryError<Response["error"]>>;
130
+ type UseSuspenseQueryMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, Options extends Omit<UseSuspenseQueryOptions<QueryData<Response["data"]>, QueryError<Response["error"]>, InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>) => UseSuspenseQueryResult<InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>, QueryError<Response["error"]>>;
131
+ type UseMutationMethod<Paths extends {}, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>, TOnMutateResult = unknown>(method: Method, url: Path, options?: Omit<UseMutationOptions<Response["data"], QueryError<Response["error"]>, Init, TOnMutateResult>, "mutationKey" | "mutationFn">, queryClient?: QueryClient) => UseMutationResult<Response["data"], QueryError<Response["error"]>, Init, TOnMutateResult>;
74
132
  interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
75
133
  queryOptions: QueryOptionsFunction<Paths, Media>;
76
134
  useQuery: UseQueryMethod<Paths, Media>;
@@ -81,11 +139,28 @@ interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaTy
81
139
  type MethodResponse<CreatedClient extends OpenapiQueryClient<any, any>, Method extends HttpMethod, Path extends CreatedClient extends OpenapiQueryClient<infer Paths, infer _Media> ? PathsWithMethod<Paths, Method> : never, Options = object> = CreatedClient extends OpenapiQueryClient<infer Paths extends {
82
140
  [key: string]: any;
83
141
  }, infer Media extends MediaType> ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]> : never;
142
+ /**
143
+ * Whether `error` is the `ResponseError` of a failed response without an error body. It narrows a
144
+ * hook's `error`, where `error instanceof Error` also matches network and parse errors. It checks
145
+ * the `name`, not the class, so a `ResponseError` of another copy of this package (its CommonJS and
146
+ * ES module builds loaded side by side) is one too. An error body from JSON is never an `Error`.
147
+ */
148
+ declare function isResponseError(error: unknown): error is ResponseError;
149
+ /** Options of `createClient`. */
150
+ interface CreateClientOptions {
151
+ /**
152
+ * The name of the API in every query key, in place of the fetch client's `baseUrl`. Give the
153
+ * same name where the server and the browser reach one API by different URLs, so the browser
154
+ * reads the data the server rendered with; give different names to clients of one base URL that
155
+ * fetch different data, such as one per tenant header.
156
+ */
157
+ keyScope?: string;
158
+ }
84
159
  /**
85
160
  * Hooks and query options for a fetch client. A `QueryClient` other than the one from the React
86
161
  * context is passed to each hook as its last argument; `createClient` does not take one.
87
162
  */
88
- declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>): OpenapiQueryClient<Paths, Media>;
163
+ declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>, { keyScope }?: CreateClientOptions): OpenapiQueryClient<Paths, Media>;
89
164
 
90
- export { createClient as default };
91
- export type { InfiniteQueryKey, MethodResponse, OpenapiQueryClient, QueryKey, QueryOptionsFunction, UseInfiniteQueryMethod, UseMutationMethod, UseQueryMethod, UseSuspenseQueryMethod };
165
+ export { createClient as default, isResponseError };
166
+ export type { CreateClientOptions, InfiniteQueryKey, MethodResponse, OpenapiQueryClient, QueryError, QueryKey, QueryKeyWithDataTag, QueryOptionsFunction, ResponseError, UseInfiniteQueryMethod, UseMutationMethod, UseQueryMethod, UseSuspenseQueryMethod };
package/dist/index.mjs CHANGED
@@ -1,39 +1,174 @@
1
1
  import { useMutation, useInfiniteQuery, useSuspenseQuery, useQuery } from '@tanstack/react-query';
2
2
 
3
+ const TYPE_TAG_START = "[object ".length;
4
+ const TYPE_TAG_END = -1;
5
+ function isClass(value, name) {
6
+ const type = globalThis[name];
7
+ return typeof type === "function" && value instanceof type || Object.prototype.toString.call(value).slice(TYPE_TAG_START, TYPE_TAG_END) === name;
8
+ }
9
+ function plainHeaders(headers) {
10
+ if (!(Symbol.iterator in headers)) {
11
+ return headers;
12
+ }
13
+ const record = {};
14
+ for (const pair of headers) {
15
+ const [name, value] = Array.from(pair);
16
+ const key = String(name).toLowerCase();
17
+ record[key] = key in record ? `${record[key]}, ${String(value)}` : String(value);
18
+ }
19
+ return record;
20
+ }
21
+ function blobKey(blob) {
22
+ const { name, lastModified } = blob;
23
+ return { type: blob.type, size: blob.size, name, lastModified };
24
+ }
25
+ function formDataKey(form) {
26
+ if (typeof form.entries !== "function") {
27
+ return [];
28
+ }
29
+ return Array.from(form.entries(), ([name, value]) => [
30
+ name,
31
+ typeof value === "string" ? value : blobKey(value)
32
+ ]);
33
+ }
34
+ function bodyBytes(body) {
35
+ if (ArrayBuffer.isView(body)) {
36
+ return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
37
+ }
38
+ return isClass(body, "ArrayBuffer") ? new Uint8Array(body) : null;
39
+ }
40
+ function bodyKey(body) {
41
+ if (typeof body !== "object" || body === null) {
42
+ return body;
43
+ }
44
+ if (isClass(body, "URLSearchParams")) {
45
+ return { URLSearchParams: String(body) };
46
+ }
47
+ if (isClass(body, "FormData")) {
48
+ return { FormData: formDataKey(body) };
49
+ }
50
+ if (isClass(body, "Blob") || isClass(body, "File")) {
51
+ return { Blob: blobKey(body) };
52
+ }
53
+ const bytes = bodyBytes(body);
54
+ return bytes === null ? body : { bytes: Array.from(bytes) };
55
+ }
56
+ function queryKeyInit(init, client) {
57
+ const { signal: _signal, ...rest } = init ?? {};
58
+ const keyInit = { ...rest };
59
+ if (typeof rest.headers === "object" && rest.headers !== null) {
60
+ keyInit.headers = plainHeaders(rest.headers);
61
+ }
62
+ if ("body" in rest) {
63
+ keyInit.body = bodyKey(rest.body);
64
+ }
65
+ if (client.keyScope !== void 0) {
66
+ keyInit.keyScope = client.keyScope;
67
+ } else if (!rest.baseUrl && client.baseUrl) {
68
+ keyInit.baseUrl = client.baseUrl;
69
+ }
70
+ return Object.keys(keyInit).length === 0 ? void 0 : keyInit;
71
+ }
72
+
73
+ const ABORT_EVENT = "abort";
74
+ function withoutRelease(init) {
75
+ return { init, release: () => void 0 };
76
+ }
77
+ function withQuerySignal(init, querySignal) {
78
+ const { signal: ownSignal, ...rest } = init ?? {};
79
+ if (!ownSignal) {
80
+ return withoutRelease({ ...rest, signal: querySignal });
81
+ }
82
+ const controller = new AbortController();
83
+ const sources = [ownSignal, querySignal];
84
+ const aborted = sources.find((source) => source.aborted);
85
+ if (aborted !== void 0) {
86
+ controller.abort(aborted.reason);
87
+ return withoutRelease({ ...rest, signal: controller.signal });
88
+ }
89
+ const listeners = sources.map((source) => {
90
+ const listener = () => controller.abort(source.reason);
91
+ source.addEventListener(ABORT_EVENT, listener, { once: true });
92
+ return () => source.removeEventListener(ABORT_EVENT, listener);
93
+ });
94
+ return {
95
+ init: { ...rest, signal: controller.signal },
96
+ release: () => {
97
+ for (const remove of listeners) {
98
+ remove();
99
+ }
100
+ }
101
+ };
102
+ }
103
+
3
104
  const INFINITE_QUERY_KEY_MARKER = "infinite";
105
+ const RESPONSE_ERROR_NAME = "ResponseError";
106
+ class ResponseWithoutErrorBody extends Error {
107
+ name = RESPONSE_ERROR_NAME;
108
+ response;
109
+ status;
110
+ constructor(response) {
111
+ const statusText = response.statusText ? ` ${response.statusText}` : "";
112
+ super(`Request failed with status ${response.status}${statusText} and no error body`);
113
+ this.response = response;
114
+ this.status = response.status;
115
+ }
116
+ }
117
+ function isResponseError(error) {
118
+ return error instanceof Error && error.name === RESPONSE_ERROR_NAME;
119
+ }
4
120
  function throwIfFailed(result) {
5
- if (result.error !== void 0 || !result.response.ok) {
121
+ if (result.error !== void 0 && result.error !== null) {
6
122
  throw result.error;
7
123
  }
124
+ if (!result.response.ok) {
125
+ throw new ResponseWithoutErrorBody(result.response);
126
+ }
8
127
  }
9
128
  const DEFAULT_PAGE_PARAM_NAME = "cursor";
10
- function pageInit(init, { pageParamName, pageParam, signal }) {
129
+ function pageInit(init, { pageParamName, pageParam }) {
11
130
  const { params, ...rest } = init ?? {};
12
131
  const page = pageParam === void 0 ? {} : { [pageParamName]: pageParam };
13
- return { ...rest, signal, params: { ...params, query: { ...params?.query, ...page } } };
132
+ return { ...rest, params: { ...params, query: { ...params?.query, ...page } } };
14
133
  }
15
- function createClient(client) {
16
- const queryFn = async ({
17
- queryKey,
18
- signal
19
- }) => {
20
- const [method, path, init] = queryKey;
134
+ function createClient(client, { keyScope } = {}) {
135
+ const keyClient = { baseUrl: client.baseUrl, keyScope };
136
+ const fetchPage = async (method, path, init, signal) => {
21
137
  const mth = method.toUpperCase();
22
138
  const fn = client[mth];
23
- const result = await fn(path, { signal, ...init });
24
- throwIfFailed(result);
25
- return result.data ?? null;
139
+ const request = withQuerySignal(init, signal);
140
+ try {
141
+ const result = await fn(path, request.init);
142
+ throwIfFailed(result);
143
+ return result.data;
144
+ } finally {
145
+ request.release();
146
+ }
147
+ };
148
+ const queryOptions = (method, path, ...[init, options]) => {
149
+ const keyInit = queryKeyInit(init, keyClient);
150
+ return {
151
+ queryKey: keyInit === void 0 ? [method, path] : [method, path, keyInit],
152
+ // TanStack Query rejects `undefined` query data. The fetch client gives `undefined` for
153
+ // every success without a body: a 204, `Content-Length: 0`, an empty chunked body, a HEAD
154
+ // request (upstream #2234). A parsed body is never `undefined`, so this changes nothing else.
155
+ queryFn: async ({ signal }) => await fetchPage(method, path, init, signal) ?? null,
156
+ // Without options, `Options` is its constraint, which has no `initialData` to keep.
157
+ ...options
158
+ // The declared type types a defined `initialData` as the query data, a conditional type of
159
+ // the generic options that TypeScript cannot relate to the object; it is the same object.
160
+ };
26
161
  };
27
- const queryOptions = (method, path, ...[init, options]) => ({
28
- queryKey: init === void 0 ? [method, path] : [method, path, init],
29
- queryFn,
30
- ...options
31
- });
32
162
  return {
33
163
  queryOptions,
34
- useQuery: (method, path, ...[init, options, queryClient]) => useQuery(
35
- queryOptions(method, path, init, options),
36
- queryClient
164
+ useQuery: (method, path, ...[init, options, queryClient]) => (
165
+ // TanStack Query's `useQuery` returns `DefinedUseQueryResult` for options with defined
166
+ // `initialData`; while `Options` is generic it cannot pick that overload, and the declared
167
+ // `QueryResult` gives the caller the same answer. Both are the same object at runtime.
168
+ useQuery(
169
+ queryOptions(method, path, init, options),
170
+ queryClient
171
+ )
37
172
  ),
38
173
  useSuspenseQuery: (method, path, ...[init, options, queryClient]) => useSuspenseQuery(
39
174
  queryOptions(method, path, init, options),
@@ -43,21 +178,12 @@ function createClient(client) {
43
178
  useInfiniteQuery: (method, path, init, options, queryClient) => {
44
179
  const { pageParamName = DEFAULT_PAGE_PARAM_NAME, placeholderData, ...restOptions } = options;
45
180
  const { queryKey } = queryOptions(method, path, init);
46
- const mth = method.toUpperCase();
47
- const fn = client[mth];
48
181
  return useInfiniteQuery(
49
182
  {
50
183
  queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER],
51
- // The request comes from the arguments; the key only identifies the cache entry.
52
- queryFn: async ({ pageParam, signal }) => {
53
- const result = await fn(
54
- path,
55
- // biome-ignore lint/suspicious/noExplicitAny: a generic init; see queryFn
56
- pageInit(init, { pageParamName, pageParam, signal })
57
- );
58
- throwIfFailed(result);
59
- return result.data;
60
- },
184
+ // An empty page stays `undefined`, as in upstream: TanStack Query rejects only
185
+ // `undefined` query data, and the infinite query data is the `{ pages }` object.
186
+ queryFn: ({ pageParam, signal }) => fetchPage(method, path, pageInit(init, { pageParamName, pageParam }), signal),
61
187
  ...restOptions,
62
188
  // Typed with an `unknown` page param for the caller (`InfinitePlaceholderData`). TanStack
63
189
  // passes the previous data of this query, with its own page params, and takes it back.
@@ -85,5 +211,5 @@ function createClient(client) {
85
211
  };
86
212
  }
87
213
 
88
- export { createClient as default };
214
+ export { createClient as default, isResponseError };
89
215
  //# sourceMappingURL=index.mjs.map