openapi-typesafe-react-query 1.0.0-rc.0 → 1.0.0-rc.2

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/README.md CHANGED
@@ -1,20 +1,21 @@
1
1
  # openapi-typesafe-react-query
2
2
 
3
- openapi-typesafe-react-query is a small, type-safe wrapper (about 2.5 kB) around [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) to work with OpenAPI schema.
3
+ openapi-typesafe-react-query is a small, type-safe wrapper (about 1 kB minified) around [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) to work with OpenAPI schema.
4
4
 
5
5
  It works by using [openapi-typesafe-fetch](../openapi-typesafe-fetch) and [openapi-typesafe](../openapi-typesafe) so URLs, parameters, request bodies and responses are checked against your schema, with no manual types,
6
6
  no `any` and no `as` assertions.
7
7
 
8
8
  A maintained, drop-in successor to `openapi-react-query` (see the [repository README](../../README.md)).
9
9
 
10
- > **Status: in development; not published to npm yet.**
10
+ > **Release candidate.** The current version is a 1.0.0 release candidate (`1.0.0-rc.N`);
11
+ > names and output can still change before 1.0.0.
11
12
 
12
13
  ## Setup
13
14
 
14
15
  Install this library along with [openapi-typesafe-fetch](../openapi-typesafe-fetch) and [openapi-typesafe](../openapi-typesafe):
15
16
 
16
17
  ```bash
17
- npm i openapi-typesafe-react-query openapi-typesafe-fetch
18
+ npm i openapi-typesafe-react-query openapi-typesafe-fetch @tanstack/react-query
18
19
  npm i -D openapi-typesafe typescript
19
20
  ```
20
21
 
@@ -45,10 +46,10 @@ const MyComponent = () => {
45
46
  },
46
47
  });
47
48
 
48
- if (isPending || !data) return "Loading...";
49
-
50
49
  if (error) return `An error occurred: ${error.message}`;
51
50
 
51
+ if (isPending || !data) return "Loading...";
52
+
52
53
  return <div>{data.title}</div>;
53
54
  };
54
55
  ```
@@ -62,6 +63,12 @@ const MyComponent = () => {
62
63
  + import createClient from "openapi-typesafe-react-query";
63
64
  ```
64
65
 
66
+ ## Versioning
67
+
68
+ Semantic versioning from 1.0.0, with one version for all openapi-typesafe packages. What the
69
+ version promises, and what may grow in a minor release, is listed under
70
+ [Versioning](https://github.com/openapi-typesafe/openapi-typesafe/blob/main/docs/compatibility.md#versioning).
71
+
65
72
  ## License
66
73
 
67
74
  [MIT](./LICENSE)
package/dist/index.cjs CHANGED
@@ -16,18 +16,15 @@ function pageInit(init, { pageParamName, pageParam, signal }) {
16
16
  }
17
17
  function createClient(client) {
18
18
  const queryFn = async ({
19
- queryKey: [method, path, init],
19
+ queryKey,
20
20
  signal
21
21
  }) => {
22
+ const [method, path, init] = queryKey;
22
23
  const mth = method.toUpperCase();
23
24
  const fn = client[mth];
24
25
  const result = await fn(path, { signal, ...init });
25
26
  throwIfFailed(result);
26
- const { data, response } = result;
27
- if (response.status === 204 || response.headers.get("Content-Length") === "0") {
28
- return data ?? null;
29
- }
30
- return data;
27
+ return result.data ?? null;
31
28
  };
32
29
  const queryOptions = (method, path, ...[init, options]) => ({
33
30
  queryKey: init === void 0 ? [method, path] : [method, path, init],
@@ -36,10 +33,17 @@ function createClient(client) {
36
33
  });
37
34
  return {
38
35
  queryOptions,
39
- useQuery: (method, path, ...[init, options, queryClient]) => reactQuery.useQuery(queryOptions(method, path, init, options), queryClient),
40
- useSuspenseQuery: (method, path, ...[init, options, queryClient]) => reactQuery.useSuspenseQuery(queryOptions(method, path, init, options), queryClient),
36
+ useQuery: (method, path, ...[init, options, queryClient]) => reactQuery.useQuery(
37
+ queryOptions(method, path, init, options),
38
+ queryClient
39
+ ),
40
+ useSuspenseQuery: (method, path, ...[init, options, queryClient]) => reactQuery.useSuspenseQuery(
41
+ queryOptions(method, path, init, options),
42
+ queryClient
43
+ ),
44
+ // biome-ignore lint/complexity/useMaxParams: public signature of 1.x (ADR-036)
41
45
  useInfiniteQuery: (method, path, init, options, queryClient) => {
42
- const { pageParamName = DEFAULT_PAGE_PARAM_NAME, ...restOptions } = options;
46
+ const { pageParamName = DEFAULT_PAGE_PARAM_NAME, placeholderData, ...restOptions } = options;
43
47
  const { queryKey } = queryOptions(method, path, init);
44
48
  const mth = method.toUpperCase();
45
49
  const fn = client[mth];
@@ -48,11 +52,19 @@ function createClient(client) {
48
52
  queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER],
49
53
  // The request comes from the arguments; the key only identifies the cache entry.
50
54
  queryFn: async ({ pageParam, signal }) => {
51
- const result = await fn(path, pageInit(init, { pageParamName, pageParam, signal }));
55
+ const result = await fn(
56
+ path,
57
+ // biome-ignore lint/suspicious/noExplicitAny: a generic init; see queryFn
58
+ pageInit(init, { pageParamName, pageParam, signal })
59
+ );
52
60
  throwIfFailed(result);
53
61
  return result.data;
54
62
  },
55
- ...restOptions
63
+ ...restOptions,
64
+ // Typed with an `unknown` page param for the caller (`InfinitePlaceholderData`). TanStack
65
+ // passes the previous data of this query, with its own page params, and takes it back.
66
+ // biome-ignore lint/suspicious/noExplicitAny: TanStack's type carries its own page params
67
+ placeholderData
56
68
  },
57
69
  queryClient
58
70
  );
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["import {\n type DataTag,\n type InfiniteData,\n type QueryClient,\n type QueryFunctionContext,\n type SkipToken,\n type UseInfiniteQueryOptions,\n type UseInfiniteQueryResult,\n type UseMutationOptions,\n type UseMutationResult,\n type UseQueryOptions,\n type UseQueryResult,\n type UseSuspenseQueryOptions,\n type UseSuspenseQueryResult,\n useInfiniteQuery,\n useMutation,\n useQuery,\n useSuspenseQuery,\n} from \"@tanstack/react-query\";\nimport type {\n ClientMethod,\n DefaultParamsOption,\n Client as FetchClient,\n FetchResponse,\n MaybeOptionalInit,\n} from \"openapi-typesafe-fetch\";\nimport type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from \"openapi-typesafe-helpers\";\n\n// Helper type to dynamically infer the type from the `select` property\ntype InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;\n\ntype InitWithUnknowns<Init> = Init & { [key: string]: unknown };\n\nexport type QueryKey<\n Paths extends Record<string, Record<HttpMethod, {}>>,\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init = MaybeOptionalInit<Paths[Path], Method>,\n> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];\n\n/**\n * The last element of an infinite query's key. `useQuery` and `useInfiniteQuery` cache different\n * data (`TData` and `InfiniteData<TData>`), so the same request must not share one key (upstream\n * #2184, ADR-022). A prefix such as `[method, path]` still matches both. Not exported: a runtime\n * named export next to the default export would change what `require()` returns.\n */\nconst INFINITE_QUERY_KEY_MARKER = \"infinite\";\n\nexport type InfiniteQueryKey<\n Paths extends Record<string, Record<HttpMethod, {}>>,\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n> = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];\n\nexport type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types\n Options extends Omit<\n UseQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<Response[\"data\"], Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n path: Path,\n ...[init, options]: RequiredKeysOf<Init> extends never\n ? [InitWithUnknowns<Init>?, Options?]\n : [InitWithUnknowns<Init>, Options?]\n) => NoInfer<\n Omit<\n UseQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<Response[\"data\"], Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryFn\" | \"queryKey\"\n > & {\n queryKey: DataTag<QueryKey<Paths, Method, Path>, Response[\"data\"], Response[\"error\"]>;\n queryFn: Exclude<\n UseQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<Response[\"data\"], Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >[\"queryFn\"],\n SkipToken | undefined\n >;\n }\n>;\n\nexport type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types\n Options extends Omit<\n UseQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<Response[\"data\"], Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n url: Path,\n ...[init, options, queryClient]: RequiredKeysOf<Init> extends never\n ? [InitWithUnknowns<Init>?, Options?, QueryClient?]\n : [InitWithUnknowns<Init>, Options?, QueryClient?]\n) => UseQueryResult<InferSelectReturnType<Response[\"data\"], Options[\"select\"]>, Response[\"error\"]>;\n\nexport type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,\n Options extends Omit<\n UseInfiniteQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<InfiniteData<Response[\"data\"]>, Options[\"select\"]>,\n InfiniteQueryKey<Paths, Method, Path>,\n unknown\n >,\n \"queryKey\" | \"queryFn\"\n > & {\n pageParamName?: string;\n },\n>(\n method: Method,\n url: Path,\n init: InitWithUnknowns<Init>,\n options: Options,\n queryClient?: QueryClient,\n) => UseInfiniteQueryResult<\n InferSelectReturnType<InfiniteData<Response[\"data\"]>, Options[\"select\"]>,\n Response[\"error\"]\n>;\n\nexport type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types\n Options extends Omit<\n UseSuspenseQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<Response[\"data\"], Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n url: Path,\n ...[init, options, queryClient]: RequiredKeysOf<Init> extends never\n ? [InitWithUnknowns<Init>?, Options?, QueryClient?]\n : [InitWithUnknowns<Init>, Options?, QueryClient?]\n) => UseSuspenseQueryResult<InferSelectReturnType<Response[\"data\"], Options[\"select\"]>, Response[\"error\"]>;\n\nexport type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types\n TOnMutateResult = unknown,\n>(\n method: Method,\n url: Path,\n options?: Omit<\n UseMutationOptions<Response[\"data\"], Response[\"error\"], Init, TOnMutateResult>,\n \"mutationKey\" | \"mutationFn\"\n >,\n queryClient?: QueryClient,\n) => UseMutationResult<Response[\"data\"], Response[\"error\"], Init, TOnMutateResult>;\n\nexport interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {\n queryOptions: QueryOptionsFunction<Paths, Media>;\n useQuery: UseQueryMethod<Paths, Media>;\n useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;\n useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;\n useMutation: UseMutationMethod<Paths, Media>;\n}\n\nexport type MethodResponse<\n CreatedClient extends OpenapiQueryClient<any, any>,\n Method extends HttpMethod,\n Path extends CreatedClient extends OpenapiQueryClient<infer Paths, infer _Media>\n ? PathsWithMethod<Paths, Method>\n : never,\n Options = object,\n> =\n CreatedClient extends OpenapiQueryClient<infer Paths extends { [key: string]: any }, infer Media extends MediaType>\n ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>[\"data\"]>\n : never;\n\n/**\n * Throw the error of a failed fetch result. `if (error)` missed two failures: a non-OK response with\n * an empty body (`error` is `undefined`, upstream #2070, #2829) and a falsy error body such as `0`.\n */\nfunction throwIfFailed(result: { error?: unknown; response: Response }): void {\n if (result.error !== undefined || !result.response.ok) {\n throw result.error;\n }\n}\n\n/** The query parameter that carries the page param unless `pageParamName` names another. */\nconst DEFAULT_PAGE_PARAM_NAME = \"cursor\";\n\ninterface PageRequest {\n readonly pageParamName: string;\n readonly pageParam: unknown;\n readonly signal: AbortSignal;\n}\n\n/**\n * The request init of one page. An `undefined` page param (the first page with\n * `initialPageParam: undefined`) is left out instead of being sent as `0` (upstream #2715, #2340,\n * ADR-022), so a cursor API gets no cursor on the first page.\n */\nfunction pageInit(init: unknown, { pageParamName, pageParam, signal }: PageRequest) {\n const { params, ...rest } = (init ?? {}) as { params?: { query?: DefaultParamsOption } };\n const page = pageParam === undefined ? {} : { [pageParamName]: pageParam };\n return { ...rest, signal, params: { ...params, query: { ...params?.query, ...page } } };\n}\n\n// TODO: Add the ability to bring queryClient as argument\nexport default function createClient<Paths extends {}, Media extends MediaType = MediaType>(\n client: FetchClient<Paths, Media>,\n): OpenapiQueryClient<Paths, Media> {\n const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({\n queryKey: [method, path, init],\n signal,\n }: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n const result = await fn(path, { signal, ...(init as any) }); // TODO: find a way to avoid as any\n throwIfFailed(result);\n const { data, response } = result;\n if (response.status === 204 || response.headers.get(\"Content-Length\") === \"0\") {\n return data ?? null;\n }\n\n return data;\n };\n\n const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({\n queryKey: (init === undefined ? ([method, path] as const) : ([method, path, init] as const)) as DataTag<\n QueryKey<Paths, typeof method, typeof path>,\n any,\n any\n >,\n queryFn,\n ...options,\n });\n\n return {\n queryOptions,\n useQuery: (method, path, ...[init, options, queryClient]) =>\n useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),\n useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>\n useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),\n useInfiniteQuery: (method, path, init, options, queryClient) => {\n const { pageParamName = DEFAULT_PAGE_PARAM_NAME, ...restOptions } = options;\n const { queryKey } = queryOptions(method, path, init);\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n return useInfiniteQuery(\n {\n queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER] as const,\n // The request comes from the arguments; the key only identifies the cache entry.\n queryFn: async ({ pageParam, signal }) => {\n const result = await fn(path, pageInit(init, { pageParamName, pageParam, signal }) as any);\n throwIfFailed(result);\n return result.data;\n },\n ...restOptions,\n },\n queryClient,\n );\n },\n useMutation: (method, path, options, queryClient) =>\n useMutation(\n {\n mutationKey: [method, path],\n mutationFn: async (init) => {\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n const result = await fn(path, init as InitWithUnknowns<typeof init>);\n throwIfFailed(result);\n const { data } = result;\n\n return data as Exclude<typeof data, undefined>;\n },\n ...options,\n },\n queryClient,\n ),\n };\n}\n"],"names":["useQuery","useSuspenseQuery","useInfiniteQuery","useMutation"],"mappings":";;;;AA8CA,MAAM,yBAAA,GAA4B,UAAA;AAmKlC,SAAS,cAAc,MAAA,EAAuD;AAC5E,EAAA,IAAI,OAAO,KAAA,KAAU,MAAA,IAAa,CAAC,MAAA,CAAO,SAAS,EAAA,EAAI;AACrD,IAAA,MAAM,MAAA,CAAO,KAAA;AAAA,EACf;AACF;AAGA,MAAM,uBAAA,GAA0B,QAAA;AAahC,SAAS,SAAS,IAAA,EAAe,EAAE,aAAA,EAAe,SAAA,EAAW,QAAO,EAAgB;AAClF,EAAA,MAAM,EAAE,MAAA,EAAQ,GAAG,IAAA,EAAK,GAAK,QAAQ,EAAC;AACtC,EAAA,MAAM,IAAA,GAAO,cAAc,MAAA,GAAY,KAAK,EAAE,CAAC,aAAa,GAAG,SAAA,EAAU;AACzE,EAAA,OAAO,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,EAAE,GAAG,MAAA,EAAQ,KAAA,EAAO,EAAE,GAAG,MAAA,EAAQ,KAAA,EAAO,GAAG,IAAA,IAAO,EAAE;AACxF;AAGA,SAAwB,aACtB,MAAA,EACkC;AAClC,EAAA,MAAM,UAAU,OAA+E;AAAA,IAC7F,QAAA,EAAU,CAAC,MAAA,EAAQ,IAAA,EAAM,IAAI,CAAA;AAAA,IAC7B;AAAA,GACF,KAA2D;AACzD,IAAA,MAAM,GAAA,GAAM,OAAO,WAAA,EAAY;AAC/B,IAAA,MAAM,EAAA,GAAK,OAAO,GAAG,CAAA;AACrB,IAAA,MAAM,MAAA,GAAS,MAAM,EAAA,CAAG,IAAA,EAAM,EAAE,MAAA,EAAQ,GAAI,MAAc,CAAA;AAC1D,IAAA,aAAA,CAAc,MAAM,CAAA;AACpB,IAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAS,GAAI,MAAA;AAC3B,IAAA,IAAI,QAAA,CAAS,WAAW,GAAA,IAAO,QAAA,CAAS,QAAQ,GAAA,CAAI,gBAAgB,MAAM,GAAA,EAAK;AAC7E,MAAA,OAAO,IAAA,IAAQ,IAAA;AAAA,IACjB;AAEA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,eAAmD,CAAC,MAAA,EAAQ,SAAS,CAAC,IAAA,EAAM,OAAO,CAAA,MAAO;AAAA,IAC9F,QAAA,EAAW,IAAA,KAAS,MAAA,GAAa,CAAC,MAAA,EAAQ,IAAI,CAAA,GAAe,CAAC,MAAA,EAAQ,IAAA,EAAM,IAAI,CAAA;AAAA,IAKhF,OAAA;AAAA,IACA,GAAG;AAAA,GACL,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,YAAA;AAAA,IACA,UAAU,CAAC,MAAA,EAAQ,IAAA,EAAA,GAAS,CAAC,MAAM,OAAA,EAAS,WAAW,CAAA,KACrDA,mBAAA,CAAS,aAAa,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAuC,OAAO,GAAG,WAAW,CAAA;AAAA,IAClG,kBAAkB,CAAC,MAAA,EAAQ,IAAA,EAAA,GAAS,CAAC,MAAM,OAAA,EAAS,WAAW,CAAA,KAC7DC,2BAAA,CAAiB,aAAa,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAuC,OAAO,GAAG,WAAW,CAAA;AAAA,IAC1G,kBAAkB,CAAC,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAM,SAAS,WAAA,KAAgB;AAC9D,MAAA,MAAM,EAAE,aAAA,GAAgB,uBAAA,EAAyB,GAAG,aAAY,GAAI,OAAA;AACpE,MAAA,MAAM,EAAE,QAAA,EAAS,GAAI,YAAA,CAAa,MAAA,EAAQ,MAAM,IAAI,CAAA;AACpD,MAAA,MAAM,GAAA,GAAM,OAAO,WAAA,EAAY;AAC/B,MAAA,MAAM,EAAA,GAAK,OAAO,GAAG,CAAA;AACrB,MAAA,OAAOC,2BAAA;AAAA,QACL;AAAA,UACE,QAAA,EAAU,CAAC,GAAG,QAAA,EAAU,yBAAyB,CAAA;AAAA;AAAA,UAEjD,OAAA,EAAS,OAAO,EAAE,SAAA,EAAW,QAAO,KAAM;AACxC,YAAA,MAAM,MAAA,GAAS,MAAM,EAAA,CAAG,IAAA,EAAM,QAAA,CAAS,IAAA,EAAM,EAAE,aAAA,EAAe,SAAA,EAAW,MAAA,EAAQ,CAAQ,CAAA;AACzF,YAAA,aAAA,CAAc,MAAM,CAAA;AACpB,YAAA,OAAO,MAAA,CAAO,IAAA;AAAA,UAChB,CAAA;AAAA,UACA,GAAG;AAAA,SACL;AAAA,QACA;AAAA,OACF;AAAA,IACF,CAAA;AAAA,IACA,WAAA,EAAa,CAAC,MAAA,EAAQ,IAAA,EAAM,SAAS,WAAA,KACnCC,sBAAA;AAAA,MACE;AAAA,QACE,WAAA,EAAa,CAAC,MAAA,EAAQ,IAAI,CAAA;AAAA,QAC1B,UAAA,EAAY,OAAO,IAAA,KAAS;AAC1B,UAAA,MAAM,GAAA,GAAM,OAAO,WAAA,EAAY;AAC/B,UAAA,MAAM,EAAA,GAAK,OAAO,GAAG,CAAA;AACrB,UAAA,MAAM,MAAA,GAAS,MAAM,EAAA,CAAG,IAAA,EAAM,IAAqC,CAAA;AACnE,UAAA,aAAA,CAAc,MAAM,CAAA;AACpB,UAAA,MAAM,EAAE,MAAK,GAAI,MAAA;AAEjB,UAAA,OAAO,IAAA;AAAA,QACT,CAAA;AAAA,QACA,GAAG;AAAA,OACL;AAAA,MACA;AAAA;AACF,GACJ;AACF;;"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["import {\n type DataTag,\n type InfiniteData,\n type QueryClient,\n type QueryFunctionContext,\n type SkipToken,\n type UseInfiniteQueryOptions,\n type UseInfiniteQueryResult,\n type UseMutationOptions,\n type UseMutationResult,\n type UseQueryOptions,\n type UseQueryResult,\n type UseSuspenseQueryOptions,\n type UseSuspenseQueryResult,\n useInfiniteQuery,\n useMutation,\n useQuery,\n useSuspenseQuery,\n} from \"@tanstack/react-query\";\nimport type {\n ClientMethod,\n DefaultParamsOption,\n Client as FetchClient,\n FetchResponse,\n MaybeOptionalInit,\n PathOperation,\n} from \"openapi-typesafe-fetch\";\nimport type {\n HttpMethod,\n MediaType,\n PathsWithMethod,\n RequiredKeysOf,\n} from \"openapi-typesafe-helpers\";\n\n// Helper type to dynamically infer the type from the `select` property\ntype InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;\n\n/**\n * Query data of a response type: a success without a body is `undefined` in the fetch client and\n * `null` in the query, as `queryFn` resolves it (upstream #2234, #2464).\n */\ntype QueryData<Data> = undefined extends Data ? Exclude<Data, undefined> | null : Data;\n\ntype InitWithUnknowns<Init> = Init & { [key: string]: unknown };\n\n/**\n * The arguments after the path: the init, required when the operation requires a parameter or a\n * body, then `Rest`. The condition is openapi-react-query's, as `InitParam` of the fetch client\n * is openapi-fetch's (ADR-030, decision 2): TypeScript relates two conditional types only when\n * their `extends` types are the same, so a hook type copied from openapi-react-query takes these\n * hooks. For a union of inits, `MaybeOptionalInit` leaves out `undefined` when one is required,\n * so `Parameters<typeof $api.queryOptions>` still requires it then (upstream #2178).\n */\ntype InitArguments<Init, Rest extends unknown[]> =\n RequiredKeysOf<Init> extends never\n ? [InitWithUnknowns<Init>?, ...Rest]\n : [InitWithUnknowns<Init>, ...Rest];\n\nexport type QueryKey<\n Paths extends {},\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init = MaybeOptionalInit<Paths[Path], Method>,\n> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];\n\n/**\n * The last element of an infinite query's key. `useQuery` and `useInfiniteQuery` cache different\n * data (`TData` and `InfiniteData<TData>`), so the same request must not share one key (upstream\n * #2184, ADR-022). A prefix such as `[method, path]` still matches both. Not exported: a runtime\n * named export next to the default export would change what `require()` returns.\n */\nconst INFINITE_QUERY_KEY_MARKER = \"infinite\";\n\nexport type InfiniteQueryKey<\n Paths extends {},\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n> = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];\n\nexport type QueryOptionsFunction<Paths extends {}, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n // note: Required is used to avoid repeating NonNullable in UseQuery types\n Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,\n Options extends Omit<\n UseQueryOptions<\n QueryData<Response[\"data\"]>,\n Response[\"error\"],\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n path: Path,\n ...[init, options]: InitArguments<Init, [Options?]>\n) => NoInfer<\n Omit<\n UseQueryOptions<\n QueryData<Response[\"data\"]>,\n Response[\"error\"],\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryFn\" | \"queryKey\"\n > & {\n queryKey: DataTag<\n QueryKey<Paths, Method, Path>,\n QueryData<Response[\"data\"]>,\n Response[\"error\"]\n >;\n queryFn: Exclude<\n UseQueryOptions<\n QueryData<Response[\"data\"]>,\n Response[\"error\"],\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >[\"queryFn\"],\n SkipToken | undefined\n >;\n }\n>;\n\nexport type UseQueryMethod<Paths extends {}, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n // note: Required is used to avoid repeating NonNullable in UseQuery types\n Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,\n Options extends Omit<\n UseQueryOptions<\n QueryData<Response[\"data\"]>,\n Response[\"error\"],\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n url: Path,\n ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>\n) => UseQueryResult<\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n Response[\"error\"]\n>;\n\n/**\n * The type of the page params: the initial one and the ones `getNextPageParam` and\n * `getPreviousPageParam` return (upstream #2458). It was `unknown`. It stays `unknown` when a\n * return type is untyped, so code that compiled still compiles (roadmap §3): TypeScript cannot\n * infer the return type of a callback that computes it from an unannotated `lastPageParam`,\n * whose type depends on that return type, and gives `never` or `unknown`.\n */\ntype PageParam<Initial, Next, Previous> =\n IsUntyped<Next> extends true\n ? unknown\n : [Previous] extends [NoPreviousPageParam]\n ? NullishToUnknown<Initial | NonNullable<Next>>\n : IsUntyped<Previous> extends true\n ? unknown\n : NullishToUnknown<Initial | NonNullable<Next> | NonNullable<Previous>>;\n\n/** The `Previous` type parameter when the options have no `getPreviousPageParam`. */\ndeclare const NO_PREVIOUS_PAGE_PARAM: unique symbol;\ntype NoPreviousPageParam = typeof NO_PREVIOUS_PAGE_PARAM;\n\ntype IsUntyped<T> = [T] extends [never] ? true : unknown extends T ? true : false;\n\n/** Only `undefined` or `null` names no type: a cursor API's `initialPageParam: undefined`. */\ntype NullishToUnknown<T> = [T] extends [undefined | null] ? unknown : T;\n\n/** Read only for the return type; `never` arguments add no inference for TanStack's page param. */\ntype PageParamFunction<Result> = (...args: never) => Result;\n\n/**\n * Inference sites of the page param types. Intersected before `Options`, so TanStack's own\n * inference in the implementation reads the full signatures of `Options`.\n */\ninterface PageParamSources<Initial, Next, Previous> {\n initialPageParam: Initial;\n getNextPageParam: PageParamFunction<Next>;\n getPreviousPageParam?: PageParamFunction<Previous>;\n}\n\n/**\n * `placeholderData` of `useInfiniteQuery`, with an `unknown` page param as before: TypeScript types\n * a `(previousData) => previousData` callback while it still infers the page param, so it got\n * `unknown` there and failed against the typed page param.\n */\ntype InfinitePlaceholderData<\n Data,\n Error,\n Paths extends {},\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n> = UseInfiniteQueryOptions<\n Data,\n Error,\n InfiniteData<Data, unknown>,\n InfiniteQueryKey<Paths, Method, Path>,\n unknown\n>[\"placeholderData\"];\n\nexport type UseInfiniteQueryMethod<Paths extends {}, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,\n Options extends Omit<\n UseInfiniteQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<\n InfiniteData<Response[\"data\"], PageParam<Initial, Next, Previous>>,\n Options[\"select\"]\n >,\n InfiniteQueryKey<Paths, Method, Path>,\n PageParam<Initial, Next, Previous>\n >,\n \"queryKey\" | \"queryFn\" | \"placeholderData\"\n > & {\n placeholderData?: InfinitePlaceholderData<\n Response[\"data\"],\n Response[\"error\"],\n Paths,\n Method,\n Path\n >;\n pageParamName?: string;\n },\n Initial = unknown,\n Next = never,\n Previous = NoPreviousPageParam,\n // biome-ignore lint/complexity/useMaxParams: public signature of 1.x (ADR-036)\n>(\n method: Method,\n url: Path,\n init: InitWithUnknowns<Init>,\n options: PageParamSources<Initial, Next, Previous> & Options,\n queryClient?: QueryClient,\n) => UseInfiniteQueryResult<\n InferSelectReturnType<\n InfiniteData<Response[\"data\"], PageParam<Initial, Next, Previous>>,\n Options[\"select\"]\n >,\n Response[\"error\"]\n>;\n\nexport type UseSuspenseQueryMethod<Paths extends {}, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n // note: Required is used to avoid repeating NonNullable in UseQuery types\n Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,\n Options extends Omit<\n UseSuspenseQueryOptions<\n QueryData<Response[\"data\"]>,\n Response[\"error\"],\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n url: Path,\n ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>\n) => UseSuspenseQueryResult<\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n Response[\"error\"]\n>;\n\nexport type UseMutationMethod<Paths extends {}, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n // note: Required is used to avoid repeating NonNullable in UseQuery types\n Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,\n TOnMutateResult = unknown,\n>(\n method: Method,\n url: Path,\n options?: Omit<\n UseMutationOptions<Response[\"data\"], Response[\"error\"], Init, TOnMutateResult>,\n \"mutationKey\" | \"mutationFn\"\n >,\n queryClient?: QueryClient,\n) => UseMutationResult<Response[\"data\"], Response[\"error\"], Init, TOnMutateResult>;\n\nexport interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {\n queryOptions: QueryOptionsFunction<Paths, Media>;\n useQuery: UseQueryMethod<Paths, Media>;\n useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;\n useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;\n useMutation: UseMutationMethod<Paths, Media>;\n}\n\nexport type MethodResponse<\n // biome-ignore lint/suspicious/noExplicitAny: matches a client of any paths, as upstream's\n CreatedClient extends OpenapiQueryClient<any, any>,\n Method extends HttpMethod,\n Path extends CreatedClient extends OpenapiQueryClient<infer Paths, infer _Media>\n ? PathsWithMethod<Paths, Method>\n : never,\n Options = object,\n> =\n CreatedClient extends OpenapiQueryClient<\n // biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces\n infer Paths extends { [key: string]: any },\n infer Media extends MediaType\n >\n ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>[\"data\"]>\n : never;\n\n/**\n * Throw the error of a failed fetch result. `if (error)` missed two failures: a non-OK response\n * with an empty body (`error` is `undefined`, upstream #2070, #2829) and a falsy error body such as\n * `0`.\n */\nfunction throwIfFailed(result: { error?: unknown; response: Response }): void {\n if (result.error !== undefined || !result.response.ok) {\n throw result.error;\n }\n}\n\n/** The query parameter that carries the page param unless `pageParamName` names another. */\nconst DEFAULT_PAGE_PARAM_NAME = \"cursor\";\n\ninterface PageRequest {\n readonly pageParamName: string;\n readonly pageParam: unknown;\n readonly signal: AbortSignal;\n}\n\n/**\n * The request init of one page. An `undefined` page param (the first page with\n * `initialPageParam: undefined`) is left out instead of being sent as `0` (upstream #2715, #2340,\n * ADR-022), so a cursor API gets no cursor on the first page.\n */\nfunction pageInit(init: unknown, { pageParamName, pageParam, signal }: PageRequest) {\n const { params, ...rest } = (init ?? {}) as { params?: { query?: DefaultParamsOption } };\n const page = pageParam === undefined ? {} : { [pageParamName]: pageParam };\n return { ...rest, signal, params: { ...params, query: { ...params?.query, ...page } } };\n}\n\n/**\n * Hooks and query options for a fetch client. A `QueryClient` other than the one from the React\n * context is passed to each hook as its last argument; `createClient` does not take one.\n */\n// biome-ignore lint/complexity/noExcessiveLinesPerFunction: inherited, kept until 1.1 (ADR-036)\nexport default function createClient<Paths extends {}, Media extends MediaType = MediaType>(\n client: FetchClient<Paths, Media>,\n): OpenapiQueryClient<Paths, Media> {\n const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({\n queryKey,\n signal,\n }: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {\n // While `Path` is generic, TypeScript cannot work out that the key is a tuple: the init is a\n // conditional type per operation (`MaybeOptionalInit`), and the key's type waits for it.\n const [method, path, init] = queryKey as readonly [Method, Path, unknown?];\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n // The init is `unknown` here. An `any` init lets the call infer the generic response type\n // that `queryOptions` declares; a typed init makes TypeScript compare two unresolved\n // conditional types, which it cannot do.\n // biome-ignore lint/suspicious/noExplicitAny: see the comment above\n const result = await fn(path, { signal, ...(init as any) });\n throwIfFailed(result);\n // TanStack Query rejects `undefined` query data. The fetch client gives `undefined` for every\n // success without a body: a 204, `Content-Length: 0`, an empty chunked body, a HEAD request\n // (upstream #2234). A parsed body is never `undefined`, so this changes nothing else.\n return result.data ?? null;\n };\n\n const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({\n queryKey: (init === undefined\n ? ([method, path] as const)\n : ([method, path, init] as const)) as DataTag<\n QueryKey<Paths, typeof method, typeof path>,\n // biome-ignore lint/suspicious/noExplicitAny: the declared return type sets the data type\n any,\n // biome-ignore lint/suspicious/noExplicitAny: the declared return type sets the error type\n any\n >,\n queryFn,\n ...options,\n });\n\n return {\n queryOptions,\n useQuery: (method, path, ...[init, options, queryClient]) =>\n useQuery(\n queryOptions(method, path, init as InitWithUnknowns<typeof init>, options),\n queryClient,\n ),\n useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>\n useSuspenseQuery(\n queryOptions(method, path, init as InitWithUnknowns<typeof init>, options),\n queryClient,\n ),\n // biome-ignore lint/complexity/useMaxParams: public signature of 1.x (ADR-036)\n useInfiniteQuery: (method, path, init, options, queryClient) => {\n const { pageParamName = DEFAULT_PAGE_PARAM_NAME, placeholderData, ...restOptions } = options;\n const { queryKey } = queryOptions(method, path, init);\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n return useInfiniteQuery(\n {\n queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER] as const,\n // The request comes from the arguments; the key only identifies the cache entry.\n queryFn: async ({ pageParam, signal }) => {\n const result = await fn(\n path,\n // biome-ignore lint/suspicious/noExplicitAny: a generic init; see queryFn\n pageInit(init, { pageParamName, pageParam, signal }) as any,\n );\n throwIfFailed(result);\n // An empty page stays `undefined`, as in upstream: TanStack Query rejects only\n // `undefined` query data, and the infinite query data is the `{ pages }` object.\n return result.data;\n },\n ...restOptions,\n // Typed with an `unknown` page param for the caller (`InfinitePlaceholderData`). TanStack\n // passes the previous data of this query, with its own page params, and takes it back.\n // biome-ignore lint/suspicious/noExplicitAny: TanStack's type carries its own page params\n placeholderData: placeholderData as any,\n },\n queryClient,\n );\n },\n useMutation: (method, path, options, queryClient) =>\n useMutation(\n {\n mutationKey: [method, path],\n mutationFn: async (init) => {\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n const result = await fn(path, init as InitWithUnknowns<typeof init>);\n throwIfFailed(result);\n // TanStack Query accepts `undefined` mutation data, so a success without a body stays\n // `undefined`, as in upstream; only query data needs `null` (upstream #2234). The cast\n // only bridges the generic types: `Response[\"data\"]` still has `undefined` for an\n // operation with a response without a body.\n const { data } = result;\n return data as Exclude<typeof data, undefined>;\n },\n ...options,\n },\n queryClient,\n ),\n };\n}\n"],"names":["useQuery","useSuspenseQuery","useInfiniteQuery","useMutation"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,24 +1,76 @@
1
1
  import { UseQueryOptions, DataTag, SkipToken, QueryClient, UseQueryResult, UseSuspenseQueryOptions, UseSuspenseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
2
- import { MaybeOptionalInit, FetchResponse, Client } from 'openapi-typesafe-fetch';
2
+ import { MaybeOptionalInit, FetchResponse, PathOperation, Client } from 'openapi-typesafe-fetch';
3
3
  import { HttpMethod, PathsWithMethod, MediaType, RequiredKeysOf } from 'openapi-typesafe-helpers';
4
4
 
5
5
  type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
6
+ /**
7
+ * Query data of a response type: a success without a body is `undefined` in the fetch client and
8
+ * `null` in the query, as `queryFn` resolves it (upstream #2234, #2464).
9
+ */
10
+ type QueryData<Data> = undefined extends Data ? Exclude<Data, undefined> | null : Data;
6
11
  type InitWithUnknowns<Init> = Init & {
7
12
  [key: string]: unknown;
8
13
  };
9
- type QueryKey<Paths extends Record<string, Record<HttpMethod, {}>>, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init = MaybeOptionalInit<Paths[Path], Method>> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
14
+ /**
15
+ * The arguments after the path: the init, required when the operation requires a parameter or a
16
+ * body, then `Rest`. The condition is openapi-react-query's, as `InitParam` of the fetch client
17
+ * is openapi-fetch's (ADR-030, decision 2): TypeScript relates two conditional types only when
18
+ * their `extends` types are the same, so a hook type copied from openapi-react-query takes these
19
+ * hooks. For a union of inits, `MaybeOptionalInit` leaves out `undefined` when one is required,
20
+ * so `Parameters<typeof $api.queryOptions>` still requires it then (upstream #2178).
21
+ */
22
+ type InitArguments<Init, Rest extends unknown[]> = RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, ...Rest] : [InitWithUnknowns<Init>, ...Rest];
23
+ 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
+ /**
25
+ * The last element of an infinite query's key. `useQuery` and `useInfiniteQuery` cache different
26
+ * 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.
29
+ */
10
30
  declare const INFINITE_QUERY_KEY_MARKER = "infinite";
11
- type InfiniteQueryKey<Paths extends Record<string, Record<HttpMethod, {}>>, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>> = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];
12
- type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, path: Path, ...[init, options]: RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, Options?] : [InitWithUnknowns<Init>, Options?]) => NoInfer<Omit<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryFn" | "queryKey"> & {
13
- queryKey: DataTag<QueryKey<Paths, Method, Path>, Response["data"], Response["error"]>;
14
- queryFn: Exclude<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>["queryFn"], SkipToken | undefined>;
31
+ 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>;
15
35
  }>;
16
- type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, Options?, QueryClient?] : [InitWithUnknowns<Init>, Options?, QueryClient?]) => UseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
17
- type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseInfiniteQueryOptions<Response["data"], Response["error"], InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>, InfiniteQueryKey<Paths, Method, Path>, unknown>, "queryKey" | "queryFn"> & {
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"]>;
37
+ /**
38
+ * The type of the page params: the initial one and the ones `getNextPageParam` and
39
+ * `getPreviousPageParam` return (upstream #2458). It was `unknown`. It stays `unknown` when a
40
+ * return type is untyped, so code that compiled still compiles (roadmap §3): TypeScript cannot
41
+ * infer the return type of a callback that computes it from an unannotated `lastPageParam`,
42
+ * whose type depends on that return type, and gives `never` or `unknown`.
43
+ */
44
+ type PageParam<Initial, Next, Previous> = IsUntyped<Next> extends true ? unknown : [Previous] extends [NoPreviousPageParam] ? NullishToUnknown<Initial | NonNullable<Next>> : IsUntyped<Previous> extends true ? unknown : NullishToUnknown<Initial | NonNullable<Next> | NonNullable<Previous>>;
45
+ /** The `Previous` type parameter when the options have no `getPreviousPageParam`. */
46
+ declare const NO_PREVIOUS_PAGE_PARAM: unique symbol;
47
+ type NoPreviousPageParam = typeof NO_PREVIOUS_PAGE_PARAM;
48
+ type IsUntyped<T> = [T] extends [never] ? true : unknown extends T ? true : false;
49
+ /** Only `undefined` or `null` names no type: a cursor API's `initialPageParam: undefined`. */
50
+ type NullishToUnknown<T> = [T] extends [undefined | null] ? unknown : T;
51
+ /** Read only for the return type; `never` arguments add no inference for TanStack's page param. */
52
+ type PageParamFunction<Result> = (...args: never) => Result;
53
+ /**
54
+ * Inference sites of the page param types. Intersected before `Options`, so TanStack's own
55
+ * inference in the implementation reads the full signatures of `Options`.
56
+ */
57
+ interface PageParamSources<Initial, Next, Previous> {
58
+ initialPageParam: Initial;
59
+ getNextPageParam: PageParamFunction<Next>;
60
+ getPreviousPageParam?: PageParamFunction<Previous>;
61
+ }
62
+ /**
63
+ * `placeholderData` of `useInfiniteQuery`, with an `unknown` page param as before: TypeScript types
64
+ * a `(previousData) => previousData` callback while it still infers the page param, so it got
65
+ * `unknown` there and failed against the typed page param.
66
+ */
67
+ 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>;
18
70
  pageParamName?: string;
19
- }>(method: Method, url: Path, init: InitWithUnknowns<Init>, options: Options, queryClient?: QueryClient) => UseInfiniteQueryResult<InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>, Response["error"]>;
20
- type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseSuspenseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, Options?, QueryClient?] : [InitWithUnknowns<Init>, Options?, QueryClient?]) => UseSuspenseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
21
- type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<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>;
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>;
22
74
  interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
23
75
  queryOptions: QueryOptionsFunction<Paths, Media>;
24
76
  useQuery: UseQueryMethod<Paths, Media>;
@@ -29,7 +81,13 @@ interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaTy
29
81
  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 {
30
82
  [key: string]: any;
31
83
  }, infer Media extends MediaType> ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]> : never;
84
+ /**
85
+ * Hooks and query options for a fetch client. A `QueryClient` other than the one from the React
86
+ * context is passed to each hook as its last argument; `createClient` does not take one.
87
+ */
32
88
  declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>): OpenapiQueryClient<Paths, Media>;
33
89
 
90
+ declare namespace createClient {
91
+ export { InfiniteQueryKey, MethodResponse, OpenapiQueryClient, QueryKey, QueryOptionsFunction, UseInfiniteQueryMethod, UseMutationMethod, UseQueryMethod, UseSuspenseQueryMethod };
92
+ }
34
93
  export = createClient;
35
- export type { InfiniteQueryKey, MethodResponse, OpenapiQueryClient, QueryKey, QueryOptionsFunction, UseInfiniteQueryMethod, UseMutationMethod, UseQueryMethod, UseSuspenseQueryMethod };
package/dist/index.d.mts CHANGED
@@ -1,24 +1,76 @@
1
1
  import { UseQueryOptions, DataTag, SkipToken, QueryClient, UseQueryResult, UseSuspenseQueryOptions, UseSuspenseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
2
- import { MaybeOptionalInit, FetchResponse, Client } from 'openapi-typesafe-fetch';
2
+ import { MaybeOptionalInit, FetchResponse, PathOperation, Client } from 'openapi-typesafe-fetch';
3
3
  import { HttpMethod, PathsWithMethod, MediaType, RequiredKeysOf } from 'openapi-typesafe-helpers';
4
4
 
5
5
  type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
6
+ /**
7
+ * Query data of a response type: a success without a body is `undefined` in the fetch client and
8
+ * `null` in the query, as `queryFn` resolves it (upstream #2234, #2464).
9
+ */
10
+ type QueryData<Data> = undefined extends Data ? Exclude<Data, undefined> | null : Data;
6
11
  type InitWithUnknowns<Init> = Init & {
7
12
  [key: string]: unknown;
8
13
  };
9
- type QueryKey<Paths extends Record<string, Record<HttpMethod, {}>>, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init = MaybeOptionalInit<Paths[Path], Method>> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
14
+ /**
15
+ * The arguments after the path: the init, required when the operation requires a parameter or a
16
+ * body, then `Rest`. The condition is openapi-react-query's, as `InitParam` of the fetch client
17
+ * is openapi-fetch's (ADR-030, decision 2): TypeScript relates two conditional types only when
18
+ * their `extends` types are the same, so a hook type copied from openapi-react-query takes these
19
+ * hooks. For a union of inits, `MaybeOptionalInit` leaves out `undefined` when one is required,
20
+ * so `Parameters<typeof $api.queryOptions>` still requires it then (upstream #2178).
21
+ */
22
+ type InitArguments<Init, Rest extends unknown[]> = RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, ...Rest] : [InitWithUnknowns<Init>, ...Rest];
23
+ 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
+ /**
25
+ * The last element of an infinite query's key. `useQuery` and `useInfiniteQuery` cache different
26
+ * 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.
29
+ */
10
30
  declare const INFINITE_QUERY_KEY_MARKER = "infinite";
11
- type InfiniteQueryKey<Paths extends Record<string, Record<HttpMethod, {}>>, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>> = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];
12
- type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, path: Path, ...[init, options]: RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, Options?] : [InitWithUnknowns<Init>, Options?]) => NoInfer<Omit<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryFn" | "queryKey"> & {
13
- queryKey: DataTag<QueryKey<Paths, Method, Path>, Response["data"], Response["error"]>;
14
- queryFn: Exclude<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>["queryFn"], SkipToken | undefined>;
31
+ 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>;
15
35
  }>;
16
- type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, Options?, QueryClient?] : [InitWithUnknowns<Init>, Options?, QueryClient?]) => UseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
17
- type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseInfiniteQueryOptions<Response["data"], Response["error"], InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>, InfiniteQueryKey<Paths, Method, Path>, unknown>, "queryKey" | "queryFn"> & {
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"]>;
37
+ /**
38
+ * The type of the page params: the initial one and the ones `getNextPageParam` and
39
+ * `getPreviousPageParam` return (upstream #2458). It was `unknown`. It stays `unknown` when a
40
+ * return type is untyped, so code that compiled still compiles (roadmap §3): TypeScript cannot
41
+ * infer the return type of a callback that computes it from an unannotated `lastPageParam`,
42
+ * whose type depends on that return type, and gives `never` or `unknown`.
43
+ */
44
+ type PageParam<Initial, Next, Previous> = IsUntyped<Next> extends true ? unknown : [Previous] extends [NoPreviousPageParam] ? NullishToUnknown<Initial | NonNullable<Next>> : IsUntyped<Previous> extends true ? unknown : NullishToUnknown<Initial | NonNullable<Next> | NonNullable<Previous>>;
45
+ /** The `Previous` type parameter when the options have no `getPreviousPageParam`. */
46
+ declare const NO_PREVIOUS_PAGE_PARAM: unique symbol;
47
+ type NoPreviousPageParam = typeof NO_PREVIOUS_PAGE_PARAM;
48
+ type IsUntyped<T> = [T] extends [never] ? true : unknown extends T ? true : false;
49
+ /** Only `undefined` or `null` names no type: a cursor API's `initialPageParam: undefined`. */
50
+ type NullishToUnknown<T> = [T] extends [undefined | null] ? unknown : T;
51
+ /** Read only for the return type; `never` arguments add no inference for TanStack's page param. */
52
+ type PageParamFunction<Result> = (...args: never) => Result;
53
+ /**
54
+ * Inference sites of the page param types. Intersected before `Options`, so TanStack's own
55
+ * inference in the implementation reads the full signatures of `Options`.
56
+ */
57
+ interface PageParamSources<Initial, Next, Previous> {
58
+ initialPageParam: Initial;
59
+ getNextPageParam: PageParamFunction<Next>;
60
+ getPreviousPageParam?: PageParamFunction<Previous>;
61
+ }
62
+ /**
63
+ * `placeholderData` of `useInfiniteQuery`, with an `unknown` page param as before: TypeScript types
64
+ * a `(previousData) => previousData` callback while it still infers the page param, so it got
65
+ * `unknown` there and failed against the typed page param.
66
+ */
67
+ 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>;
18
70
  pageParamName?: string;
19
- }>(method: Method, url: Path, init: InitWithUnknowns<Init>, options: Options, queryClient?: QueryClient) => UseInfiniteQueryResult<InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>, Response["error"]>;
20
- type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseSuspenseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, Options?, QueryClient?] : [InitWithUnknowns<Init>, Options?, QueryClient?]) => UseSuspenseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
21
- type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<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>;
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>;
22
74
  interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
23
75
  queryOptions: QueryOptionsFunction<Paths, Media>;
24
76
  useQuery: UseQueryMethod<Paths, Media>;
@@ -29,6 +81,10 @@ interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaTy
29
81
  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 {
30
82
  [key: string]: any;
31
83
  }, infer Media extends MediaType> ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]> : never;
84
+ /**
85
+ * Hooks and query options for a fetch client. A `QueryClient` other than the one from the React
86
+ * context is passed to each hook as its last argument; `createClient` does not take one.
87
+ */
32
88
  declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>): OpenapiQueryClient<Paths, Media>;
33
89
 
34
90
  export { createClient as default };
package/dist/index.d.ts CHANGED
@@ -1,24 +1,76 @@
1
1
  import { UseQueryOptions, DataTag, SkipToken, QueryClient, UseQueryResult, UseSuspenseQueryOptions, UseSuspenseQueryResult, UseInfiniteQueryOptions, InfiniteData, UseInfiniteQueryResult, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
2
- import { MaybeOptionalInit, FetchResponse, Client } from 'openapi-typesafe-fetch';
2
+ import { MaybeOptionalInit, FetchResponse, PathOperation, Client } from 'openapi-typesafe-fetch';
3
3
  import { HttpMethod, PathsWithMethod, MediaType, RequiredKeysOf } from 'openapi-typesafe-helpers';
4
4
 
5
5
  type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
6
+ /**
7
+ * Query data of a response type: a success without a body is `undefined` in the fetch client and
8
+ * `null` in the query, as `queryFn` resolves it (upstream #2234, #2464).
9
+ */
10
+ type QueryData<Data> = undefined extends Data ? Exclude<Data, undefined> | null : Data;
6
11
  type InitWithUnknowns<Init> = Init & {
7
12
  [key: string]: unknown;
8
13
  };
9
- type QueryKey<Paths extends Record<string, Record<HttpMethod, {}>>, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init = MaybeOptionalInit<Paths[Path], Method>> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
14
+ /**
15
+ * The arguments after the path: the init, required when the operation requires a parameter or a
16
+ * body, then `Rest`. The condition is openapi-react-query's, as `InitParam` of the fetch client
17
+ * is openapi-fetch's (ADR-030, decision 2): TypeScript relates two conditional types only when
18
+ * their `extends` types are the same, so a hook type copied from openapi-react-query takes these
19
+ * hooks. For a union of inits, `MaybeOptionalInit` leaves out `undefined` when one is required,
20
+ * so `Parameters<typeof $api.queryOptions>` still requires it then (upstream #2178).
21
+ */
22
+ type InitArguments<Init, Rest extends unknown[]> = RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, ...Rest] : [InitWithUnknowns<Init>, ...Rest];
23
+ 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
+ /**
25
+ * The last element of an infinite query's key. `useQuery` and `useInfiniteQuery` cache different
26
+ * 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.
29
+ */
10
30
  declare const INFINITE_QUERY_KEY_MARKER = "infinite";
11
- type InfiniteQueryKey<Paths extends Record<string, Record<HttpMethod, {}>>, Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>> = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];
12
- type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, path: Path, ...[init, options]: RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, Options?] : [InitWithUnknowns<Init>, Options?]) => NoInfer<Omit<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryFn" | "queryKey"> & {
13
- queryKey: DataTag<QueryKey<Paths, Method, Path>, Response["data"], Response["error"]>;
14
- queryFn: Exclude<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>["queryFn"], SkipToken | undefined>;
31
+ 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>;
15
35
  }>;
16
- type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, Options?, QueryClient?] : [InitWithUnknowns<Init>, Options?, QueryClient?]) => UseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
17
- type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseInfiniteQueryOptions<Response["data"], Response["error"], InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>, InfiniteQueryKey<Paths, Method, Path>, unknown>, "queryKey" | "queryFn"> & {
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"]>;
37
+ /**
38
+ * The type of the page params: the initial one and the ones `getNextPageParam` and
39
+ * `getPreviousPageParam` return (upstream #2458). It was `unknown`. It stays `unknown` when a
40
+ * return type is untyped, so code that compiled still compiles (roadmap §3): TypeScript cannot
41
+ * infer the return type of a callback that computes it from an unannotated `lastPageParam`,
42
+ * whose type depends on that return type, and gives `never` or `unknown`.
43
+ */
44
+ type PageParam<Initial, Next, Previous> = IsUntyped<Next> extends true ? unknown : [Previous] extends [NoPreviousPageParam] ? NullishToUnknown<Initial | NonNullable<Next>> : IsUntyped<Previous> extends true ? unknown : NullishToUnknown<Initial | NonNullable<Next> | NonNullable<Previous>>;
45
+ /** The `Previous` type parameter when the options have no `getPreviousPageParam`. */
46
+ declare const NO_PREVIOUS_PAGE_PARAM: unique symbol;
47
+ type NoPreviousPageParam = typeof NO_PREVIOUS_PAGE_PARAM;
48
+ type IsUntyped<T> = [T] extends [never] ? true : unknown extends T ? true : false;
49
+ /** Only `undefined` or `null` names no type: a cursor API's `initialPageParam: undefined`. */
50
+ type NullishToUnknown<T> = [T] extends [undefined | null] ? unknown : T;
51
+ /** Read only for the return type; `never` arguments add no inference for TanStack's page param. */
52
+ type PageParamFunction<Result> = (...args: never) => Result;
53
+ /**
54
+ * Inference sites of the page param types. Intersected before `Options`, so TanStack's own
55
+ * inference in the implementation reads the full signatures of `Options`.
56
+ */
57
+ interface PageParamSources<Initial, Next, Previous> {
58
+ initialPageParam: Initial;
59
+ getNextPageParam: PageParamFunction<Next>;
60
+ getPreviousPageParam?: PageParamFunction<Previous>;
61
+ }
62
+ /**
63
+ * `placeholderData` of `useInfiniteQuery`, with an `unknown` page param as before: TypeScript types
64
+ * a `(previousData) => previousData` callback while it still infers the page param, so it got
65
+ * `unknown` there and failed against the typed page param.
66
+ */
67
+ 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>;
18
70
  pageParamName?: string;
19
- }>(method: Method, url: Path, init: InitWithUnknowns<Init>, options: Options, queryClient?: QueryClient) => UseInfiniteQueryResult<InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>, Response["error"]>;
20
- type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends Omit<UseSuspenseQueryOptions<Response["data"], Response["error"], InferSelectReturnType<Response["data"], Options["select"]>, QueryKey<Paths, Method, Path>>, "queryKey" | "queryFn">>(method: Method, url: Path, ...[init, options, queryClient]: RequiredKeysOf<Init> extends never ? [InitWithUnknowns<Init>?, Options?, QueryClient?] : [InitWithUnknowns<Init>, Options?, QueryClient?]) => UseSuspenseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
21
- type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<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>;
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>;
22
74
  interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
23
75
  queryOptions: QueryOptionsFunction<Paths, Media>;
24
76
  useQuery: UseQueryMethod<Paths, Media>;
@@ -29,7 +81,11 @@ interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaTy
29
81
  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 {
30
82
  [key: string]: any;
31
83
  }, infer Media extends MediaType> ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]> : never;
84
+ /**
85
+ * Hooks and query options for a fetch client. A `QueryClient` other than the one from the React
86
+ * context is passed to each hook as its last argument; `createClient` does not take one.
87
+ */
32
88
  declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>): OpenapiQueryClient<Paths, Media>;
33
89
 
34
- export = createClient;
90
+ export { createClient as default };
35
91
  export type { InfiniteQueryKey, MethodResponse, OpenapiQueryClient, QueryKey, QueryOptionsFunction, UseInfiniteQueryMethod, UseMutationMethod, UseQueryMethod, UseSuspenseQueryMethod };
package/dist/index.mjs CHANGED
@@ -14,18 +14,15 @@ function pageInit(init, { pageParamName, pageParam, signal }) {
14
14
  }
15
15
  function createClient(client) {
16
16
  const queryFn = async ({
17
- queryKey: [method, path, init],
17
+ queryKey,
18
18
  signal
19
19
  }) => {
20
+ const [method, path, init] = queryKey;
20
21
  const mth = method.toUpperCase();
21
22
  const fn = client[mth];
22
23
  const result = await fn(path, { signal, ...init });
23
24
  throwIfFailed(result);
24
- const { data, response } = result;
25
- if (response.status === 204 || response.headers.get("Content-Length") === "0") {
26
- return data ?? null;
27
- }
28
- return data;
25
+ return result.data ?? null;
29
26
  };
30
27
  const queryOptions = (method, path, ...[init, options]) => ({
31
28
  queryKey: init === void 0 ? [method, path] : [method, path, init],
@@ -34,10 +31,17 @@ function createClient(client) {
34
31
  });
35
32
  return {
36
33
  queryOptions,
37
- useQuery: (method, path, ...[init, options, queryClient]) => useQuery(queryOptions(method, path, init, options), queryClient),
38
- useSuspenseQuery: (method, path, ...[init, options, queryClient]) => useSuspenseQuery(queryOptions(method, path, init, options), queryClient),
34
+ useQuery: (method, path, ...[init, options, queryClient]) => useQuery(
35
+ queryOptions(method, path, init, options),
36
+ queryClient
37
+ ),
38
+ useSuspenseQuery: (method, path, ...[init, options, queryClient]) => useSuspenseQuery(
39
+ queryOptions(method, path, init, options),
40
+ queryClient
41
+ ),
42
+ // biome-ignore lint/complexity/useMaxParams: public signature of 1.x (ADR-036)
39
43
  useInfiniteQuery: (method, path, init, options, queryClient) => {
40
- const { pageParamName = DEFAULT_PAGE_PARAM_NAME, ...restOptions } = options;
44
+ const { pageParamName = DEFAULT_PAGE_PARAM_NAME, placeholderData, ...restOptions } = options;
41
45
  const { queryKey } = queryOptions(method, path, init);
42
46
  const mth = method.toUpperCase();
43
47
  const fn = client[mth];
@@ -46,11 +50,19 @@ function createClient(client) {
46
50
  queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER],
47
51
  // The request comes from the arguments; the key only identifies the cache entry.
48
52
  queryFn: async ({ pageParam, signal }) => {
49
- const result = await fn(path, pageInit(init, { pageParamName, 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
+ );
50
58
  throwIfFailed(result);
51
59
  return result.data;
52
60
  },
53
- ...restOptions
61
+ ...restOptions,
62
+ // Typed with an `unknown` page param for the caller (`InfinitePlaceholderData`). TanStack
63
+ // passes the previous data of this query, with its own page params, and takes it back.
64
+ // biome-ignore lint/suspicious/noExplicitAny: TanStack's type carries its own page params
65
+ placeholderData
54
66
  },
55
67
  queryClient
56
68
  );
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/index.ts"],"sourcesContent":["import {\n type DataTag,\n type InfiniteData,\n type QueryClient,\n type QueryFunctionContext,\n type SkipToken,\n type UseInfiniteQueryOptions,\n type UseInfiniteQueryResult,\n type UseMutationOptions,\n type UseMutationResult,\n type UseQueryOptions,\n type UseQueryResult,\n type UseSuspenseQueryOptions,\n type UseSuspenseQueryResult,\n useInfiniteQuery,\n useMutation,\n useQuery,\n useSuspenseQuery,\n} from \"@tanstack/react-query\";\nimport type {\n ClientMethod,\n DefaultParamsOption,\n Client as FetchClient,\n FetchResponse,\n MaybeOptionalInit,\n} from \"openapi-typesafe-fetch\";\nimport type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from \"openapi-typesafe-helpers\";\n\n// Helper type to dynamically infer the type from the `select` property\ntype InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;\n\ntype InitWithUnknowns<Init> = Init & { [key: string]: unknown };\n\nexport type QueryKey<\n Paths extends Record<string, Record<HttpMethod, {}>>,\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init = MaybeOptionalInit<Paths[Path], Method>,\n> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];\n\n/**\n * The last element of an infinite query's key. `useQuery` and `useInfiniteQuery` cache different\n * data (`TData` and `InfiniteData<TData>`), so the same request must not share one key (upstream\n * #2184, ADR-022). A prefix such as `[method, path]` still matches both. Not exported: a runtime\n * named export next to the default export would change what `require()` returns.\n */\nconst INFINITE_QUERY_KEY_MARKER = \"infinite\";\n\nexport type InfiniteQueryKey<\n Paths extends Record<string, Record<HttpMethod, {}>>,\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n> = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];\n\nexport type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types\n Options extends Omit<\n UseQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<Response[\"data\"], Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n path: Path,\n ...[init, options]: RequiredKeysOf<Init> extends never\n ? [InitWithUnknowns<Init>?, Options?]\n : [InitWithUnknowns<Init>, Options?]\n) => NoInfer<\n Omit<\n UseQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<Response[\"data\"], Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryFn\" | \"queryKey\"\n > & {\n queryKey: DataTag<QueryKey<Paths, Method, Path>, Response[\"data\"], Response[\"error\"]>;\n queryFn: Exclude<\n UseQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<Response[\"data\"], Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >[\"queryFn\"],\n SkipToken | undefined\n >;\n }\n>;\n\nexport type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types\n Options extends Omit<\n UseQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<Response[\"data\"], Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n url: Path,\n ...[init, options, queryClient]: RequiredKeysOf<Init> extends never\n ? [InitWithUnknowns<Init>?, Options?, QueryClient?]\n : [InitWithUnknowns<Init>, Options?, QueryClient?]\n) => UseQueryResult<InferSelectReturnType<Response[\"data\"], Options[\"select\"]>, Response[\"error\"]>;\n\nexport type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,\n Options extends Omit<\n UseInfiniteQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<InfiniteData<Response[\"data\"]>, Options[\"select\"]>,\n InfiniteQueryKey<Paths, Method, Path>,\n unknown\n >,\n \"queryKey\" | \"queryFn\"\n > & {\n pageParamName?: string;\n },\n>(\n method: Method,\n url: Path,\n init: InitWithUnknowns<Init>,\n options: Options,\n queryClient?: QueryClient,\n) => UseInfiniteQueryResult<\n InferSelectReturnType<InfiniteData<Response[\"data\"]>, Options[\"select\"]>,\n Response[\"error\"]\n>;\n\nexport type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types\n Options extends Omit<\n UseSuspenseQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<Response[\"data\"], Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n url: Path,\n ...[init, options, queryClient]: RequiredKeysOf<Init> extends never\n ? [InitWithUnknowns<Init>?, Options?, QueryClient?]\n : [InitWithUnknowns<Init>, Options?, QueryClient?]\n) => UseSuspenseQueryResult<InferSelectReturnType<Response[\"data\"], Options[\"select\"]>, Response[\"error\"]>;\n\nexport type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types\n TOnMutateResult = unknown,\n>(\n method: Method,\n url: Path,\n options?: Omit<\n UseMutationOptions<Response[\"data\"], Response[\"error\"], Init, TOnMutateResult>,\n \"mutationKey\" | \"mutationFn\"\n >,\n queryClient?: QueryClient,\n) => UseMutationResult<Response[\"data\"], Response[\"error\"], Init, TOnMutateResult>;\n\nexport interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {\n queryOptions: QueryOptionsFunction<Paths, Media>;\n useQuery: UseQueryMethod<Paths, Media>;\n useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;\n useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;\n useMutation: UseMutationMethod<Paths, Media>;\n}\n\nexport type MethodResponse<\n CreatedClient extends OpenapiQueryClient<any, any>,\n Method extends HttpMethod,\n Path extends CreatedClient extends OpenapiQueryClient<infer Paths, infer _Media>\n ? PathsWithMethod<Paths, Method>\n : never,\n Options = object,\n> =\n CreatedClient extends OpenapiQueryClient<infer Paths extends { [key: string]: any }, infer Media extends MediaType>\n ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>[\"data\"]>\n : never;\n\n/**\n * Throw the error of a failed fetch result. `if (error)` missed two failures: a non-OK response with\n * an empty body (`error` is `undefined`, upstream #2070, #2829) and a falsy error body such as `0`.\n */\nfunction throwIfFailed(result: { error?: unknown; response: Response }): void {\n if (result.error !== undefined || !result.response.ok) {\n throw result.error;\n }\n}\n\n/** The query parameter that carries the page param unless `pageParamName` names another. */\nconst DEFAULT_PAGE_PARAM_NAME = \"cursor\";\n\ninterface PageRequest {\n readonly pageParamName: string;\n readonly pageParam: unknown;\n readonly signal: AbortSignal;\n}\n\n/**\n * The request init of one page. An `undefined` page param (the first page with\n * `initialPageParam: undefined`) is left out instead of being sent as `0` (upstream #2715, #2340,\n * ADR-022), so a cursor API gets no cursor on the first page.\n */\nfunction pageInit(init: unknown, { pageParamName, pageParam, signal }: PageRequest) {\n const { params, ...rest } = (init ?? {}) as { params?: { query?: DefaultParamsOption } };\n const page = pageParam === undefined ? {} : { [pageParamName]: pageParam };\n return { ...rest, signal, params: { ...params, query: { ...params?.query, ...page } } };\n}\n\n// TODO: Add the ability to bring queryClient as argument\nexport default function createClient<Paths extends {}, Media extends MediaType = MediaType>(\n client: FetchClient<Paths, Media>,\n): OpenapiQueryClient<Paths, Media> {\n const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({\n queryKey: [method, path, init],\n signal,\n }: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n const result = await fn(path, { signal, ...(init as any) }); // TODO: find a way to avoid as any\n throwIfFailed(result);\n const { data, response } = result;\n if (response.status === 204 || response.headers.get(\"Content-Length\") === \"0\") {\n return data ?? null;\n }\n\n return data;\n };\n\n const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({\n queryKey: (init === undefined ? ([method, path] as const) : ([method, path, init] as const)) as DataTag<\n QueryKey<Paths, typeof method, typeof path>,\n any,\n any\n >,\n queryFn,\n ...options,\n });\n\n return {\n queryOptions,\n useQuery: (method, path, ...[init, options, queryClient]) =>\n useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),\n useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>\n useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),\n useInfiniteQuery: (method, path, init, options, queryClient) => {\n const { pageParamName = DEFAULT_PAGE_PARAM_NAME, ...restOptions } = options;\n const { queryKey } = queryOptions(method, path, init);\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n return useInfiniteQuery(\n {\n queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER] as const,\n // The request comes from the arguments; the key only identifies the cache entry.\n queryFn: async ({ pageParam, signal }) => {\n const result = await fn(path, pageInit(init, { pageParamName, pageParam, signal }) as any);\n throwIfFailed(result);\n return result.data;\n },\n ...restOptions,\n },\n queryClient,\n );\n },\n useMutation: (method, path, options, queryClient) =>\n useMutation(\n {\n mutationKey: [method, path],\n mutationFn: async (init) => {\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n const result = await fn(path, init as InitWithUnknowns<typeof init>);\n throwIfFailed(result);\n const { data } = result;\n\n return data as Exclude<typeof data, undefined>;\n },\n ...options,\n },\n queryClient,\n ),\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/index.ts"],"sourcesContent":["import {\n type DataTag,\n type InfiniteData,\n type QueryClient,\n type QueryFunctionContext,\n type SkipToken,\n type UseInfiniteQueryOptions,\n type UseInfiniteQueryResult,\n type UseMutationOptions,\n type UseMutationResult,\n type UseQueryOptions,\n type UseQueryResult,\n type UseSuspenseQueryOptions,\n type UseSuspenseQueryResult,\n useInfiniteQuery,\n useMutation,\n useQuery,\n useSuspenseQuery,\n} from \"@tanstack/react-query\";\nimport type {\n ClientMethod,\n DefaultParamsOption,\n Client as FetchClient,\n FetchResponse,\n MaybeOptionalInit,\n PathOperation,\n} from \"openapi-typesafe-fetch\";\nimport type {\n HttpMethod,\n MediaType,\n PathsWithMethod,\n RequiredKeysOf,\n} from \"openapi-typesafe-helpers\";\n\n// Helper type to dynamically infer the type from the `select` property\ntype InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;\n\n/**\n * Query data of a response type: a success without a body is `undefined` in the fetch client and\n * `null` in the query, as `queryFn` resolves it (upstream #2234, #2464).\n */\ntype QueryData<Data> = undefined extends Data ? Exclude<Data, undefined> | null : Data;\n\ntype InitWithUnknowns<Init> = Init & { [key: string]: unknown };\n\n/**\n * The arguments after the path: the init, required when the operation requires a parameter or a\n * body, then `Rest`. The condition is openapi-react-query's, as `InitParam` of the fetch client\n * is openapi-fetch's (ADR-030, decision 2): TypeScript relates two conditional types only when\n * their `extends` types are the same, so a hook type copied from openapi-react-query takes these\n * hooks. For a union of inits, `MaybeOptionalInit` leaves out `undefined` when one is required,\n * so `Parameters<typeof $api.queryOptions>` still requires it then (upstream #2178).\n */\ntype InitArguments<Init, Rest extends unknown[]> =\n RequiredKeysOf<Init> extends never\n ? [InitWithUnknowns<Init>?, ...Rest]\n : [InitWithUnknowns<Init>, ...Rest];\n\nexport type QueryKey<\n Paths extends {},\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init = MaybeOptionalInit<Paths[Path], Method>,\n> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];\n\n/**\n * The last element of an infinite query's key. `useQuery` and `useInfiniteQuery` cache different\n * data (`TData` and `InfiniteData<TData>`), so the same request must not share one key (upstream\n * #2184, ADR-022). A prefix such as `[method, path]` still matches both. Not exported: a runtime\n * named export next to the default export would change what `require()` returns.\n */\nconst INFINITE_QUERY_KEY_MARKER = \"infinite\";\n\nexport type InfiniteQueryKey<\n Paths extends {},\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n> = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];\n\nexport type QueryOptionsFunction<Paths extends {}, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n // note: Required is used to avoid repeating NonNullable in UseQuery types\n Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,\n Options extends Omit<\n UseQueryOptions<\n QueryData<Response[\"data\"]>,\n Response[\"error\"],\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n path: Path,\n ...[init, options]: InitArguments<Init, [Options?]>\n) => NoInfer<\n Omit<\n UseQueryOptions<\n QueryData<Response[\"data\"]>,\n Response[\"error\"],\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryFn\" | \"queryKey\"\n > & {\n queryKey: DataTag<\n QueryKey<Paths, Method, Path>,\n QueryData<Response[\"data\"]>,\n Response[\"error\"]\n >;\n queryFn: Exclude<\n UseQueryOptions<\n QueryData<Response[\"data\"]>,\n Response[\"error\"],\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >[\"queryFn\"],\n SkipToken | undefined\n >;\n }\n>;\n\nexport type UseQueryMethod<Paths extends {}, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n // note: Required is used to avoid repeating NonNullable in UseQuery types\n Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,\n Options extends Omit<\n UseQueryOptions<\n QueryData<Response[\"data\"]>,\n Response[\"error\"],\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n url: Path,\n ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>\n) => UseQueryResult<\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n Response[\"error\"]\n>;\n\n/**\n * The type of the page params: the initial one and the ones `getNextPageParam` and\n * `getPreviousPageParam` return (upstream #2458). It was `unknown`. It stays `unknown` when a\n * return type is untyped, so code that compiled still compiles (roadmap §3): TypeScript cannot\n * infer the return type of a callback that computes it from an unannotated `lastPageParam`,\n * whose type depends on that return type, and gives `never` or `unknown`.\n */\ntype PageParam<Initial, Next, Previous> =\n IsUntyped<Next> extends true\n ? unknown\n : [Previous] extends [NoPreviousPageParam]\n ? NullishToUnknown<Initial | NonNullable<Next>>\n : IsUntyped<Previous> extends true\n ? unknown\n : NullishToUnknown<Initial | NonNullable<Next> | NonNullable<Previous>>;\n\n/** The `Previous` type parameter when the options have no `getPreviousPageParam`. */\ndeclare const NO_PREVIOUS_PAGE_PARAM: unique symbol;\ntype NoPreviousPageParam = typeof NO_PREVIOUS_PAGE_PARAM;\n\ntype IsUntyped<T> = [T] extends [never] ? true : unknown extends T ? true : false;\n\n/** Only `undefined` or `null` names no type: a cursor API's `initialPageParam: undefined`. */\ntype NullishToUnknown<T> = [T] extends [undefined | null] ? unknown : T;\n\n/** Read only for the return type; `never` arguments add no inference for TanStack's page param. */\ntype PageParamFunction<Result> = (...args: never) => Result;\n\n/**\n * Inference sites of the page param types. Intersected before `Options`, so TanStack's own\n * inference in the implementation reads the full signatures of `Options`.\n */\ninterface PageParamSources<Initial, Next, Previous> {\n initialPageParam: Initial;\n getNextPageParam: PageParamFunction<Next>;\n getPreviousPageParam?: PageParamFunction<Previous>;\n}\n\n/**\n * `placeholderData` of `useInfiniteQuery`, with an `unknown` page param as before: TypeScript types\n * a `(previousData) => previousData` callback while it still infers the page param, so it got\n * `unknown` there and failed against the typed page param.\n */\ntype InfinitePlaceholderData<\n Data,\n Error,\n Paths extends {},\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n> = UseInfiniteQueryOptions<\n Data,\n Error,\n InfiniteData<Data, unknown>,\n InfiniteQueryKey<Paths, Method, Path>,\n unknown\n>[\"placeholderData\"];\n\nexport type UseInfiniteQueryMethod<Paths extends {}, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,\n Options extends Omit<\n UseInfiniteQueryOptions<\n Response[\"data\"],\n Response[\"error\"],\n InferSelectReturnType<\n InfiniteData<Response[\"data\"], PageParam<Initial, Next, Previous>>,\n Options[\"select\"]\n >,\n InfiniteQueryKey<Paths, Method, Path>,\n PageParam<Initial, Next, Previous>\n >,\n \"queryKey\" | \"queryFn\" | \"placeholderData\"\n > & {\n placeholderData?: InfinitePlaceholderData<\n Response[\"data\"],\n Response[\"error\"],\n Paths,\n Method,\n Path\n >;\n pageParamName?: string;\n },\n Initial = unknown,\n Next = never,\n Previous = NoPreviousPageParam,\n // biome-ignore lint/complexity/useMaxParams: public signature of 1.x (ADR-036)\n>(\n method: Method,\n url: Path,\n init: InitWithUnknowns<Init>,\n options: PageParamSources<Initial, Next, Previous> & Options,\n queryClient?: QueryClient,\n) => UseInfiniteQueryResult<\n InferSelectReturnType<\n InfiniteData<Response[\"data\"], PageParam<Initial, Next, Previous>>,\n Options[\"select\"]\n >,\n Response[\"error\"]\n>;\n\nexport type UseSuspenseQueryMethod<Paths extends {}, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n // note: Required is used to avoid repeating NonNullable in UseQuery types\n Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,\n Options extends Omit<\n UseSuspenseQueryOptions<\n QueryData<Response[\"data\"]>,\n Response[\"error\"],\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n QueryKey<Paths, Method, Path>\n >,\n \"queryKey\" | \"queryFn\"\n >,\n>(\n method: Method,\n url: Path,\n ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>\n) => UseSuspenseQueryResult<\n InferSelectReturnType<QueryData<Response[\"data\"]>, Options[\"select\"]>,\n Response[\"error\"]\n>;\n\nexport type UseMutationMethod<Paths extends {}, Media extends MediaType> = <\n Method extends HttpMethod,\n Path extends PathsWithMethod<Paths, Method>,\n Init extends MaybeOptionalInit<Paths[Path], Method>,\n // note: Required is used to avoid repeating NonNullable in UseQuery types\n Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,\n TOnMutateResult = unknown,\n>(\n method: Method,\n url: Path,\n options?: Omit<\n UseMutationOptions<Response[\"data\"], Response[\"error\"], Init, TOnMutateResult>,\n \"mutationKey\" | \"mutationFn\"\n >,\n queryClient?: QueryClient,\n) => UseMutationResult<Response[\"data\"], Response[\"error\"], Init, TOnMutateResult>;\n\nexport interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {\n queryOptions: QueryOptionsFunction<Paths, Media>;\n useQuery: UseQueryMethod<Paths, Media>;\n useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;\n useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;\n useMutation: UseMutationMethod<Paths, Media>;\n}\n\nexport type MethodResponse<\n // biome-ignore lint/suspicious/noExplicitAny: matches a client of any paths, as upstream's\n CreatedClient extends OpenapiQueryClient<any, any>,\n Method extends HttpMethod,\n Path extends CreatedClient extends OpenapiQueryClient<infer Paths, infer _Media>\n ? PathsWithMethod<Paths, Method>\n : never,\n Options = object,\n> =\n CreatedClient extends OpenapiQueryClient<\n // biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces\n infer Paths extends { [key: string]: any },\n infer Media extends MediaType\n >\n ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>[\"data\"]>\n : never;\n\n/**\n * Throw the error of a failed fetch result. `if (error)` missed two failures: a non-OK response\n * with an empty body (`error` is `undefined`, upstream #2070, #2829) and a falsy error body such as\n * `0`.\n */\nfunction throwIfFailed(result: { error?: unknown; response: Response }): void {\n if (result.error !== undefined || !result.response.ok) {\n throw result.error;\n }\n}\n\n/** The query parameter that carries the page param unless `pageParamName` names another. */\nconst DEFAULT_PAGE_PARAM_NAME = \"cursor\";\n\ninterface PageRequest {\n readonly pageParamName: string;\n readonly pageParam: unknown;\n readonly signal: AbortSignal;\n}\n\n/**\n * The request init of one page. An `undefined` page param (the first page with\n * `initialPageParam: undefined`) is left out instead of being sent as `0` (upstream #2715, #2340,\n * ADR-022), so a cursor API gets no cursor on the first page.\n */\nfunction pageInit(init: unknown, { pageParamName, pageParam, signal }: PageRequest) {\n const { params, ...rest } = (init ?? {}) as { params?: { query?: DefaultParamsOption } };\n const page = pageParam === undefined ? {} : { [pageParamName]: pageParam };\n return { ...rest, signal, params: { ...params, query: { ...params?.query, ...page } } };\n}\n\n/**\n * Hooks and query options for a fetch client. A `QueryClient` other than the one from the React\n * context is passed to each hook as its last argument; `createClient` does not take one.\n */\n// biome-ignore lint/complexity/noExcessiveLinesPerFunction: inherited, kept until 1.1 (ADR-036)\nexport default function createClient<Paths extends {}, Media extends MediaType = MediaType>(\n client: FetchClient<Paths, Media>,\n): OpenapiQueryClient<Paths, Media> {\n const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({\n queryKey,\n signal,\n }: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {\n // While `Path` is generic, TypeScript cannot work out that the key is a tuple: the init is a\n // conditional type per operation (`MaybeOptionalInit`), and the key's type waits for it.\n const [method, path, init] = queryKey as readonly [Method, Path, unknown?];\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n // The init is `unknown` here. An `any` init lets the call infer the generic response type\n // that `queryOptions` declares; a typed init makes TypeScript compare two unresolved\n // conditional types, which it cannot do.\n // biome-ignore lint/suspicious/noExplicitAny: see the comment above\n const result = await fn(path, { signal, ...(init as any) });\n throwIfFailed(result);\n // TanStack Query rejects `undefined` query data. The fetch client gives `undefined` for every\n // success without a body: a 204, `Content-Length: 0`, an empty chunked body, a HEAD request\n // (upstream #2234). A parsed body is never `undefined`, so this changes nothing else.\n return result.data ?? null;\n };\n\n const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({\n queryKey: (init === undefined\n ? ([method, path] as const)\n : ([method, path, init] as const)) as DataTag<\n QueryKey<Paths, typeof method, typeof path>,\n // biome-ignore lint/suspicious/noExplicitAny: the declared return type sets the data type\n any,\n // biome-ignore lint/suspicious/noExplicitAny: the declared return type sets the error type\n any\n >,\n queryFn,\n ...options,\n });\n\n return {\n queryOptions,\n useQuery: (method, path, ...[init, options, queryClient]) =>\n useQuery(\n queryOptions(method, path, init as InitWithUnknowns<typeof init>, options),\n queryClient,\n ),\n useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>\n useSuspenseQuery(\n queryOptions(method, path, init as InitWithUnknowns<typeof init>, options),\n queryClient,\n ),\n // biome-ignore lint/complexity/useMaxParams: public signature of 1.x (ADR-036)\n useInfiniteQuery: (method, path, init, options, queryClient) => {\n const { pageParamName = DEFAULT_PAGE_PARAM_NAME, placeholderData, ...restOptions } = options;\n const { queryKey } = queryOptions(method, path, init);\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n return useInfiniteQuery(\n {\n queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER] as const,\n // The request comes from the arguments; the key only identifies the cache entry.\n queryFn: async ({ pageParam, signal }) => {\n const result = await fn(\n path,\n // biome-ignore lint/suspicious/noExplicitAny: a generic init; see queryFn\n pageInit(init, { pageParamName, pageParam, signal }) as any,\n );\n throwIfFailed(result);\n // An empty page stays `undefined`, as in upstream: TanStack Query rejects only\n // `undefined` query data, and the infinite query data is the `{ pages }` object.\n return result.data;\n },\n ...restOptions,\n // Typed with an `unknown` page param for the caller (`InfinitePlaceholderData`). TanStack\n // passes the previous data of this query, with its own page params, and takes it back.\n // biome-ignore lint/suspicious/noExplicitAny: TanStack's type carries its own page params\n placeholderData: placeholderData as any,\n },\n queryClient,\n );\n },\n useMutation: (method, path, options, queryClient) =>\n useMutation(\n {\n mutationKey: [method, path],\n mutationFn: async (init) => {\n const mth = method.toUpperCase() as Uppercase<typeof method>;\n const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;\n const result = await fn(path, init as InitWithUnknowns<typeof init>);\n throwIfFailed(result);\n // TanStack Query accepts `undefined` mutation data, so a success without a body stays\n // `undefined`, as in upstream; only query data needs `null` (upstream #2234). The cast\n // only bridges the generic types: `Response[\"data\"]` still has `undefined` for an\n // operation with a response without a body.\n const { data } = result;\n return data as Exclude<typeof data, undefined>;\n },\n ...options,\n },\n queryClient,\n ),\n };\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "openapi-typesafe-react-query",
3
3
  "description": "Fast, type-safe @tanstack/react-query client to work with your OpenAPI schema.",
4
- "version": "1.0.0-rc.0",
4
+ "version": "1.0.0-rc.2",
5
5
  "author": {
6
6
  "name": "Fedor Chishchin",
7
7
  "url": "https://github.com/fedorello"
@@ -50,26 +50,26 @@
50
50
  "tanstack"
51
51
  ],
52
52
  "dependencies": {
53
- "openapi-typesafe-helpers": "^1.0.0-rc.0"
53
+ "openapi-typesafe-helpers": "^1.0.0-rc.2"
54
54
  },
55
55
  "devDependencies": {
56
- "@tanstack/react-query": "5.105.1",
56
+ "@tanstack/react-query": "5.106.0",
57
57
  "@testing-library/react": "16.3.3",
58
58
  "@types/react": "19.3.0",
59
59
  "@types/react-dom": "19.3.0",
60
- "@vitejs/plugin-react": "6.1.2",
61
- "execa": "^10.2.1",
60
+ "@vitejs/plugin-react": "6.2.1",
62
61
  "jsdom": "30.1.3",
63
62
  "msw": "3.0.3",
64
- "openapi-typesafe": "^1.0.0-rc.0",
65
- "openapi-typesafe-fetch": "^1.0.0-rc.0",
63
+ "openapi-typesafe": "^1.0.0-rc.2",
64
+ "openapi-typesafe-fetch": "^1.0.0-rc.2",
66
65
  "react": "19.3.1",
67
66
  "react-dom": "19.3.1",
68
- "react-error-boundary": "6.1.6"
67
+ "react-error-boundary": "6.1.6",
68
+ "vite-node": "6.0.0"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@tanstack/react-query": "^5.80.0",
72
- "openapi-typesafe-fetch": "^1.0.0-rc.0"
72
+ "openapi-typesafe-fetch": "^1.0.0-rc.2"
73
73
  },
74
74
  "engines": {
75
75
  "node": ">=22"
@@ -78,9 +78,10 @@
78
78
  "build": "unbuild",
79
79
  "dev": "tsc -p tsconfig.build.json --watch",
80
80
  "format": "biome format . --write",
81
- "lint": "pnpm run lint:js && pnpm run lint:ts",
81
+ "lint": "pnpm run lint:js && pnpm run lint:ts && pnpm run lint:sizes",
82
82
  "lint:js": "biome check .",
83
83
  "lint:ts": "tsc --noEmit",
84
+ "lint:sizes": "vite-node ../openapi-typesafe/scripts/size-rules/check-sizes.ts src",
84
85
  "generate-types": "openapi-typesafe test/fixtures/api.yaml -o test/fixtures/api.d.ts && openapi-typesafe test/fixtures/query.yaml -o test/fixtures/query.d.ts",
85
86
  "test": "pnpm run generate-types && pnpm run test:js && pnpm run test:exports",
86
87
  "test:js": "vitest run",
package/src/index.ts CHANGED
@@ -23,16 +23,41 @@ import type {
23
23
  Client as FetchClient,
24
24
  FetchResponse,
25
25
  MaybeOptionalInit,
26
+ PathOperation,
26
27
  } from "openapi-typesafe-fetch";
27
- import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typesafe-helpers";
28
+ import type {
29
+ HttpMethod,
30
+ MediaType,
31
+ PathsWithMethod,
32
+ RequiredKeysOf,
33
+ } from "openapi-typesafe-helpers";
28
34
 
29
35
  // Helper type to dynamically infer the type from the `select` property
30
36
  type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
31
37
 
38
+ /**
39
+ * Query data of a response type: a success without a body is `undefined` in the fetch client and
40
+ * `null` in the query, as `queryFn` resolves it (upstream #2234, #2464).
41
+ */
42
+ type QueryData<Data> = undefined extends Data ? Exclude<Data, undefined> | null : Data;
43
+
32
44
  type InitWithUnknowns<Init> = Init & { [key: string]: unknown };
33
45
 
46
+ /**
47
+ * The arguments after the path: the init, required when the operation requires a parameter or a
48
+ * body, then `Rest`. The condition is openapi-react-query's, as `InitParam` of the fetch client
49
+ * is openapi-fetch's (ADR-030, decision 2): TypeScript relates two conditional types only when
50
+ * their `extends` types are the same, so a hook type copied from openapi-react-query takes these
51
+ * hooks. For a union of inits, `MaybeOptionalInit` leaves out `undefined` when one is required,
52
+ * so `Parameters<typeof $api.queryOptions>` still requires it then (upstream #2178).
53
+ */
54
+ type InitArguments<Init, Rest extends unknown[]> =
55
+ RequiredKeysOf<Init> extends never
56
+ ? [InitWithUnknowns<Init>?, ...Rest]
57
+ : [InitWithUnknowns<Init>, ...Rest];
58
+
34
59
  export type QueryKey<
35
- Paths extends Record<string, Record<HttpMethod, {}>>,
60
+ Paths extends {},
36
61
  Method extends HttpMethod,
37
62
  Path extends PathsWithMethod<Paths, Method>,
38
63
  Init = MaybeOptionalInit<Paths[Path], Method>,
@@ -47,21 +72,22 @@ export type QueryKey<
47
72
  const INFINITE_QUERY_KEY_MARKER = "infinite";
48
73
 
49
74
  export type InfiniteQueryKey<
50
- Paths extends Record<string, Record<HttpMethod, {}>>,
75
+ Paths extends {},
51
76
  Method extends HttpMethod,
52
77
  Path extends PathsWithMethod<Paths, Method>,
53
78
  > = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];
54
79
 
55
- export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
80
+ export type QueryOptionsFunction<Paths extends {}, Media extends MediaType> = <
56
81
  Method extends HttpMethod,
57
82
  Path extends PathsWithMethod<Paths, Method>,
58
83
  Init extends MaybeOptionalInit<Paths[Path], Method>,
59
- Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
84
+ // note: Required is used to avoid repeating NonNullable in UseQuery types
85
+ Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,
60
86
  Options extends Omit<
61
87
  UseQueryOptions<
62
- Response["data"],
88
+ QueryData<Response["data"]>,
63
89
  Response["error"],
64
- InferSelectReturnType<Response["data"], Options["select"]>,
90
+ InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>,
65
91
  QueryKey<Paths, Method, Path>
66
92
  >,
67
93
  "queryKey" | "queryFn"
@@ -69,25 +95,27 @@ export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod,
69
95
  >(
70
96
  method: Method,
71
97
  path: Path,
72
- ...[init, options]: RequiredKeysOf<Init> extends never
73
- ? [InitWithUnknowns<Init>?, Options?]
74
- : [InitWithUnknowns<Init>, Options?]
98
+ ...[init, options]: InitArguments<Init, [Options?]>
75
99
  ) => NoInfer<
76
100
  Omit<
77
101
  UseQueryOptions<
78
- Response["data"],
102
+ QueryData<Response["data"]>,
79
103
  Response["error"],
80
- InferSelectReturnType<Response["data"], Options["select"]>,
104
+ InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>,
81
105
  QueryKey<Paths, Method, Path>
82
106
  >,
83
107
  "queryFn" | "queryKey"
84
108
  > & {
85
- queryKey: DataTag<QueryKey<Paths, Method, Path>, Response["data"], Response["error"]>;
109
+ queryKey: DataTag<
110
+ QueryKey<Paths, Method, Path>,
111
+ QueryData<Response["data"]>,
112
+ Response["error"]
113
+ >;
86
114
  queryFn: Exclude<
87
115
  UseQueryOptions<
88
- Response["data"],
116
+ QueryData<Response["data"]>,
89
117
  Response["error"],
90
- InferSelectReturnType<Response["data"], Options["select"]>,
118
+ InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>,
91
119
  QueryKey<Paths, Method, Path>
92
120
  >["queryFn"],
93
121
  SkipToken | undefined
@@ -95,16 +123,17 @@ export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod,
95
123
  }
96
124
  >;
97
125
 
98
- export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
126
+ export type UseQueryMethod<Paths extends {}, Media extends MediaType> = <
99
127
  Method extends HttpMethod,
100
128
  Path extends PathsWithMethod<Paths, Method>,
101
129
  Init extends MaybeOptionalInit<Paths[Path], Method>,
102
- Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
130
+ // note: Required is used to avoid repeating NonNullable in UseQuery types
131
+ Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,
103
132
  Options extends Omit<
104
133
  UseQueryOptions<
105
- Response["data"],
134
+ QueryData<Response["data"]>,
106
135
  Response["error"],
107
- InferSelectReturnType<Response["data"], Options["select"]>,
136
+ InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>,
108
137
  QueryKey<Paths, Method, Path>
109
138
  >,
110
139
  "queryKey" | "queryFn"
@@ -112,49 +141,125 @@ export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>,
112
141
  >(
113
142
  method: Method,
114
143
  url: Path,
115
- ...[init, options, queryClient]: RequiredKeysOf<Init> extends never
116
- ? [InitWithUnknowns<Init>?, Options?, QueryClient?]
117
- : [InitWithUnknowns<Init>, Options?, QueryClient?]
118
- ) => UseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
144
+ ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>
145
+ ) => UseQueryResult<
146
+ InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>,
147
+ Response["error"]
148
+ >;
149
+
150
+ /**
151
+ * The type of the page params: the initial one and the ones `getNextPageParam` and
152
+ * `getPreviousPageParam` return (upstream #2458). It was `unknown`. It stays `unknown` when a
153
+ * return type is untyped, so code that compiled still compiles (roadmap §3): TypeScript cannot
154
+ * infer the return type of a callback that computes it from an unannotated `lastPageParam`,
155
+ * whose type depends on that return type, and gives `never` or `unknown`.
156
+ */
157
+ type PageParam<Initial, Next, Previous> =
158
+ IsUntyped<Next> extends true
159
+ ? unknown
160
+ : [Previous] extends [NoPreviousPageParam]
161
+ ? NullishToUnknown<Initial | NonNullable<Next>>
162
+ : IsUntyped<Previous> extends true
163
+ ? unknown
164
+ : NullishToUnknown<Initial | NonNullable<Next> | NonNullable<Previous>>;
165
+
166
+ /** The `Previous` type parameter when the options have no `getPreviousPageParam`. */
167
+ declare const NO_PREVIOUS_PAGE_PARAM: unique symbol;
168
+ type NoPreviousPageParam = typeof NO_PREVIOUS_PAGE_PARAM;
169
+
170
+ type IsUntyped<T> = [T] extends [never] ? true : unknown extends T ? true : false;
119
171
 
120
- export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
172
+ /** Only `undefined` or `null` names no type: a cursor API's `initialPageParam: undefined`. */
173
+ type NullishToUnknown<T> = [T] extends [undefined | null] ? unknown : T;
174
+
175
+ /** Read only for the return type; `never` arguments add no inference for TanStack's page param. */
176
+ type PageParamFunction<Result> = (...args: never) => Result;
177
+
178
+ /**
179
+ * Inference sites of the page param types. Intersected before `Options`, so TanStack's own
180
+ * inference in the implementation reads the full signatures of `Options`.
181
+ */
182
+ interface PageParamSources<Initial, Next, Previous> {
183
+ initialPageParam: Initial;
184
+ getNextPageParam: PageParamFunction<Next>;
185
+ getPreviousPageParam?: PageParamFunction<Previous>;
186
+ }
187
+
188
+ /**
189
+ * `placeholderData` of `useInfiniteQuery`, with an `unknown` page param as before: TypeScript types
190
+ * a `(previousData) => previousData` callback while it still infers the page param, so it got
191
+ * `unknown` there and failed against the typed page param.
192
+ */
193
+ type InfinitePlaceholderData<
194
+ Data,
195
+ Error,
196
+ Paths extends {},
197
+ Method extends HttpMethod,
198
+ Path extends PathsWithMethod<Paths, Method>,
199
+ > = UseInfiniteQueryOptions<
200
+ Data,
201
+ Error,
202
+ InfiniteData<Data, unknown>,
203
+ InfiniteQueryKey<Paths, Method, Path>,
204
+ unknown
205
+ >["placeholderData"];
206
+
207
+ export type UseInfiniteQueryMethod<Paths extends {}, Media extends MediaType> = <
121
208
  Method extends HttpMethod,
122
209
  Path extends PathsWithMethod<Paths, Method>,
123
210
  Init extends MaybeOptionalInit<Paths[Path], Method>,
124
- Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
211
+ Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,
125
212
  Options extends Omit<
126
213
  UseInfiniteQueryOptions<
127
214
  Response["data"],
128
215
  Response["error"],
129
- InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
216
+ InferSelectReturnType<
217
+ InfiniteData<Response["data"], PageParam<Initial, Next, Previous>>,
218
+ Options["select"]
219
+ >,
130
220
  InfiniteQueryKey<Paths, Method, Path>,
131
- unknown
221
+ PageParam<Initial, Next, Previous>
132
222
  >,
133
- "queryKey" | "queryFn"
223
+ "queryKey" | "queryFn" | "placeholderData"
134
224
  > & {
225
+ placeholderData?: InfinitePlaceholderData<
226
+ Response["data"],
227
+ Response["error"],
228
+ Paths,
229
+ Method,
230
+ Path
231
+ >;
135
232
  pageParamName?: string;
136
233
  },
234
+ Initial = unknown,
235
+ Next = never,
236
+ Previous = NoPreviousPageParam,
237
+ // biome-ignore lint/complexity/useMaxParams: public signature of 1.x (ADR-036)
137
238
  >(
138
239
  method: Method,
139
240
  url: Path,
140
241
  init: InitWithUnknowns<Init>,
141
- options: Options,
242
+ options: PageParamSources<Initial, Next, Previous> & Options,
142
243
  queryClient?: QueryClient,
143
244
  ) => UseInfiniteQueryResult<
144
- InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
245
+ InferSelectReturnType<
246
+ InfiniteData<Response["data"], PageParam<Initial, Next, Previous>>,
247
+ Options["select"]
248
+ >,
145
249
  Response["error"]
146
250
  >;
147
251
 
148
- export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
252
+ export type UseSuspenseQueryMethod<Paths extends {}, Media extends MediaType> = <
149
253
  Method extends HttpMethod,
150
254
  Path extends PathsWithMethod<Paths, Method>,
151
255
  Init extends MaybeOptionalInit<Paths[Path], Method>,
152
- Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
256
+ // note: Required is used to avoid repeating NonNullable in UseQuery types
257
+ Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,
153
258
  Options extends Omit<
154
259
  UseSuspenseQueryOptions<
155
- Response["data"],
260
+ QueryData<Response["data"]>,
156
261
  Response["error"],
157
- InferSelectReturnType<Response["data"], Options["select"]>,
262
+ InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>,
158
263
  QueryKey<Paths, Method, Path>
159
264
  >,
160
265
  "queryKey" | "queryFn"
@@ -162,16 +267,18 @@ export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMetho
162
267
  >(
163
268
  method: Method,
164
269
  url: Path,
165
- ...[init, options, queryClient]: RequiredKeysOf<Init> extends never
166
- ? [InitWithUnknowns<Init>?, Options?, QueryClient?]
167
- : [InitWithUnknowns<Init>, Options?, QueryClient?]
168
- ) => UseSuspenseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
270
+ ...[init, options, queryClient]: InitArguments<Init, [Options?, QueryClient?]>
271
+ ) => UseSuspenseQueryResult<
272
+ InferSelectReturnType<QueryData<Response["data"]>, Options["select"]>,
273
+ Response["error"]
274
+ >;
169
275
 
170
- export type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
276
+ export type UseMutationMethod<Paths extends {}, Media extends MediaType> = <
171
277
  Method extends HttpMethod,
172
278
  Path extends PathsWithMethod<Paths, Method>,
173
279
  Init extends MaybeOptionalInit<Paths[Path], Method>,
174
- Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
280
+ // note: Required is used to avoid repeating NonNullable in UseQuery types
281
+ Response extends Required<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>,
175
282
  TOnMutateResult = unknown,
176
283
  >(
177
284
  method: Method,
@@ -192,6 +299,7 @@ export interface OpenapiQueryClient<Paths extends {}, Media extends MediaType =
192
299
  }
193
300
 
194
301
  export type MethodResponse<
302
+ // biome-ignore lint/suspicious/noExplicitAny: matches a client of any paths, as upstream's
195
303
  CreatedClient extends OpenapiQueryClient<any, any>,
196
304
  Method extends HttpMethod,
197
305
  Path extends CreatedClient extends OpenapiQueryClient<infer Paths, infer _Media>
@@ -199,13 +307,18 @@ export type MethodResponse<
199
307
  : never,
200
308
  Options = object,
201
309
  > =
202
- CreatedClient extends OpenapiQueryClient<infer Paths extends { [key: string]: any }, infer Media extends MediaType>
310
+ CreatedClient extends OpenapiQueryClient<
311
+ // biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
312
+ infer Paths extends { [key: string]: any },
313
+ infer Media extends MediaType
314
+ >
203
315
  ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]>
204
316
  : never;
205
317
 
206
318
  /**
207
- * Throw the error of a failed fetch result. `if (error)` missed two failures: a non-OK response with
208
- * an empty body (`error` is `undefined`, upstream #2070, #2829) and a falsy error body such as `0`.
319
+ * Throw the error of a failed fetch result. `if (error)` missed two failures: a non-OK response
320
+ * with an empty body (`error` is `undefined`, upstream #2070, #2829) and a falsy error body such as
321
+ * `0`.
209
322
  */
210
323
  function throwIfFailed(result: { error?: unknown; response: Response }): void {
211
324
  if (result.error !== undefined || !result.response.ok) {
@@ -233,30 +346,43 @@ function pageInit(init: unknown, { pageParamName, pageParam, signal }: PageReque
233
346
  return { ...rest, signal, params: { ...params, query: { ...params?.query, ...page } } };
234
347
  }
235
348
 
236
- // TODO: Add the ability to bring queryClient as argument
349
+ /**
350
+ * Hooks and query options for a fetch client. A `QueryClient` other than the one from the React
351
+ * context is passed to each hook as its last argument; `createClient` does not take one.
352
+ */
353
+ // biome-ignore lint/complexity/noExcessiveLinesPerFunction: inherited, kept until 1.1 (ADR-036)
237
354
  export default function createClient<Paths extends {}, Media extends MediaType = MediaType>(
238
355
  client: FetchClient<Paths, Media>,
239
356
  ): OpenapiQueryClient<Paths, Media> {
240
357
  const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({
241
- queryKey: [method, path, init],
358
+ queryKey,
242
359
  signal,
243
360
  }: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {
361
+ // While `Path` is generic, TypeScript cannot work out that the key is a tuple: the init is a
362
+ // conditional type per operation (`MaybeOptionalInit`), and the key's type waits for it.
363
+ const [method, path, init] = queryKey as readonly [Method, Path, unknown?];
244
364
  const mth = method.toUpperCase() as Uppercase<typeof method>;
245
365
  const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
246
- const result = await fn(path, { signal, ...(init as any) }); // TODO: find a way to avoid as any
366
+ // The init is `unknown` here. An `any` init lets the call infer the generic response type
367
+ // that `queryOptions` declares; a typed init makes TypeScript compare two unresolved
368
+ // conditional types, which it cannot do.
369
+ // biome-ignore lint/suspicious/noExplicitAny: see the comment above
370
+ const result = await fn(path, { signal, ...(init as any) });
247
371
  throwIfFailed(result);
248
- const { data, response } = result;
249
- if (response.status === 204 || response.headers.get("Content-Length") === "0") {
250
- return data ?? null;
251
- }
252
-
253
- return data;
372
+ // TanStack Query rejects `undefined` query data. The fetch client gives `undefined` for every
373
+ // success without a body: a 204, `Content-Length: 0`, an empty chunked body, a HEAD request
374
+ // (upstream #2234). A parsed body is never `undefined`, so this changes nothing else.
375
+ return result.data ?? null;
254
376
  };
255
377
 
256
378
  const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({
257
- queryKey: (init === undefined ? ([method, path] as const) : ([method, path, init] as const)) as DataTag<
379
+ queryKey: (init === undefined
380
+ ? ([method, path] as const)
381
+ : ([method, path, init] as const)) as DataTag<
258
382
  QueryKey<Paths, typeof method, typeof path>,
383
+ // biome-ignore lint/suspicious/noExplicitAny: the declared return type sets the data type
259
384
  any,
385
+ // biome-ignore lint/suspicious/noExplicitAny: the declared return type sets the error type
260
386
  any
261
387
  >,
262
388
  queryFn,
@@ -266,11 +392,18 @@ export default function createClient<Paths extends {}, Media extends MediaType =
266
392
  return {
267
393
  queryOptions,
268
394
  useQuery: (method, path, ...[init, options, queryClient]) =>
269
- useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
395
+ useQuery(
396
+ queryOptions(method, path, init as InitWithUnknowns<typeof init>, options),
397
+ queryClient,
398
+ ),
270
399
  useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>
271
- useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
400
+ useSuspenseQuery(
401
+ queryOptions(method, path, init as InitWithUnknowns<typeof init>, options),
402
+ queryClient,
403
+ ),
404
+ // biome-ignore lint/complexity/useMaxParams: public signature of 1.x (ADR-036)
272
405
  useInfiniteQuery: (method, path, init, options, queryClient) => {
273
- const { pageParamName = DEFAULT_PAGE_PARAM_NAME, ...restOptions } = options;
406
+ const { pageParamName = DEFAULT_PAGE_PARAM_NAME, placeholderData, ...restOptions } = options;
274
407
  const { queryKey } = queryOptions(method, path, init);
275
408
  const mth = method.toUpperCase() as Uppercase<typeof method>;
276
409
  const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
@@ -279,11 +412,21 @@ export default function createClient<Paths extends {}, Media extends MediaType =
279
412
  queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER] as const,
280
413
  // The request comes from the arguments; the key only identifies the cache entry.
281
414
  queryFn: async ({ pageParam, signal }) => {
282
- const result = await fn(path, pageInit(init, { pageParamName, pageParam, signal }) as any);
415
+ const result = await fn(
416
+ path,
417
+ // biome-ignore lint/suspicious/noExplicitAny: a generic init; see queryFn
418
+ pageInit(init, { pageParamName, pageParam, signal }) as any,
419
+ );
283
420
  throwIfFailed(result);
421
+ // An empty page stays `undefined`, as in upstream: TanStack Query rejects only
422
+ // `undefined` query data, and the infinite query data is the `{ pages }` object.
284
423
  return result.data;
285
424
  },
286
425
  ...restOptions,
426
+ // Typed with an `unknown` page param for the caller (`InfinitePlaceholderData`). TanStack
427
+ // passes the previous data of this query, with its own page params, and takes it back.
428
+ // biome-ignore lint/suspicious/noExplicitAny: TanStack's type carries its own page params
429
+ placeholderData: placeholderData as any,
287
430
  },
288
431
  queryClient,
289
432
  );
@@ -297,8 +440,11 @@ export default function createClient<Paths extends {}, Media extends MediaType =
297
440
  const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
298
441
  const result = await fn(path, init as InitWithUnknowns<typeof init>);
299
442
  throwIfFailed(result);
443
+ // TanStack Query accepts `undefined` mutation data, so a success without a body stays
444
+ // `undefined`, as in upstream; only query data needs `null` (upstream #2234). The cast
445
+ // only bridges the generic types: `Response["data"]` still has `undefined` for an
446
+ // operation with a response without a body.
300
447
  const { data } = result;
301
-
302
448
  return data as Exclude<typeof data, undefined>;
303
449
  },
304
450
  ...options,