@7nohe/openapi-react-query-codegen 3.0.0-beta.1 โ 3.0.0-beta.3
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 +55 -5
- package/dist/constants.mjs +1 -0
- package/dist/tsmorph/buildCommon.mjs +69 -0
- package/dist/tsmorph/buildMutationHooks.mjs +2 -1
- package/dist/tsmorph/buildQueryHooks.mjs +154 -90
- package/dist/tsmorph/buildQueryOptions.mjs +67 -0
- package/dist/tsmorph/generateFiles.mjs +63 -20
- package/dist/tsmorph/index.mjs +3 -3
- package/dist/tsmorph/projectFactory.mjs +15 -0
- package/package.json +11 -9
package/README.md
CHANGED
|
@@ -1,12 +1,62 @@
|
|
|
1
1
|
# OpenAPI React Query Codegen
|
|
2
2
|
|
|
3
|
-
> Code generator for
|
|
3
|
+
> Code generator for [TanStack Query (React Query)](https://tanstack.com/query) based on your OpenAPI schema โ `queryOptions` factories following the official TanStack Query v5 pattern, plus ready-to-use hooks, prefetch, ensure, suspense, and infinite query helpers.
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen)
|
|
6
6
|
|
|
7
|
+
๐ **[Documentation](https://openapi-react-query-codegen.vercel.app)** ยท [Migrating to v3](https://openapi-react-query-codegen.vercel.app/guides/migrating-to-v3/)
|
|
8
|
+
|
|
7
9
|
## Features
|
|
8
10
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
11
|
+
- **`queryOptions` / `infiniteQueryOptions` factories** for every GET operation โ the [TanStack Query v5 recommended pattern](https://tanstack.com/query/latest/docs/framework/react/guides/query-options), composable with `useQuery`, `useQueries`, `useSuspenseQuery`, `prefetchQuery`, `ensureQueryData`, and `setQueryData` with full type safety
|
|
12
|
+
- **Custom hooks**: `useQuery`, `useSuspenseQuery`, `useMutation`, `useInfiniteQuery`, and `useSuspenseInfiniteQuery` variants per operation
|
|
13
|
+
- **SSR helpers**: `prefetchQuery`, `prefetchInfiniteQuery`, and `ensureQueryData` functions per operation โ ready for Next.js App Router hydration
|
|
14
|
+
- **Hierarchical query keys** with exported key constants and functions: invalidate one exact query, all infinite pages of an operation, or every cache entry of an operation with a single prefix
|
|
15
|
+
- **Pure TypeScript clients** generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) (fetch and axios)
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -D @7nohe/openapi-react-query-codegen
|
|
21
|
+
npx openapi-rq -i ./petstore.yaml
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { useQuery } from "@tanstack/react-query";
|
|
26
|
+
import { findPetsOptions } from "./openapi/queries";
|
|
27
|
+
|
|
28
|
+
function Pets() {
|
|
29
|
+
const { data } = useQuery(findPetsOptions({ query: { limit: 10 } }));
|
|
30
|
+
// ...or use the generated hook directly: useFindPets({ query: { limit: 10 } })
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
See the [documentation](https://openapi-react-query-codegen.vercel.app) for CLI options, SSR recipes, and infinite query usage.
|
|
35
|
+
|
|
36
|
+
## How it compares
|
|
37
|
+
|
|
38
|
+
| | This library | @hey-api tanstack-query plugin | Orval |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `queryOptions` / `infiniteQueryOptions` factories (TanStack v5 pattern) | โ
| โ
| โ |
|
|
41
|
+
| Ready-to-use hooks (`useQuery` / suspense / infinite variants) | โ
| โ (options only) | โ
|
|
|
42
|
+
| SSR helpers (`prefetchQuery` / `prefetchInfiniteQuery` / `ensureQueryData`) | โ
| โ | Partial (`usePrefetch`) |
|
|
43
|
+
| Hierarchical query keys for granular invalidation | โ
| โ
(tags) | Partial |
|
|
44
|
+
| Stable release line | โ
SemVer | pre-1.0, frequent breaking changes | โ
|
|
|
45
|
+
| MSW mock generation | โ (out of scope) | โ | โ
|
|
|
46
|
+
| Vue / Solid / Svelte / Angular | โ React-focused | โ
| โ
|
|
|
47
|
+
|
|
48
|
+
**Scope**: this library is deliberately React-focused and does not generate API mocks โ use Orval if MSW mocks are your priority, or hey-api's own plugin if you need non-React frameworks.
|
|
49
|
+
|
|
50
|
+
## Stability policy
|
|
51
|
+
|
|
52
|
+
This library builds on [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts), which is pre-1.0 and moves fast. We **pin the exact hey-api version** and absorb its breaking changes for you: hey-api upgrades land here only after our full snapshot-test suite passes, and are released as minor versions. Your generated API surface follows SemVer โ breaking output changes only happen in major versions, with a migration guide.
|
|
53
|
+
|
|
54
|
+
## Requirements
|
|
55
|
+
|
|
56
|
+
- Node.js 22.18+
|
|
57
|
+
- `@tanstack/react-query` 5.x (peer dependency)
|
|
58
|
+
- `typescript` 5.x or 6.x, `ts-morph` 28.x, `commander` 12โ15 (peer dependencies)
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT
|
package/dist/constants.mjs
CHANGED
|
@@ -117,3 +117,72 @@ export function buildMutationKeyFn(op) {
|
|
|
117
117
|
],
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Build the client options type for infinite queries.
|
|
122
|
+
* The page parameter is excluded because TanStack Query supplies it via the
|
|
123
|
+
* pageParam mechanism (#140).
|
|
124
|
+
* Example:
|
|
125
|
+
* export type FindPaginatedPetsInfiniteClientOptions = Omit<Options<FindPaginatedPetsData, true>, "query"> &
|
|
126
|
+
* { query?: Omit<NonNullable<FindPaginatedPetsData["query"]>, "page"> };
|
|
127
|
+
*/
|
|
128
|
+
export function buildInfiniteClientOptionsType(op, ctx) {
|
|
129
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
130
|
+
? `${op.capitalizedMethodName}Data`
|
|
131
|
+
: "unknown";
|
|
132
|
+
const type = dataTypeName === "unknown"
|
|
133
|
+
? "Options<unknown, true>"
|
|
134
|
+
: `Omit<Options<${dataTypeName}, true>, "query"> & { query?: Omit<NonNullable<${dataTypeName}["query"]>, "${ctx.pageParam}"> }`;
|
|
135
|
+
return {
|
|
136
|
+
kind: StructureKind.TypeAlias,
|
|
137
|
+
isExported: true,
|
|
138
|
+
name: `${op.capitalizedMethodName}InfiniteClientOptions`,
|
|
139
|
+
type,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Build the infinite query key constant.
|
|
144
|
+
* Shares the plain query key as its first segment so a single
|
|
145
|
+
* `invalidateQueries({ queryKey: [useXKey] })` matches both the plain and the
|
|
146
|
+
* infinite cache entries of an operation (#174), while the extra "infinite"
|
|
147
|
+
* segment keeps cached InfiniteData from colliding with plain query data (#140).
|
|
148
|
+
* Example: export const useFindPaginatedPetsInfiniteKey = [useFindPaginatedPetsKey, "infinite"] as const;
|
|
149
|
+
*/
|
|
150
|
+
export function buildInfiniteQueryKeyConst(op) {
|
|
151
|
+
return {
|
|
152
|
+
kind: StructureKind.VariableStatement,
|
|
153
|
+
isExported: true,
|
|
154
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
155
|
+
declarations: [
|
|
156
|
+
{
|
|
157
|
+
name: `use${op.capitalizedMethodName}InfiniteKey`,
|
|
158
|
+
initializer: `[use${op.capitalizedMethodName}Key, "infinite"] as const`,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build the infinite query key function.
|
|
165
|
+
* The custom queryKey argument only replaces the params segment โ the
|
|
166
|
+
* hierarchical [opKey, "infinite"] prefix is always preserved so
|
|
167
|
+
* prefix-based invalidation keeps working.
|
|
168
|
+
* Example: export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array<unknown>) =>
|
|
169
|
+
* [...useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])];
|
|
170
|
+
*/
|
|
171
|
+
export function buildInfiniteQueryKeyFn(op) {
|
|
172
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
173
|
+
const params = [
|
|
174
|
+
`clientOptions: ${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`,
|
|
175
|
+
"queryKey?: Array<unknown>",
|
|
176
|
+
];
|
|
177
|
+
return {
|
|
178
|
+
kind: StructureKind.VariableStatement,
|
|
179
|
+
isExported: true,
|
|
180
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
181
|
+
declarations: [
|
|
182
|
+
{
|
|
183
|
+
name: `Use${op.capitalizedMethodName}InfiniteKeyFn`,
|
|
184
|
+
initializer: `(${params.join(", ")}) => [...use${op.capitalizedMethodName}InfiniteKey, ...(queryKey ?? [clientOptions])]`,
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StructureKind, VariableDeclarationKind, } from "ts-morph";
|
|
2
|
+
import { SDK_CALL_ARGS } from "./buildQueryHooks.mjs";
|
|
2
3
|
/**
|
|
3
4
|
* Get the error type string based on client type.
|
|
4
5
|
*/
|
|
@@ -33,7 +34,7 @@ export function buildUseMutationHook(op, ctx) {
|
|
|
33
34
|
? `${op.capitalizedMethodName}Data`
|
|
34
35
|
: "unknown";
|
|
35
36
|
const optionsType = `Options<${dataTypeName}, true>`;
|
|
36
|
-
const mutationFn = `clientOptions => ${op.methodName}(
|
|
37
|
+
const mutationFn = `clientOptions => ${op.methodName}(${SDK_CALL_ARGS}) as unknown as Promise<TData>`;
|
|
37
38
|
const body = `useMutation<TData, TError, ${optionsType}, TContext>({ mutationKey: Common.Use${op.capitalizedMethodName}KeyFn(mutationKey), mutationFn: ${mutationFn}, ...options })`;
|
|
38
39
|
return {
|
|
39
40
|
kind: StructureKind.VariableStatement,
|
|
@@ -14,38 +14,26 @@ function getErrorType(op, ctx) {
|
|
|
14
14
|
return errorType;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* Resolve the generated Data type name for an operation, falling back to
|
|
18
|
+
* unknown when the operation has no generated Data type.
|
|
18
19
|
*/
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
if (hookType === "useInfiniteQuery") {
|
|
25
|
-
return `InfiniteData<${baseType}>`;
|
|
26
|
-
}
|
|
27
|
-
return baseType;
|
|
20
|
+
export function getDataTypeName(op, ctx) {
|
|
21
|
+
return ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
22
|
+
? `${op.capitalizedMethodName}Data`
|
|
23
|
+
: "unknown";
|
|
28
24
|
}
|
|
29
25
|
/**
|
|
30
|
-
*
|
|
26
|
+
* SDK call arguments shared by every generated queryFn/mutationFn.
|
|
27
|
+
* throwOnError: true forces the SDK call to reject on error responses; the
|
|
28
|
+
* hey-api runtime default is false, which would resolve undefined data and
|
|
29
|
+
* swallow the error instead of surfacing it to TanStack Query (#172).
|
|
31
30
|
*/
|
|
32
|
-
|
|
33
|
-
switch (hookType) {
|
|
34
|
-
case "useSuspenseQuery":
|
|
35
|
-
return "UseSuspenseQueryOptions";
|
|
36
|
-
case "useInfiniteQuery":
|
|
37
|
-
return "UseInfiniteQueryOptions";
|
|
38
|
-
default:
|
|
39
|
-
return "UseQueryOptions";
|
|
40
|
-
}
|
|
41
|
-
}
|
|
31
|
+
export const SDK_CALL_ARGS = "{ ...clientOptions, throwOnError: true }";
|
|
42
32
|
/**
|
|
43
33
|
* Build the client options parameter string.
|
|
44
34
|
*/
|
|
45
|
-
function buildClientOptionsParam(op, ctx) {
|
|
46
|
-
const dataTypeName =
|
|
47
|
-
? `${op.capitalizedMethodName}Data`
|
|
48
|
-
: "unknown";
|
|
35
|
+
export function buildClientOptionsParam(op, ctx) {
|
|
36
|
+
const dataTypeName = getDataTypeName(op, ctx);
|
|
49
37
|
const hasParams = op.parameters.length > 0;
|
|
50
38
|
if (!hasParams) {
|
|
51
39
|
return `clientOptions: Options<${dataTypeName}, true> = {}`;
|
|
@@ -53,6 +41,62 @@ function buildClientOptionsParam(op, ctx) {
|
|
|
53
41
|
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
54
42
|
return `clientOptions: Options<${dataTypeName}, true>${defaultValue}`;
|
|
55
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Build the clientOptions parameter typed with the page-less infinite
|
|
46
|
+
* options type โ the page parameter is supplied by TanStack Query's
|
|
47
|
+
* pageParam mechanism.
|
|
48
|
+
*/
|
|
49
|
+
export function buildInfiniteClientOptionsParam(op) {
|
|
50
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
51
|
+
return `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the paginated SDK call shared by every infinite query builder.
|
|
55
|
+
*/
|
|
56
|
+
export function buildPagedQueryFn(op, ctx, castTData) {
|
|
57
|
+
const dataTypeName = getDataTypeName(op, ctx);
|
|
58
|
+
const thenClause = castTData
|
|
59
|
+
? ".then(response => response.data as TData) as TData"
|
|
60
|
+
: ".then(response => response.data)";
|
|
61
|
+
return `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number }, throwOnError: true } as Options<${dataTypeName}, true>)${thenClause}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Format the initialPageParam literal. Emits a numeric literal when possible
|
|
65
|
+
* so the inferred pageParam type matches what getNextPageParam returns.
|
|
66
|
+
*/
|
|
67
|
+
export function formatInitialPageParam(ctx) {
|
|
68
|
+
return /^-?\d+$/.test(ctx.initialPageParam)
|
|
69
|
+
? ctx.initialPageParam
|
|
70
|
+
: JSON.stringify(ctx.initialPageParam);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build the nested type for getNextPageParam.
|
|
74
|
+
* E.g., "meta.next" becomes "{ meta: { next: number } }"
|
|
75
|
+
*/
|
|
76
|
+
export function buildNestedNextPageType(nextPageParam) {
|
|
77
|
+
const segments = nextPageParam.split(".");
|
|
78
|
+
return segments.reduceRight((acc, segment) => {
|
|
79
|
+
return `{ ${segment}: ${acc} }`;
|
|
80
|
+
}, "number");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build the getNextPageParam expression. The parameter is annotated because
|
|
84
|
+
* not every TanStack entry point contextually types it (prefetchInfiniteQuery
|
|
85
|
+
* does not, which would fail noImplicitAny).
|
|
86
|
+
*/
|
|
87
|
+
export function buildGetNextPageParamExpr(ctx) {
|
|
88
|
+
const nestedType = buildNestedNextPageType(ctx.nextPageParam);
|
|
89
|
+
return `(response: unknown) => (response as ${nestedType}).${ctx.nextPageParam}`;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build an options type where the pagination fields TanStack Query marks as
|
|
93
|
+
* required become optional overrides: the generator supplies them, and
|
|
94
|
+
* callers may replace them for custom pagination schemes (#156, #146).
|
|
95
|
+
*/
|
|
96
|
+
export function buildOverridableInfiniteOptionsType(optionsTypeName) {
|
|
97
|
+
const instantiated = `${optionsTypeName}<TData, TError>`;
|
|
98
|
+
return `Omit<${instantiated}, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial<Pick<${instantiated}, "initialPageParam" | "getNextPageParam">>`;
|
|
99
|
+
}
|
|
56
100
|
/**
|
|
57
101
|
* Build useQuery hook.
|
|
58
102
|
* Example:
|
|
@@ -62,19 +106,16 @@ function buildClientOptionsParam(op, ctx) {
|
|
|
62
106
|
* options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">
|
|
63
107
|
* ) => useQuery<TData, TError>({
|
|
64
108
|
* queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey),
|
|
65
|
-
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data as TData) as TData,
|
|
109
|
+
* queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData,
|
|
66
110
|
* ...options
|
|
67
111
|
* });
|
|
68
112
|
*/
|
|
69
113
|
export function buildUseQueryHook(op, ctx) {
|
|
70
114
|
const hookName = `use${op.capitalizedMethodName}`;
|
|
71
115
|
const errorType = getErrorType(op, ctx);
|
|
72
|
-
const dataTypeDefault =
|
|
116
|
+
const dataTypeDefault = `Common.${op.capitalizedMethodName}DefaultResponse`;
|
|
73
117
|
const clientOptionsParam = buildClientOptionsParam(op, ctx);
|
|
74
|
-
const
|
|
75
|
-
// Build the queryFn body
|
|
76
|
-
const callArgs = hasParams ? "{ ...clientOptions }" : "{ ...clientOptions }";
|
|
77
|
-
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data as TData) as TData`;
|
|
118
|
+
const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data as TData) as TData`;
|
|
78
119
|
const body = `useQuery<TData, TError>({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;
|
|
79
120
|
return {
|
|
80
121
|
kind: StructureKind.VariableStatement,
|
|
@@ -96,11 +137,9 @@ export function buildUseQueryHook(op, ctx) {
|
|
|
96
137
|
export function buildUseSuspenseQueryHook(op, ctx) {
|
|
97
138
|
const hookName = `use${op.capitalizedMethodName}Suspense`;
|
|
98
139
|
const errorType = getErrorType(op, ctx);
|
|
99
|
-
const dataTypeDefault =
|
|
140
|
+
const dataTypeDefault = `NonNullable<Common.${op.capitalizedMethodName}DefaultResponse>`;
|
|
100
141
|
const clientOptionsParam = buildClientOptionsParam(op, ctx);
|
|
101
|
-
const
|
|
102
|
-
const callArgs = hasParams ? "{ ...clientOptions }" : "{ ...clientOptions }";
|
|
103
|
-
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data as TData) as TData`;
|
|
142
|
+
const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data as TData) as TData`;
|
|
104
143
|
const body = `useSuspenseQuery<TData, TError>({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;
|
|
105
144
|
return {
|
|
106
145
|
kind: StructureKind.VariableStatement,
|
|
@@ -117,38 +156,30 @@ export function buildUseSuspenseQueryHook(op, ctx) {
|
|
|
117
156
|
};
|
|
118
157
|
}
|
|
119
158
|
/**
|
|
120
|
-
* Build
|
|
121
|
-
*
|
|
159
|
+
* Build a useInfiniteQuery / useSuspenseInfiniteQuery hook. Both variants
|
|
160
|
+
* share the infinite query key (and therefore the cache); they differ only
|
|
161
|
+
* in the TanStack hook called, the options type, and the NonNullable TData
|
|
162
|
+
* default of the suspense variant.
|
|
122
163
|
*/
|
|
123
|
-
function
|
|
124
|
-
const segments = nextPageParam.split(".");
|
|
125
|
-
return segments.reduceRight((acc, segment) => {
|
|
126
|
-
return `{ ${segment}: ${acc} }`;
|
|
127
|
-
}, "number");
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Build useInfiniteQuery hook.
|
|
131
|
-
*/
|
|
132
|
-
export function buildUseInfiniteQueryHook(op, ctx) {
|
|
164
|
+
function buildInfiniteHook(op, ctx, suspense) {
|
|
133
165
|
if (!op.isPaginatable) {
|
|
134
166
|
return null;
|
|
135
167
|
}
|
|
136
|
-
const
|
|
168
|
+
const hookCall = suspense ? "useSuspenseInfiniteQuery" : "useInfiniteQuery";
|
|
169
|
+
const optionsTypeName = suspense
|
|
170
|
+
? "UseSuspenseInfiniteQueryOptions"
|
|
171
|
+
: "UseInfiniteQueryOptions";
|
|
172
|
+
const hookName = suspense
|
|
173
|
+
? `use${op.capitalizedMethodName}SuspenseInfinite`
|
|
174
|
+
: `use${op.capitalizedMethodName}Infinite`;
|
|
137
175
|
const errorType = getErrorType(op, ctx);
|
|
138
176
|
const baseDataType = `Common.${op.capitalizedMethodName}DefaultResponse`;
|
|
139
|
-
const
|
|
140
|
-
?
|
|
141
|
-
:
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number } }).then(response => response.data as TData) as TData`;
|
|
146
|
-
// Build getNextPageParam with nested type
|
|
147
|
-
const nestedType = buildNestedNextPageType(ctx.nextPageParam);
|
|
148
|
-
const getNextPageParam = `getNextPageParam: (response) => (response as ${nestedType}).${ctx.nextPageParam}`;
|
|
149
|
-
// initialPageParam is a string literal
|
|
150
|
-
const infiniteOptions = `initialPageParam: "${ctx.initialPageParam}", ${getNextPageParam}`;
|
|
151
|
-
const body = `useInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
|
|
177
|
+
const dataTypeDefault = suspense
|
|
178
|
+
? `InfiniteData<NonNullable<${baseDataType}>>`
|
|
179
|
+
: `InfiniteData<${baseDataType}>`;
|
|
180
|
+
const queryFn = buildPagedQueryFn(op, ctx, true);
|
|
181
|
+
const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`;
|
|
182
|
+
const body = `${hookCall}({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
|
|
152
183
|
return {
|
|
153
184
|
kind: StructureKind.VariableStatement,
|
|
154
185
|
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
@@ -158,33 +189,38 @@ export function buildUseInfiniteQueryHook(op, ctx) {
|
|
|
158
189
|
declarations: [
|
|
159
190
|
{
|
|
160
191
|
name: hookName,
|
|
161
|
-
initializer: `<TData =
|
|
192
|
+
initializer: `<TData = ${dataTypeDefault}, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${buildInfiniteClientOptionsParam(op)}, queryKey?: TQueryKey, options?: ${buildOverridableInfiniteOptionsType(optionsTypeName)}) => ${body}`,
|
|
162
193
|
},
|
|
163
194
|
],
|
|
164
195
|
};
|
|
165
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Build useInfiniteQuery hook.
|
|
199
|
+
*/
|
|
200
|
+
export function buildUseInfiniteQueryHook(op, ctx) {
|
|
201
|
+
return buildInfiniteHook(op, ctx, false);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Build useSuspenseInfiniteQuery hook.
|
|
205
|
+
*/
|
|
206
|
+
export function buildUseSuspenseInfiniteQueryHook(op, ctx) {
|
|
207
|
+
return buildInfiniteHook(op, ctx, true);
|
|
208
|
+
}
|
|
166
209
|
/**
|
|
167
210
|
* Build prefetch function.
|
|
168
211
|
* Example:
|
|
169
|
-
* export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}) =>
|
|
212
|
+
* export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}, options?: Omit<FetchQueryOptions<Common.FindPetsDefaultResponse>, "queryKey" | "queryFn">) =>
|
|
170
213
|
* queryClient.prefetchQuery({
|
|
171
214
|
* queryKey: Common.UseFindPetsKeyFn(clientOptions),
|
|
172
|
-
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data)
|
|
215
|
+
* queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data),
|
|
216
|
+
* ...options
|
|
173
217
|
* });
|
|
174
218
|
*/
|
|
175
219
|
export function buildPrefetchFn(op, ctx) {
|
|
176
220
|
const fnName = `prefetchUse${op.capitalizedMethodName}`;
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const hasParams = op.parameters.length > 0;
|
|
181
|
-
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
182
|
-
const clientOptionsParam = hasParams
|
|
183
|
-
? `clientOptions: Options<${dataTypeName}, true>${defaultValue}`
|
|
184
|
-
: `clientOptions: Options<${dataTypeName}, true> = {}`;
|
|
185
|
-
const callArgs = "{ ...clientOptions }";
|
|
186
|
-
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data)`;
|
|
187
|
-
const body = `queryClient.prefetchQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn} })`;
|
|
221
|
+
const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`;
|
|
222
|
+
const optionsParam = `options?: Omit<FetchQueryOptions<Common.${op.capitalizedMethodName}DefaultResponse>, "queryKey" | "queryFn">`;
|
|
223
|
+
const body = `queryClient.prefetchQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn}, ...options })`;
|
|
188
224
|
return {
|
|
189
225
|
kind: StructureKind.VariableStatement,
|
|
190
226
|
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
@@ -194,7 +230,42 @@ export function buildPrefetchFn(op, ctx) {
|
|
|
194
230
|
declarations: [
|
|
195
231
|
{
|
|
196
232
|
name: fnName,
|
|
197
|
-
initializer: `(queryClient: QueryClient, ${
|
|
233
|
+
initializer: `(queryClient: QueryClient, ${buildClientOptionsParam(op, ctx)}, ${optionsParam}) => ${body}`,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Build prefetchInfiniteQuery function for a paginatable operation.
|
|
240
|
+
* Example:
|
|
241
|
+
* export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit<FetchInfiniteQueryOptions<Common.FindPaginatedPetsDefaultResponse>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) =>
|
|
242
|
+
* queryClient.prefetchInfiniteQuery({
|
|
243
|
+
* queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions),
|
|
244
|
+
* queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data),
|
|
245
|
+
* initialPageParam: 1,
|
|
246
|
+
* getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage,
|
|
247
|
+
* ...options
|
|
248
|
+
* });
|
|
249
|
+
*/
|
|
250
|
+
export function buildPrefetchInfiniteQueryFn(op, ctx) {
|
|
251
|
+
if (!op.isPaginatable) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const fnName = `prefetchUse${op.capitalizedMethodName}Infinite`;
|
|
255
|
+
const queryFn = buildPagedQueryFn(op, ctx, false);
|
|
256
|
+
const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`;
|
|
257
|
+
const optionsParam = `options?: Omit<FetchInfiniteQueryOptions<Common.${op.capitalizedMethodName}DefaultResponse>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">`;
|
|
258
|
+
const body = `queryClient.prefetchInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
|
|
259
|
+
return {
|
|
260
|
+
kind: StructureKind.VariableStatement,
|
|
261
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
262
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
263
|
+
isExported: true,
|
|
264
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
265
|
+
declarations: [
|
|
266
|
+
{
|
|
267
|
+
name: fnName,
|
|
268
|
+
initializer: `(queryClient: QueryClient, ${buildInfiniteClientOptionsParam(op)}, ${optionsParam}) => ${body}`,
|
|
198
269
|
},
|
|
199
270
|
],
|
|
200
271
|
};
|
|
@@ -202,25 +273,18 @@ export function buildPrefetchFn(op, ctx) {
|
|
|
202
273
|
/**
|
|
203
274
|
* Build ensureQueryData function.
|
|
204
275
|
* Example:
|
|
205
|
-
* export const ensureUseFindPetsData = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}) =>
|
|
276
|
+
* export const ensureUseFindPetsData = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}, options?: Omit<EnsureQueryDataOptions<Common.FindPetsDefaultResponse>, "queryKey" | "queryFn">) =>
|
|
206
277
|
* queryClient.ensureQueryData({
|
|
207
278
|
* queryKey: Common.UseFindPetsKeyFn(clientOptions),
|
|
208
|
-
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data)
|
|
279
|
+
* queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data),
|
|
280
|
+
* ...options
|
|
209
281
|
* });
|
|
210
282
|
*/
|
|
211
283
|
export function buildEnsureQueryDataFn(op, ctx) {
|
|
212
284
|
const fnName = `ensureUse${op.capitalizedMethodName}Data`;
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const hasParams = op.parameters.length > 0;
|
|
217
|
-
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
218
|
-
const clientOptionsParam = hasParams
|
|
219
|
-
? `clientOptions: Options<${dataTypeName}, true>${defaultValue}`
|
|
220
|
-
: `clientOptions: Options<${dataTypeName}, true> = {}`;
|
|
221
|
-
const callArgs = "{ ...clientOptions }";
|
|
222
|
-
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data)`;
|
|
223
|
-
const body = `queryClient.ensureQueryData({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn} })`;
|
|
285
|
+
const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`;
|
|
286
|
+
const optionsParam = `options?: Omit<EnsureQueryDataOptions<Common.${op.capitalizedMethodName}DefaultResponse>, "queryKey" | "queryFn">`;
|
|
287
|
+
const body = `queryClient.ensureQueryData({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn}, ...options })`;
|
|
224
288
|
return {
|
|
225
289
|
kind: StructureKind.VariableStatement,
|
|
226
290
|
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
@@ -230,7 +294,7 @@ export function buildEnsureQueryDataFn(op, ctx) {
|
|
|
230
294
|
declarations: [
|
|
231
295
|
{
|
|
232
296
|
name: fnName,
|
|
233
|
-
initializer: `(queryClient: QueryClient, ${
|
|
297
|
+
initializer: `(queryClient: QueryClient, ${buildClientOptionsParam(op, ctx)}, ${optionsParam}) => ${body}`,
|
|
234
298
|
},
|
|
235
299
|
],
|
|
236
300
|
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { StructureKind, VariableDeclarationKind, } from "ts-morph";
|
|
2
|
+
import { buildClientOptionsParam, buildGetNextPageParamExpr, buildInfiniteClientOptionsParam, buildPagedQueryFn, formatInitialPageParam, SDK_CALL_ARGS, } from "./buildQueryHooks.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Build a queryOptions factory for a GET operation.
|
|
5
|
+
* The factory centralizes queryKey and queryFn so they can be reused with
|
|
6
|
+
* every TanStack Query utility (useQuery, useQueries, prefetchQuery,
|
|
7
|
+
* ensureQueryData, setQueryData, ...) with full type safety.
|
|
8
|
+
* Example:
|
|
9
|
+
* export const findPetsOptions = (clientOptions: Options<FindPetsData, true> = {}, queryKey?: Array<unknown>) =>
|
|
10
|
+
* queryOptions({
|
|
11
|
+
* queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey),
|
|
12
|
+
* queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data),
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
export function buildQueryOptionsFn(op, ctx) {
|
|
16
|
+
const fnName = `${op.methodName}Options`;
|
|
17
|
+
const clientOptionsParam = buildClientOptionsParam(op, ctx);
|
|
18
|
+
const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`;
|
|
19
|
+
const body = `queryOptions({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn} })`;
|
|
20
|
+
return {
|
|
21
|
+
kind: StructureKind.VariableStatement,
|
|
22
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
23
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
24
|
+
isExported: true,
|
|
25
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
26
|
+
declarations: [
|
|
27
|
+
{
|
|
28
|
+
name: fnName,
|
|
29
|
+
initializer: `(${clientOptionsParam}, queryKey?: Array<unknown>) => ${body}`,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build an infiniteQueryOptions factory for a paginatable GET operation.
|
|
36
|
+
* Uses the dedicated infinite query key and page-less options type.
|
|
37
|
+
* Example:
|
|
38
|
+
* export const findPaginatedPetsInfiniteOptions = (clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array<unknown>) =>
|
|
39
|
+
* infiniteQueryOptions({
|
|
40
|
+
* queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey),
|
|
41
|
+
* queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data),
|
|
42
|
+
* initialPageParam: 1,
|
|
43
|
+
* getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage,
|
|
44
|
+
* });
|
|
45
|
+
*/
|
|
46
|
+
export function buildInfiniteQueryOptionsFn(op, ctx) {
|
|
47
|
+
if (!op.isPaginatable) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const fnName = `${op.methodName}InfiniteOptions`;
|
|
51
|
+
const queryFn = buildPagedQueryFn(op, ctx, false);
|
|
52
|
+
const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`;
|
|
53
|
+
const body = `infiniteQueryOptions({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions} })`;
|
|
54
|
+
return {
|
|
55
|
+
kind: StructureKind.VariableStatement,
|
|
56
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
57
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
58
|
+
isExported: true,
|
|
59
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
60
|
+
declarations: [
|
|
61
|
+
{
|
|
62
|
+
name: fnName,
|
|
63
|
+
initializer: `(${buildInfiniteClientOptionsParam(op)}, queryKey?: Array<unknown>) => ${body}`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { StructureKind, } from "ts-morph";
|
|
2
1
|
import { OpenApiRqFiles } from "../constants.mjs";
|
|
3
|
-
import { buildDefaultResponseType, buildMutationKeyConst, buildMutationKeyFn, buildMutationResultType, buildQueryKeyConst, buildQueryKeyFn, buildQueryResultType, } from "./buildCommon.mjs";
|
|
2
|
+
import { buildDefaultResponseType, buildInfiniteClientOptionsType, buildInfiniteQueryKeyConst, buildInfiniteQueryKeyFn, buildMutationKeyConst, buildMutationKeyFn, buildMutationResultType, buildQueryKeyConst, buildQueryKeyFn, buildQueryResultType, } from "./buildCommon.mjs";
|
|
4
3
|
import { buildUseMutationHook } from "./buildMutationHooks.mjs";
|
|
5
|
-
import { buildEnsureQueryDataFn, buildPrefetchFn, buildUseInfiniteQueryHook, buildUseQueryHook, buildUseSuspenseQueryHook, } from "./buildQueryHooks.mjs";
|
|
6
|
-
import {
|
|
4
|
+
import { buildEnsureQueryDataFn, buildPrefetchFn, buildPrefetchInfiniteQueryFn, buildUseInfiniteQueryHook, buildUseQueryHook, buildUseSuspenseInfiniteQueryHook, buildUseSuspenseQueryHook, } from "./buildQueryHooks.mjs";
|
|
5
|
+
import { buildInfiniteQueryOptionsFn, buildQueryOptionsFn, } from "./buildQueryOptions.mjs";
|
|
6
|
+
import { buildAxiosErrorImport, buildClientImport, buildCommonImport, buildModelImport, buildQueryImport, buildQueryOptionsImport, buildServiceImport, createGenerationProject, } from "./projectFactory.mjs";
|
|
7
7
|
/**
|
|
8
8
|
* Build imports for common.ts file.
|
|
9
9
|
*/
|
|
@@ -30,22 +30,10 @@ function buildHookFileImports(ctx) {
|
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* Generate the index.ts file content.
|
|
33
|
+
* The content is constant, so no ts-morph project is needed.
|
|
33
34
|
*/
|
|
34
|
-
function generateIndexFile(
|
|
35
|
-
|
|
36
|
-
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.index}.ts`, undefined, { overwrite: true });
|
|
37
|
-
const exports = [
|
|
38
|
-
{
|
|
39
|
-
kind: StructureKind.ExportDeclaration,
|
|
40
|
-
moduleSpecifier: "./common",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
kind: StructureKind.ExportDeclaration,
|
|
44
|
-
moduleSpecifier: "./queries",
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
sourceFile.addExportDeclarations(exports);
|
|
48
|
-
return sourceFile.getFullText();
|
|
35
|
+
function generateIndexFile() {
|
|
36
|
+
return `export * from "./common";\nexport * from "./queries";\nexport * from "./queryOptions";\n`;
|
|
49
37
|
}
|
|
50
38
|
/**
|
|
51
39
|
* Generate the common.ts file content.
|
|
@@ -65,6 +53,12 @@ function generateCommonFile(operations, ctx) {
|
|
|
65
53
|
sourceFile.addVariableStatement(buildQueryKeyConst(op));
|
|
66
54
|
sourceFile.addVariableStatement(buildQueryKeyFn(op, ctx));
|
|
67
55
|
}
|
|
56
|
+
// Add dedicated infinite query types and keys for paginatable operations
|
|
57
|
+
for (const op of getOperations.filter((o) => o.isPaginatable)) {
|
|
58
|
+
sourceFile.addTypeAlias(buildInfiniteClientOptionsType(op, ctx));
|
|
59
|
+
sourceFile.addVariableStatement(buildInfiniteQueryKeyConst(op));
|
|
60
|
+
sourceFile.addVariableStatement(buildInfiniteQueryKeyFn(op));
|
|
61
|
+
}
|
|
68
62
|
// Add mutation types and keys
|
|
69
63
|
for (const op of mutationOperations) {
|
|
70
64
|
sourceFile.addTypeAlias(buildMutationResultType(op));
|
|
@@ -94,6 +88,37 @@ function generateQueriesFile(operations, ctx) {
|
|
|
94
88
|
}
|
|
95
89
|
return sourceFile.getFullText();
|
|
96
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Generate the queryOptions.ts file content.
|
|
93
|
+
*/
|
|
94
|
+
function generateQueryOptionsFile(operations, ctx) {
|
|
95
|
+
const project = createGenerationProject();
|
|
96
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.queryOptions}.ts`, undefined, { overwrite: true });
|
|
97
|
+
// Add imports
|
|
98
|
+
const imports = [
|
|
99
|
+
buildCommonImport(),
|
|
100
|
+
buildQueryOptionsImport(),
|
|
101
|
+
buildClientImport(ctx),
|
|
102
|
+
buildServiceImport(ctx),
|
|
103
|
+
];
|
|
104
|
+
const modelImport = buildModelImport(ctx);
|
|
105
|
+
if (modelImport) {
|
|
106
|
+
imports.push(modelImport);
|
|
107
|
+
}
|
|
108
|
+
sourceFile.addImportDeclarations(imports);
|
|
109
|
+
// Only GET operations have query options
|
|
110
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
111
|
+
for (const op of getOperations) {
|
|
112
|
+
sourceFile.addVariableStatement(buildQueryOptionsFn(op, ctx));
|
|
113
|
+
}
|
|
114
|
+
for (const op of getOperations) {
|
|
115
|
+
const infiniteOptions = buildInfiniteQueryOptionsFn(op, ctx);
|
|
116
|
+
if (infiniteOptions) {
|
|
117
|
+
sourceFile.addVariableStatement(infiniteOptions);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return sourceFile.getFullText();
|
|
121
|
+
}
|
|
97
122
|
/**
|
|
98
123
|
* Generate the suspense.ts file content.
|
|
99
124
|
*/
|
|
@@ -108,6 +133,13 @@ function generateSuspenseFile(operations, ctx) {
|
|
|
108
133
|
for (const op of getOperations) {
|
|
109
134
|
sourceFile.addVariableStatement(buildUseSuspenseQueryHook(op, ctx));
|
|
110
135
|
}
|
|
136
|
+
// Add useSuspenseInfiniteQuery hooks for paginatable operations
|
|
137
|
+
for (const op of getOperations.filter((o) => o.isPaginatable)) {
|
|
138
|
+
const hook = buildUseSuspenseInfiniteQueryHook(op, ctx);
|
|
139
|
+
if (hook) {
|
|
140
|
+
sourceFile.addVariableStatement(hook);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
111
143
|
return sourceFile.getFullText();
|
|
112
144
|
}
|
|
113
145
|
/**
|
|
@@ -143,6 +175,13 @@ function generatePrefetchFile(operations, ctx) {
|
|
|
143
175
|
for (const op of getOperations) {
|
|
144
176
|
sourceFile.addVariableStatement(buildPrefetchFn(op, ctx));
|
|
145
177
|
}
|
|
178
|
+
// Add prefetchInfiniteQuery functions for paginatable operations
|
|
179
|
+
for (const op of getOperations.filter((o) => o.isPaginatable)) {
|
|
180
|
+
const fn = buildPrefetchInfiniteQueryFn(op, ctx);
|
|
181
|
+
if (fn) {
|
|
182
|
+
sourceFile.addVariableStatement(fn);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
146
185
|
return sourceFile.getFullText();
|
|
147
186
|
}
|
|
148
187
|
/**
|
|
@@ -175,7 +214,7 @@ export function generateAllFiles(operations, ctx) {
|
|
|
175
214
|
return [
|
|
176
215
|
{
|
|
177
216
|
name: `${OpenApiRqFiles.index}.ts`,
|
|
178
|
-
content: addHeaderComment(generateIndexFile(
|
|
217
|
+
content: addHeaderComment(generateIndexFile(), ctx.version),
|
|
179
218
|
},
|
|
180
219
|
{
|
|
181
220
|
name: `${OpenApiRqFiles.common}.ts`,
|
|
@@ -185,6 +224,10 @@ export function generateAllFiles(operations, ctx) {
|
|
|
185
224
|
name: `${OpenApiRqFiles.queries}.ts`,
|
|
186
225
|
content: addHeaderComment(generateQueriesFile(operations, ctx), ctx.version),
|
|
187
226
|
},
|
|
227
|
+
{
|
|
228
|
+
name: `${OpenApiRqFiles.queryOptions}.ts`,
|
|
229
|
+
content: addHeaderComment(generateQueryOptionsFile(operations, ctx), ctx.version),
|
|
230
|
+
},
|
|
188
231
|
{
|
|
189
232
|
name: `${OpenApiRqFiles.suspense}.ts`,
|
|
190
233
|
content: addHeaderComment(generateSuspenseFile(operations, ctx), ctx.version),
|
package/dist/tsmorph/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { generateAllFiles } from "./generateFiles.mjs";
|
|
2
|
-
export { createGenerationProject } from "./projectFactory.mjs";
|
|
3
1
|
export * from "./buildCommon.mjs";
|
|
4
|
-
export * from "./buildQueryHooks.mjs";
|
|
5
2
|
export * from "./buildMutationHooks.mjs";
|
|
3
|
+
export * from "./buildQueryHooks.mjs";
|
|
4
|
+
export { generateAllFiles } from "./generateFiles.mjs";
|
|
5
|
+
export { createGenerationProject } from "./projectFactory.mjs";
|
|
@@ -41,17 +41,32 @@ export function buildQueryImport() {
|
|
|
41
41
|
{ name: "useQuery" },
|
|
42
42
|
{ name: "useSuspenseQuery" },
|
|
43
43
|
{ name: "useInfiniteQuery" },
|
|
44
|
+
{ name: "useSuspenseInfiniteQuery" },
|
|
44
45
|
{ name: "useMutation" },
|
|
45
46
|
{ name: "UseQueryResult" },
|
|
46
47
|
{ name: "UseQueryOptions" },
|
|
47
48
|
{ name: "UseInfiniteQueryOptions" },
|
|
49
|
+
{ name: "UseSuspenseInfiniteQueryOptions" },
|
|
48
50
|
{ name: "UseMutationOptions" },
|
|
49
51
|
{ name: "UseMutationResult" },
|
|
50
52
|
{ name: "UseSuspenseQueryOptions" },
|
|
51
53
|
{ name: "InfiniteData" },
|
|
54
|
+
{ name: "FetchQueryOptions", isTypeOnly: true },
|
|
55
|
+
{ name: "FetchInfiniteQueryOptions", isTypeOnly: true },
|
|
56
|
+
{ name: "EnsureQueryDataOptions", isTypeOnly: true },
|
|
52
57
|
],
|
|
53
58
|
};
|
|
54
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Build import structure for the queryOptions/infiniteQueryOptions helpers.
|
|
62
|
+
*/
|
|
63
|
+
export function buildQueryOptionsImport() {
|
|
64
|
+
return {
|
|
65
|
+
kind: StructureKind.ImportDeclaration,
|
|
66
|
+
moduleSpecifier: "@tanstack/react-query",
|
|
67
|
+
namedImports: [{ name: "queryOptions" }, { name: "infiniteQueryOptions" }],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
55
70
|
/**
|
|
56
71
|
* Build import structure for services.
|
|
57
72
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7nohe/openapi-react-query-codegen",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.3",
|
|
4
4
|
"description": "OpenAPI React Query Codegen",
|
|
5
5
|
"bin": {
|
|
6
6
|
"openapi-rq": "dist/cli.mjs"
|
|
@@ -43,20 +43,22 @@
|
|
|
43
43
|
"cross-spawn": "^7.0.3"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@biomejs/biome": "^
|
|
46
|
+
"@biomejs/biome": "^2.5.4",
|
|
47
47
|
"@types/cross-spawn": "^6.0.6",
|
|
48
|
-
"@types/node": "^22.
|
|
48
|
+
"@types/node": "^22.20.1",
|
|
49
49
|
"@types/semver": "^7.7.1",
|
|
50
|
-
"@vitest/coverage-v8": "^1.
|
|
51
|
-
"commander": "^
|
|
52
|
-
"lefthook": "^1.
|
|
53
|
-
"rimraf": "^
|
|
50
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
51
|
+
"commander": "^15.0.0",
|
|
52
|
+
"lefthook": "^2.1.10",
|
|
53
|
+
"rimraf": "^6.1.3",
|
|
54
54
|
"ts-morph": "^28.0.0",
|
|
55
55
|
"typescript": "^6.0.3",
|
|
56
|
-
"
|
|
56
|
+
"vite": "^7",
|
|
57
|
+
"vitest": "^4.1.10"
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
59
|
-
"
|
|
60
|
+
"@tanstack/react-query": "^5.0.0",
|
|
61
|
+
"commander": "12.x || 13.x || 14.x || 15.x",
|
|
60
62
|
"ts-morph": "28.x",
|
|
61
63
|
"typescript": "5.x || 6.x"
|
|
62
64
|
},
|