openapi-typesafe-react-query 0.0.0 → 1.0.0-rc.0
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/LICENSE +22 -0
- package/README.md +65 -1
- package/dist/index.cjs +79 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +35 -0
- package/dist/index.d.mts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.mjs +77 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +89 -4
- package/src/index.ts +309 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Martin Paucot
|
|
4
|
+
Copyright (c) 2026 Fedor Chishchin and openapi-typesafe contributors
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
1
|
# openapi-typesafe-react-query
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
|
+
|
|
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
|
+
no `any` and no `as` assertions.
|
|
7
|
+
|
|
8
|
+
A maintained, drop-in successor to `openapi-react-query` (see the [repository README](../../README.md)).
|
|
9
|
+
|
|
10
|
+
> **Status: in development; not published to npm yet.**
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
Install this library along with [openapi-typesafe-fetch](../openapi-typesafe-fetch) and [openapi-typesafe](../openapi-typesafe):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm i openapi-typesafe-react-query openapi-typesafe-fetch
|
|
18
|
+
npm i -D openapi-typesafe typescript
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Next, generate TypeScript types from your OpenAPI schema using openapi-typesafe:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx openapi-typesafe ./path/to/api/v1.yaml -o ./src/lib/api/v1.d.ts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Once your types have been generated from your schema, you can create a [fetch client](../openapi-typesafe-fetch), a react-query client and start querying your API.
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import createFetchClient from "openapi-typesafe-fetch";
|
|
33
|
+
import createClient from "openapi-typesafe-react-query";
|
|
34
|
+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typesafe
|
|
35
|
+
|
|
36
|
+
const fetchClient = createFetchClient<paths>({
|
|
37
|
+
baseUrl: "https://myapi.dev/v1/",
|
|
38
|
+
});
|
|
39
|
+
const $api = createClient(fetchClient);
|
|
40
|
+
|
|
41
|
+
const MyComponent = () => {
|
|
42
|
+
const { data, error, isPending } = $api.useQuery("get", "/blogposts/{post_id}", {
|
|
43
|
+
params: {
|
|
44
|
+
path: { post_id: "5" },
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (isPending || !data) return "Loading...";
|
|
49
|
+
|
|
50
|
+
if (error) return `An error occurred: ${error.message}`;
|
|
51
|
+
|
|
52
|
+
return <div>{data.title}</div>;
|
|
53
|
+
};
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> You can find more information about `createFetchClient` in the [openapi-typesafe-fetch documentation](../openapi-typesafe-fetch).
|
|
57
|
+
|
|
58
|
+
## Migrating from openapi-react-query
|
|
59
|
+
|
|
60
|
+
```diff
|
|
61
|
+
- import createClient from "openapi-react-query";
|
|
62
|
+
+ import createClient from "openapi-typesafe-react-query";
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
[MIT](./LICENSE)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const reactQuery = require('@tanstack/react-query');
|
|
4
|
+
|
|
5
|
+
const INFINITE_QUERY_KEY_MARKER = "infinite";
|
|
6
|
+
function throwIfFailed(result) {
|
|
7
|
+
if (result.error !== void 0 || !result.response.ok) {
|
|
8
|
+
throw result.error;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const DEFAULT_PAGE_PARAM_NAME = "cursor";
|
|
12
|
+
function pageInit(init, { pageParamName, pageParam, signal }) {
|
|
13
|
+
const { params, ...rest } = init ?? {};
|
|
14
|
+
const page = pageParam === void 0 ? {} : { [pageParamName]: pageParam };
|
|
15
|
+
return { ...rest, signal, params: { ...params, query: { ...params?.query, ...page } } };
|
|
16
|
+
}
|
|
17
|
+
function createClient(client) {
|
|
18
|
+
const queryFn = async ({
|
|
19
|
+
queryKey: [method, path, init],
|
|
20
|
+
signal
|
|
21
|
+
}) => {
|
|
22
|
+
const mth = method.toUpperCase();
|
|
23
|
+
const fn = client[mth];
|
|
24
|
+
const result = await fn(path, { signal, ...init });
|
|
25
|
+
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;
|
|
31
|
+
};
|
|
32
|
+
const queryOptions = (method, path, ...[init, options]) => ({
|
|
33
|
+
queryKey: init === void 0 ? [method, path] : [method, path, init],
|
|
34
|
+
queryFn,
|
|
35
|
+
...options
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
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),
|
|
41
|
+
useInfiniteQuery: (method, path, init, options, queryClient) => {
|
|
42
|
+
const { pageParamName = DEFAULT_PAGE_PARAM_NAME, ...restOptions } = options;
|
|
43
|
+
const { queryKey } = queryOptions(method, path, init);
|
|
44
|
+
const mth = method.toUpperCase();
|
|
45
|
+
const fn = client[mth];
|
|
46
|
+
return reactQuery.useInfiniteQuery(
|
|
47
|
+
{
|
|
48
|
+
queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER],
|
|
49
|
+
// The request comes from the arguments; the key only identifies the cache entry.
|
|
50
|
+
queryFn: async ({ pageParam, signal }) => {
|
|
51
|
+
const result = await fn(path, pageInit(init, { pageParamName, pageParam, signal }));
|
|
52
|
+
throwIfFailed(result);
|
|
53
|
+
return result.data;
|
|
54
|
+
},
|
|
55
|
+
...restOptions
|
|
56
|
+
},
|
|
57
|
+
queryClient
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
useMutation: (method, path, options, queryClient) => reactQuery.useMutation(
|
|
61
|
+
{
|
|
62
|
+
mutationKey: [method, path],
|
|
63
|
+
mutationFn: async (init) => {
|
|
64
|
+
const mth = method.toUpperCase();
|
|
65
|
+
const fn = client[mth];
|
|
66
|
+
const result = await fn(path, init);
|
|
67
|
+
throwIfFailed(result);
|
|
68
|
+
const { data } = result;
|
|
69
|
+
return data;
|
|
70
|
+
},
|
|
71
|
+
...options
|
|
72
|
+
},
|
|
73
|
+
queryClient
|
|
74
|
+
)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = createClient;
|
|
79
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
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';
|
|
3
|
+
import { HttpMethod, PathsWithMethod, MediaType, RequiredKeysOf } from 'openapi-typesafe-helpers';
|
|
4
|
+
|
|
5
|
+
type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
|
|
6
|
+
type InitWithUnknowns<Init> = Init & {
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
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];
|
|
10
|
+
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>;
|
|
15
|
+
}>;
|
|
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"> & {
|
|
18
|
+
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>;
|
|
22
|
+
interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
|
|
23
|
+
queryOptions: QueryOptionsFunction<Paths, Media>;
|
|
24
|
+
useQuery: UseQueryMethod<Paths, Media>;
|
|
25
|
+
useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;
|
|
26
|
+
useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;
|
|
27
|
+
useMutation: UseMutationMethod<Paths, Media>;
|
|
28
|
+
}
|
|
29
|
+
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
|
+
[key: string]: any;
|
|
31
|
+
}, infer Media extends MediaType> ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]> : never;
|
|
32
|
+
declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>): OpenapiQueryClient<Paths, Media>;
|
|
33
|
+
|
|
34
|
+
export = createClient;
|
|
35
|
+
export type { InfiniteQueryKey, MethodResponse, OpenapiQueryClient, QueryKey, QueryOptionsFunction, UseInfiniteQueryMethod, UseMutationMethod, UseQueryMethod, UseSuspenseQueryMethod };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
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';
|
|
3
|
+
import { HttpMethod, PathsWithMethod, MediaType, RequiredKeysOf } from 'openapi-typesafe-helpers';
|
|
4
|
+
|
|
5
|
+
type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
|
|
6
|
+
type InitWithUnknowns<Init> = Init & {
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
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];
|
|
10
|
+
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>;
|
|
15
|
+
}>;
|
|
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"> & {
|
|
18
|
+
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>;
|
|
22
|
+
interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
|
|
23
|
+
queryOptions: QueryOptionsFunction<Paths, Media>;
|
|
24
|
+
useQuery: UseQueryMethod<Paths, Media>;
|
|
25
|
+
useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;
|
|
26
|
+
useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;
|
|
27
|
+
useMutation: UseMutationMethod<Paths, Media>;
|
|
28
|
+
}
|
|
29
|
+
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
|
+
[key: string]: any;
|
|
31
|
+
}, infer Media extends MediaType> ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]> : never;
|
|
32
|
+
declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>): OpenapiQueryClient<Paths, Media>;
|
|
33
|
+
|
|
34
|
+
export { createClient as default };
|
|
35
|
+
export type { InfiniteQueryKey, MethodResponse, OpenapiQueryClient, QueryKey, QueryOptionsFunction, UseInfiniteQueryMethod, UseMutationMethod, UseQueryMethod, UseSuspenseQueryMethod };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
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';
|
|
3
|
+
import { HttpMethod, PathsWithMethod, MediaType, RequiredKeysOf } from 'openapi-typesafe-helpers';
|
|
4
|
+
|
|
5
|
+
type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
|
|
6
|
+
type InitWithUnknowns<Init> = Init & {
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
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];
|
|
10
|
+
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>;
|
|
15
|
+
}>;
|
|
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"> & {
|
|
18
|
+
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>;
|
|
22
|
+
interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
|
|
23
|
+
queryOptions: QueryOptionsFunction<Paths, Media>;
|
|
24
|
+
useQuery: UseQueryMethod<Paths, Media>;
|
|
25
|
+
useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;
|
|
26
|
+
useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;
|
|
27
|
+
useMutation: UseMutationMethod<Paths, Media>;
|
|
28
|
+
}
|
|
29
|
+
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
|
+
[key: string]: any;
|
|
31
|
+
}, infer Media extends MediaType> ? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]> : never;
|
|
32
|
+
declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>): OpenapiQueryClient<Paths, Media>;
|
|
33
|
+
|
|
34
|
+
export = createClient;
|
|
35
|
+
export type { InfiniteQueryKey, MethodResponse, OpenapiQueryClient, QueryKey, QueryOptionsFunction, UseInfiniteQueryMethod, UseMutationMethod, UseQueryMethod, UseSuspenseQueryMethod };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useMutation, useInfiniteQuery, useSuspenseQuery, useQuery } from '@tanstack/react-query';
|
|
2
|
+
|
|
3
|
+
const INFINITE_QUERY_KEY_MARKER = "infinite";
|
|
4
|
+
function throwIfFailed(result) {
|
|
5
|
+
if (result.error !== void 0 || !result.response.ok) {
|
|
6
|
+
throw result.error;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const DEFAULT_PAGE_PARAM_NAME = "cursor";
|
|
10
|
+
function pageInit(init, { pageParamName, pageParam, signal }) {
|
|
11
|
+
const { params, ...rest } = init ?? {};
|
|
12
|
+
const page = pageParam === void 0 ? {} : { [pageParamName]: pageParam };
|
|
13
|
+
return { ...rest, signal, params: { ...params, query: { ...params?.query, ...page } } };
|
|
14
|
+
}
|
|
15
|
+
function createClient(client) {
|
|
16
|
+
const queryFn = async ({
|
|
17
|
+
queryKey: [method, path, init],
|
|
18
|
+
signal
|
|
19
|
+
}) => {
|
|
20
|
+
const mth = method.toUpperCase();
|
|
21
|
+
const fn = client[mth];
|
|
22
|
+
const result = await fn(path, { signal, ...init });
|
|
23
|
+
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;
|
|
29
|
+
};
|
|
30
|
+
const queryOptions = (method, path, ...[init, options]) => ({
|
|
31
|
+
queryKey: init === void 0 ? [method, path] : [method, path, init],
|
|
32
|
+
queryFn,
|
|
33
|
+
...options
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
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),
|
|
39
|
+
useInfiniteQuery: (method, path, init, options, queryClient) => {
|
|
40
|
+
const { pageParamName = DEFAULT_PAGE_PARAM_NAME, ...restOptions } = options;
|
|
41
|
+
const { queryKey } = queryOptions(method, path, init);
|
|
42
|
+
const mth = method.toUpperCase();
|
|
43
|
+
const fn = client[mth];
|
|
44
|
+
return useInfiniteQuery(
|
|
45
|
+
{
|
|
46
|
+
queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER],
|
|
47
|
+
// The request comes from the arguments; the key only identifies the cache entry.
|
|
48
|
+
queryFn: async ({ pageParam, signal }) => {
|
|
49
|
+
const result = await fn(path, pageInit(init, { pageParamName, pageParam, signal }));
|
|
50
|
+
throwIfFailed(result);
|
|
51
|
+
return result.data;
|
|
52
|
+
},
|
|
53
|
+
...restOptions
|
|
54
|
+
},
|
|
55
|
+
queryClient
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
useMutation: (method, path, options, queryClient) => useMutation(
|
|
59
|
+
{
|
|
60
|
+
mutationKey: [method, path],
|
|
61
|
+
mutationFn: async (init) => {
|
|
62
|
+
const mth = method.toUpperCase();
|
|
63
|
+
const fn = client[mth];
|
|
64
|
+
const result = await fn(path, init);
|
|
65
|
+
throwIfFailed(result);
|
|
66
|
+
const { data } = result;
|
|
67
|
+
return data;
|
|
68
|
+
},
|
|
69
|
+
...options
|
|
70
|
+
},
|
|
71
|
+
queryClient
|
|
72
|
+
)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { createClient as default };
|
|
77
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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":";;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,KACrD,QAAA,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,KAC7D,gBAAA,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,OAAO,gBAAA;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,KACnC,WAAA;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;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,91 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openapi-typesafe-react-query",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"description": "Fast, type-safe @tanstack/react-query client to work with your OpenAPI schema.",
|
|
4
|
+
"version": "1.0.0-rc.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Fedor Chishchin",
|
|
7
|
+
"url": "https://github.com/fedorello"
|
|
8
|
+
},
|
|
9
|
+
"contributors": [
|
|
10
|
+
{
|
|
11
|
+
"name": "Martin Paucot",
|
|
12
|
+
"email": "contact@martin-paucot.fr"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.mjs",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./dist/index.mjs",
|
|
21
|
+
"require": "./dist/index.cjs",
|
|
22
|
+
"default": "./dist/index.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./*": "./*"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"homepage": "https://github.com/openapi-typesafe/openapi-typesafe#readme",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/openapi-typesafe/openapi-typesafe.git",
|
|
34
|
+
"directory": "packages/openapi-typesafe-react-query"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/openapi-typesafe/openapi-typesafe/issues"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"openapi",
|
|
41
|
+
"swagger",
|
|
42
|
+
"rest",
|
|
43
|
+
"api",
|
|
44
|
+
"oapi_3",
|
|
45
|
+
"oapi_3_1",
|
|
46
|
+
"typescript",
|
|
47
|
+
"fetch",
|
|
48
|
+
"react",
|
|
49
|
+
"react-query",
|
|
50
|
+
"tanstack"
|
|
51
|
+
],
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"openapi-typesafe-helpers": "^1.0.0-rc.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@tanstack/react-query": "5.105.1",
|
|
57
|
+
"@testing-library/react": "16.3.3",
|
|
58
|
+
"@types/react": "19.3.0",
|
|
59
|
+
"@types/react-dom": "19.3.0",
|
|
60
|
+
"@vitejs/plugin-react": "6.1.2",
|
|
61
|
+
"execa": "^10.2.1",
|
|
62
|
+
"jsdom": "30.1.3",
|
|
63
|
+
"msw": "3.0.3",
|
|
64
|
+
"openapi-typesafe": "^1.0.0-rc.0",
|
|
65
|
+
"openapi-typesafe-fetch": "^1.0.0-rc.0",
|
|
66
|
+
"react": "19.3.1",
|
|
67
|
+
"react-dom": "19.3.1",
|
|
68
|
+
"react-error-boundary": "6.1.6"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@tanstack/react-query": "^5.80.0",
|
|
72
|
+
"openapi-typesafe-fetch": "^1.0.0-rc.0"
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">=22"
|
|
76
|
+
},
|
|
77
|
+
"scripts": {
|
|
78
|
+
"build": "unbuild",
|
|
79
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
80
|
+
"format": "biome format . --write",
|
|
81
|
+
"lint": "pnpm run lint:js && pnpm run lint:ts",
|
|
82
|
+
"lint:js": "biome check .",
|
|
83
|
+
"lint:ts": "tsc --noEmit",
|
|
84
|
+
"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
|
+
"test": "pnpm run generate-types && pnpm run test:js && pnpm run test:exports",
|
|
86
|
+
"test:js": "vitest run",
|
|
87
|
+
"test:exports": "pnpm run build && ../../scripts/check-exports.sh",
|
|
88
|
+
"version": "pnpm run prepare && pnpm run build",
|
|
89
|
+
"test:coverage": "vitest run --coverage"
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DataTag,
|
|
3
|
+
type InfiniteData,
|
|
4
|
+
type QueryClient,
|
|
5
|
+
type QueryFunctionContext,
|
|
6
|
+
type SkipToken,
|
|
7
|
+
type UseInfiniteQueryOptions,
|
|
8
|
+
type UseInfiniteQueryResult,
|
|
9
|
+
type UseMutationOptions,
|
|
10
|
+
type UseMutationResult,
|
|
11
|
+
type UseQueryOptions,
|
|
12
|
+
type UseQueryResult,
|
|
13
|
+
type UseSuspenseQueryOptions,
|
|
14
|
+
type UseSuspenseQueryResult,
|
|
15
|
+
useInfiniteQuery,
|
|
16
|
+
useMutation,
|
|
17
|
+
useQuery,
|
|
18
|
+
useSuspenseQuery,
|
|
19
|
+
} from "@tanstack/react-query";
|
|
20
|
+
import type {
|
|
21
|
+
ClientMethod,
|
|
22
|
+
DefaultParamsOption,
|
|
23
|
+
Client as FetchClient,
|
|
24
|
+
FetchResponse,
|
|
25
|
+
MaybeOptionalInit,
|
|
26
|
+
} from "openapi-typesafe-fetch";
|
|
27
|
+
import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typesafe-helpers";
|
|
28
|
+
|
|
29
|
+
// Helper type to dynamically infer the type from the `select` property
|
|
30
|
+
type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
|
|
31
|
+
|
|
32
|
+
type InitWithUnknowns<Init> = Init & { [key: string]: unknown };
|
|
33
|
+
|
|
34
|
+
export type QueryKey<
|
|
35
|
+
Paths extends Record<string, Record<HttpMethod, {}>>,
|
|
36
|
+
Method extends HttpMethod,
|
|
37
|
+
Path extends PathsWithMethod<Paths, Method>,
|
|
38
|
+
Init = MaybeOptionalInit<Paths[Path], Method>,
|
|
39
|
+
> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The last element of an infinite query's key. `useQuery` and `useInfiniteQuery` cache different
|
|
43
|
+
* data (`TData` and `InfiniteData<TData>`), so the same request must not share one key (upstream
|
|
44
|
+
* #2184, ADR-022). A prefix such as `[method, path]` still matches both. Not exported: a runtime
|
|
45
|
+
* named export next to the default export would change what `require()` returns.
|
|
46
|
+
*/
|
|
47
|
+
const INFINITE_QUERY_KEY_MARKER = "infinite";
|
|
48
|
+
|
|
49
|
+
export type InfiniteQueryKey<
|
|
50
|
+
Paths extends Record<string, Record<HttpMethod, {}>>,
|
|
51
|
+
Method extends HttpMethod,
|
|
52
|
+
Path extends PathsWithMethod<Paths, Method>,
|
|
53
|
+
> = readonly [...QueryKey<Paths, Method, Path>, typeof INFINITE_QUERY_KEY_MARKER];
|
|
54
|
+
|
|
55
|
+
export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
|
|
56
|
+
Method extends HttpMethod,
|
|
57
|
+
Path extends PathsWithMethod<Paths, Method>,
|
|
58
|
+
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
|
|
60
|
+
Options extends Omit<
|
|
61
|
+
UseQueryOptions<
|
|
62
|
+
Response["data"],
|
|
63
|
+
Response["error"],
|
|
64
|
+
InferSelectReturnType<Response["data"], Options["select"]>,
|
|
65
|
+
QueryKey<Paths, Method, Path>
|
|
66
|
+
>,
|
|
67
|
+
"queryKey" | "queryFn"
|
|
68
|
+
>,
|
|
69
|
+
>(
|
|
70
|
+
method: Method,
|
|
71
|
+
path: Path,
|
|
72
|
+
...[init, options]: RequiredKeysOf<Init> extends never
|
|
73
|
+
? [InitWithUnknowns<Init>?, Options?]
|
|
74
|
+
: [InitWithUnknowns<Init>, Options?]
|
|
75
|
+
) => NoInfer<
|
|
76
|
+
Omit<
|
|
77
|
+
UseQueryOptions<
|
|
78
|
+
Response["data"],
|
|
79
|
+
Response["error"],
|
|
80
|
+
InferSelectReturnType<Response["data"], Options["select"]>,
|
|
81
|
+
QueryKey<Paths, Method, Path>
|
|
82
|
+
>,
|
|
83
|
+
"queryFn" | "queryKey"
|
|
84
|
+
> & {
|
|
85
|
+
queryKey: DataTag<QueryKey<Paths, Method, Path>, Response["data"], Response["error"]>;
|
|
86
|
+
queryFn: Exclude<
|
|
87
|
+
UseQueryOptions<
|
|
88
|
+
Response["data"],
|
|
89
|
+
Response["error"],
|
|
90
|
+
InferSelectReturnType<Response["data"], Options["select"]>,
|
|
91
|
+
QueryKey<Paths, Method, Path>
|
|
92
|
+
>["queryFn"],
|
|
93
|
+
SkipToken | undefined
|
|
94
|
+
>;
|
|
95
|
+
}
|
|
96
|
+
>;
|
|
97
|
+
|
|
98
|
+
export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
|
|
99
|
+
Method extends HttpMethod,
|
|
100
|
+
Path extends PathsWithMethod<Paths, Method>,
|
|
101
|
+
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
|
|
103
|
+
Options extends Omit<
|
|
104
|
+
UseQueryOptions<
|
|
105
|
+
Response["data"],
|
|
106
|
+
Response["error"],
|
|
107
|
+
InferSelectReturnType<Response["data"], Options["select"]>,
|
|
108
|
+
QueryKey<Paths, Method, Path>
|
|
109
|
+
>,
|
|
110
|
+
"queryKey" | "queryFn"
|
|
111
|
+
>,
|
|
112
|
+
>(
|
|
113
|
+
method: Method,
|
|
114
|
+
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"]>;
|
|
119
|
+
|
|
120
|
+
export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
|
|
121
|
+
Method extends HttpMethod,
|
|
122
|
+
Path extends PathsWithMethod<Paths, Method>,
|
|
123
|
+
Init extends MaybeOptionalInit<Paths[Path], Method>,
|
|
124
|
+
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
|
|
125
|
+
Options extends Omit<
|
|
126
|
+
UseInfiniteQueryOptions<
|
|
127
|
+
Response["data"],
|
|
128
|
+
Response["error"],
|
|
129
|
+
InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
|
|
130
|
+
InfiniteQueryKey<Paths, Method, Path>,
|
|
131
|
+
unknown
|
|
132
|
+
>,
|
|
133
|
+
"queryKey" | "queryFn"
|
|
134
|
+
> & {
|
|
135
|
+
pageParamName?: string;
|
|
136
|
+
},
|
|
137
|
+
>(
|
|
138
|
+
method: Method,
|
|
139
|
+
url: Path,
|
|
140
|
+
init: InitWithUnknowns<Init>,
|
|
141
|
+
options: Options,
|
|
142
|
+
queryClient?: QueryClient,
|
|
143
|
+
) => UseInfiniteQueryResult<
|
|
144
|
+
InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
|
|
145
|
+
Response["error"]
|
|
146
|
+
>;
|
|
147
|
+
|
|
148
|
+
export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
|
|
149
|
+
Method extends HttpMethod,
|
|
150
|
+
Path extends PathsWithMethod<Paths, Method>,
|
|
151
|
+
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
|
|
153
|
+
Options extends Omit<
|
|
154
|
+
UseSuspenseQueryOptions<
|
|
155
|
+
Response["data"],
|
|
156
|
+
Response["error"],
|
|
157
|
+
InferSelectReturnType<Response["data"], Options["select"]>,
|
|
158
|
+
QueryKey<Paths, Method, Path>
|
|
159
|
+
>,
|
|
160
|
+
"queryKey" | "queryFn"
|
|
161
|
+
>,
|
|
162
|
+
>(
|
|
163
|
+
method: Method,
|
|
164
|
+
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"]>;
|
|
169
|
+
|
|
170
|
+
export type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
|
|
171
|
+
Method extends HttpMethod,
|
|
172
|
+
Path extends PathsWithMethod<Paths, Method>,
|
|
173
|
+
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
|
|
175
|
+
TOnMutateResult = unknown,
|
|
176
|
+
>(
|
|
177
|
+
method: Method,
|
|
178
|
+
url: Path,
|
|
179
|
+
options?: Omit<
|
|
180
|
+
UseMutationOptions<Response["data"], Response["error"], Init, TOnMutateResult>,
|
|
181
|
+
"mutationKey" | "mutationFn"
|
|
182
|
+
>,
|
|
183
|
+
queryClient?: QueryClient,
|
|
184
|
+
) => UseMutationResult<Response["data"], Response["error"], Init, TOnMutateResult>;
|
|
185
|
+
|
|
186
|
+
export interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
|
|
187
|
+
queryOptions: QueryOptionsFunction<Paths, Media>;
|
|
188
|
+
useQuery: UseQueryMethod<Paths, Media>;
|
|
189
|
+
useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;
|
|
190
|
+
useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;
|
|
191
|
+
useMutation: UseMutationMethod<Paths, Media>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export type MethodResponse<
|
|
195
|
+
CreatedClient extends OpenapiQueryClient<any, any>,
|
|
196
|
+
Method extends HttpMethod,
|
|
197
|
+
Path extends CreatedClient extends OpenapiQueryClient<infer Paths, infer _Media>
|
|
198
|
+
? PathsWithMethod<Paths, Method>
|
|
199
|
+
: never,
|
|
200
|
+
Options = object,
|
|
201
|
+
> =
|
|
202
|
+
CreatedClient extends OpenapiQueryClient<infer Paths extends { [key: string]: any }, infer Media extends MediaType>
|
|
203
|
+
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]>
|
|
204
|
+
: never;
|
|
205
|
+
|
|
206
|
+
/**
|
|
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`.
|
|
209
|
+
*/
|
|
210
|
+
function throwIfFailed(result: { error?: unknown; response: Response }): void {
|
|
211
|
+
if (result.error !== undefined || !result.response.ok) {
|
|
212
|
+
throw result.error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The query parameter that carries the page param unless `pageParamName` names another. */
|
|
217
|
+
const DEFAULT_PAGE_PARAM_NAME = "cursor";
|
|
218
|
+
|
|
219
|
+
interface PageRequest {
|
|
220
|
+
readonly pageParamName: string;
|
|
221
|
+
readonly pageParam: unknown;
|
|
222
|
+
readonly signal: AbortSignal;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The request init of one page. An `undefined` page param (the first page with
|
|
227
|
+
* `initialPageParam: undefined`) is left out instead of being sent as `0` (upstream #2715, #2340,
|
|
228
|
+
* ADR-022), so a cursor API gets no cursor on the first page.
|
|
229
|
+
*/
|
|
230
|
+
function pageInit(init: unknown, { pageParamName, pageParam, signal }: PageRequest) {
|
|
231
|
+
const { params, ...rest } = (init ?? {}) as { params?: { query?: DefaultParamsOption } };
|
|
232
|
+
const page = pageParam === undefined ? {} : { [pageParamName]: pageParam };
|
|
233
|
+
return { ...rest, signal, params: { ...params, query: { ...params?.query, ...page } } };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// TODO: Add the ability to bring queryClient as argument
|
|
237
|
+
export default function createClient<Paths extends {}, Media extends MediaType = MediaType>(
|
|
238
|
+
client: FetchClient<Paths, Media>,
|
|
239
|
+
): OpenapiQueryClient<Paths, Media> {
|
|
240
|
+
const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({
|
|
241
|
+
queryKey: [method, path, init],
|
|
242
|
+
signal,
|
|
243
|
+
}: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {
|
|
244
|
+
const mth = method.toUpperCase() as Uppercase<typeof method>;
|
|
245
|
+
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
|
|
247
|
+
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;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({
|
|
257
|
+
queryKey: (init === undefined ? ([method, path] as const) : ([method, path, init] as const)) as DataTag<
|
|
258
|
+
QueryKey<Paths, typeof method, typeof path>,
|
|
259
|
+
any,
|
|
260
|
+
any
|
|
261
|
+
>,
|
|
262
|
+
queryFn,
|
|
263
|
+
...options,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
queryOptions,
|
|
268
|
+
useQuery: (method, path, ...[init, options, queryClient]) =>
|
|
269
|
+
useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
|
|
270
|
+
useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>
|
|
271
|
+
useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
|
|
272
|
+
useInfiniteQuery: (method, path, init, options, queryClient) => {
|
|
273
|
+
const { pageParamName = DEFAULT_PAGE_PARAM_NAME, ...restOptions } = options;
|
|
274
|
+
const { queryKey } = queryOptions(method, path, init);
|
|
275
|
+
const mth = method.toUpperCase() as Uppercase<typeof method>;
|
|
276
|
+
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
|
|
277
|
+
return useInfiniteQuery(
|
|
278
|
+
{
|
|
279
|
+
queryKey: [...queryKey, INFINITE_QUERY_KEY_MARKER] as const,
|
|
280
|
+
// The request comes from the arguments; the key only identifies the cache entry.
|
|
281
|
+
queryFn: async ({ pageParam, signal }) => {
|
|
282
|
+
const result = await fn(path, pageInit(init, { pageParamName, pageParam, signal }) as any);
|
|
283
|
+
throwIfFailed(result);
|
|
284
|
+
return result.data;
|
|
285
|
+
},
|
|
286
|
+
...restOptions,
|
|
287
|
+
},
|
|
288
|
+
queryClient,
|
|
289
|
+
);
|
|
290
|
+
},
|
|
291
|
+
useMutation: (method, path, options, queryClient) =>
|
|
292
|
+
useMutation(
|
|
293
|
+
{
|
|
294
|
+
mutationKey: [method, path],
|
|
295
|
+
mutationFn: async (init) => {
|
|
296
|
+
const mth = method.toUpperCase() as Uppercase<typeof method>;
|
|
297
|
+
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
|
|
298
|
+
const result = await fn(path, init as InitWithUnknowns<typeof init>);
|
|
299
|
+
throwIfFailed(result);
|
|
300
|
+
const { data } = result;
|
|
301
|
+
|
|
302
|
+
return data as Exclude<typeof data, undefined>;
|
|
303
|
+
},
|
|
304
|
+
...options,
|
|
305
|
+
},
|
|
306
|
+
queryClient,
|
|
307
|
+
),
|
|
308
|
+
};
|
|
309
|
+
}
|