@7nohe/openapi-react-query-codegen 2.2.0 → 3.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common.mjs +1 -74
- package/dist/constants.mjs +1 -0
- package/dist/createSource.mjs +13 -107
- package/dist/generate.mjs +1 -1
- package/dist/parseOperations.mjs +122 -0
- package/dist/tsmorph/buildCommon.mjs +183 -0
- package/dist/tsmorph/buildKeys.mjs +110 -0
- package/dist/tsmorph/buildMutationHooks.mjs +51 -0
- package/dist/tsmorph/buildQueryHooks.mjs +246 -0
- package/dist/tsmorph/buildQueryOptions.mjs +76 -0
- package/dist/tsmorph/generateFiles.mjs +251 -0
- package/dist/tsmorph/index.mjs +5 -0
- package/dist/tsmorph/projectFactory.mjs +131 -0
- package/dist/types.mjs +1 -0
- package/package.json +1 -1
- package/dist/createExports.mjs +0 -121
- package/dist/createImports.mjs +0 -54
- package/dist/createPrefetchOrEnsure.mjs +0 -56
- package/dist/createUseMutation.mjs +0 -97
- package/dist/createUseQuery.mjs +0 -256
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { StructureKind, VariableDeclarationKind, } from "ts-morph";
|
|
2
|
+
/**
|
|
3
|
+
* Get the error type string based on client type.
|
|
4
|
+
*/
|
|
5
|
+
function getErrorType(op, ctx) {
|
|
6
|
+
const errorTypeName = `${op.capitalizedMethodName}Error`;
|
|
7
|
+
// Operations without error responses have no generated Error type
|
|
8
|
+
const errorType = ctx.modelNames.includes(errorTypeName)
|
|
9
|
+
? errorTypeName
|
|
10
|
+
: "unknown";
|
|
11
|
+
if (ctx.client === "@hey-api/client-axios") {
|
|
12
|
+
return `AxiosError<${errorType}>`;
|
|
13
|
+
}
|
|
14
|
+
return errorType;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build useMutation hook.
|
|
18
|
+
* Example:
|
|
19
|
+
* export const useAddPet = <TData = Common.AddPetMutationResult, TError = AddPetError, TQueryKey extends Array<unknown> = unknown[], TContext = unknown>(
|
|
20
|
+
* mutationKey?: TQueryKey,
|
|
21
|
+
* options?: Omit<UseMutationOptions<TData, TError, Options<AddPetData, true>, TContext>, "mutationKey" | "mutationFn">
|
|
22
|
+
* ) => useMutation<TData, TError, Options<AddPetData, true>, TContext>({
|
|
23
|
+
* mutationKey: Common.UseAddPetKeyFn(mutationKey),
|
|
24
|
+
* mutationFn: clientOptions => addPet(clientOptions) as unknown as Promise<TData>,
|
|
25
|
+
* ...options
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
export function buildUseMutationHook(op, ctx) {
|
|
29
|
+
const hookName = `use${op.capitalizedMethodName}`;
|
|
30
|
+
const errorType = getErrorType(op, ctx);
|
|
31
|
+
const dataTypeDefault = `Common.${op.capitalizedMethodName}MutationResult`;
|
|
32
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
33
|
+
? `${op.capitalizedMethodName}Data`
|
|
34
|
+
: "unknown";
|
|
35
|
+
const optionsType = `Options<${dataTypeName}, true>`;
|
|
36
|
+
const mutationFn = `clientOptions => ${op.methodName}(clientOptions) as unknown as Promise<TData>`;
|
|
37
|
+
const body = `useMutation<TData, TError, ${optionsType}, TContext>({ mutationKey: Common.Use${op.capitalizedMethodName}KeyFn(mutationKey), mutationFn: ${mutationFn}, ...options })`;
|
|
38
|
+
return {
|
|
39
|
+
kind: StructureKind.VariableStatement,
|
|
40
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
41
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
42
|
+
isExported: true,
|
|
43
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
44
|
+
declarations: [
|
|
45
|
+
{
|
|
46
|
+
name: hookName,
|
|
47
|
+
initializer: `<TData = ${dataTypeDefault}, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[], TContext = unknown>(mutationKey?: TQueryKey, options?: Omit<UseMutationOptions<TData, TError, ${optionsType}, TContext>, "mutationKey" | "mutationFn">) => ${body}`,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { StructureKind, VariableDeclarationKind, } from "ts-morph";
|
|
2
|
+
/**
|
|
3
|
+
* Get the error type string based on client type.
|
|
4
|
+
*/
|
|
5
|
+
function getErrorType(op, ctx) {
|
|
6
|
+
const errorTypeName = `${op.capitalizedMethodName}Error`;
|
|
7
|
+
// Operations without error responses have no generated Error type
|
|
8
|
+
const errorType = ctx.modelNames.includes(errorTypeName)
|
|
9
|
+
? errorTypeName
|
|
10
|
+
: "unknown";
|
|
11
|
+
if (ctx.client === "@hey-api/client-axios") {
|
|
12
|
+
return `AxiosError<${errorType}>`;
|
|
13
|
+
}
|
|
14
|
+
return errorType;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get the data type based on hook type.
|
|
18
|
+
*/
|
|
19
|
+
function getDataTypeDefault(op, hookType) {
|
|
20
|
+
const baseType = `Common.${op.capitalizedMethodName}DefaultResponse`;
|
|
21
|
+
if (hookType === "useSuspenseQuery") {
|
|
22
|
+
return `NonNullable<${baseType}>`;
|
|
23
|
+
}
|
|
24
|
+
if (hookType === "useInfiniteQuery") {
|
|
25
|
+
return `InfiniteData<${baseType}>`;
|
|
26
|
+
}
|
|
27
|
+
return baseType;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get the options type name.
|
|
31
|
+
*/
|
|
32
|
+
function getOptionsTypeName(hookType) {
|
|
33
|
+
switch (hookType) {
|
|
34
|
+
case "useSuspenseQuery":
|
|
35
|
+
return "UseSuspenseQueryOptions";
|
|
36
|
+
case "useInfiniteQuery":
|
|
37
|
+
return "UseInfiniteQueryOptions";
|
|
38
|
+
default:
|
|
39
|
+
return "UseQueryOptions";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the generated Data type name for an operation, falling back to
|
|
44
|
+
* unknown when the operation has no generated Data type.
|
|
45
|
+
*/
|
|
46
|
+
export function getDataTypeName(op, ctx) {
|
|
47
|
+
return ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
48
|
+
? `${op.capitalizedMethodName}Data`
|
|
49
|
+
: "unknown";
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build the client options parameter string.
|
|
53
|
+
*/
|
|
54
|
+
export function buildClientOptionsParam(op, ctx) {
|
|
55
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
56
|
+
? `${op.capitalizedMethodName}Data`
|
|
57
|
+
: "unknown";
|
|
58
|
+
const hasParams = op.parameters.length > 0;
|
|
59
|
+
if (!hasParams) {
|
|
60
|
+
return `clientOptions: Options<${dataTypeName}, true> = {}`;
|
|
61
|
+
}
|
|
62
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
63
|
+
return `clientOptions: Options<${dataTypeName}, true>${defaultValue}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build useQuery hook.
|
|
67
|
+
* Example:
|
|
68
|
+
* export const useFindPets = <TData = Common.FindPetsDefaultResponse, TError = FindPetsError, TQueryKey extends Array<unknown> = unknown[]>(
|
|
69
|
+
* clientOptions: Options<FindPetsData, true> = {},
|
|
70
|
+
* queryKey?: TQueryKey,
|
|
71
|
+
* options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">
|
|
72
|
+
* ) => useQuery<TData, TError>({
|
|
73
|
+
* queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey),
|
|
74
|
+
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data as TData) as TData,
|
|
75
|
+
* ...options
|
|
76
|
+
* });
|
|
77
|
+
*/
|
|
78
|
+
export function buildUseQueryHook(op, ctx) {
|
|
79
|
+
const hookName = `use${op.capitalizedMethodName}`;
|
|
80
|
+
const errorType = getErrorType(op, ctx);
|
|
81
|
+
const dataTypeDefault = getDataTypeDefault(op, "useQuery");
|
|
82
|
+
const clientOptionsParam = buildClientOptionsParam(op, ctx);
|
|
83
|
+
const hasParams = op.parameters.length > 0;
|
|
84
|
+
// Build the queryFn body
|
|
85
|
+
const callArgs = hasParams ? "{ ...clientOptions }" : "{ ...clientOptions }";
|
|
86
|
+
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data as TData) as TData`;
|
|
87
|
+
const body = `useQuery<TData, TError>({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;
|
|
88
|
+
return {
|
|
89
|
+
kind: StructureKind.VariableStatement,
|
|
90
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
91
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
92
|
+
isExported: true,
|
|
93
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
94
|
+
declarations: [
|
|
95
|
+
{
|
|
96
|
+
name: hookName,
|
|
97
|
+
initializer: `<TData = ${dataTypeDefault}, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => ${body}`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Build useSuspenseQuery hook.
|
|
104
|
+
*/
|
|
105
|
+
export function buildUseSuspenseQueryHook(op, ctx) {
|
|
106
|
+
const hookName = `use${op.capitalizedMethodName}Suspense`;
|
|
107
|
+
const errorType = getErrorType(op, ctx);
|
|
108
|
+
const dataTypeDefault = getDataTypeDefault(op, "useSuspenseQuery");
|
|
109
|
+
const clientOptionsParam = buildClientOptionsParam(op, ctx);
|
|
110
|
+
const hasParams = op.parameters.length > 0;
|
|
111
|
+
const callArgs = hasParams ? "{ ...clientOptions }" : "{ ...clientOptions }";
|
|
112
|
+
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data as TData) as TData`;
|
|
113
|
+
const body = `useSuspenseQuery<TData, TError>({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;
|
|
114
|
+
return {
|
|
115
|
+
kind: StructureKind.VariableStatement,
|
|
116
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
117
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
118
|
+
isExported: true,
|
|
119
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
120
|
+
declarations: [
|
|
121
|
+
{
|
|
122
|
+
name: hookName,
|
|
123
|
+
initializer: `<TData = ${dataTypeDefault}, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit<UseSuspenseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => ${body}`,
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Build the nested type for getNextPageParam.
|
|
130
|
+
* E.g., "meta.next" becomes "{ meta: { next: number } }"
|
|
131
|
+
*/
|
|
132
|
+
export function buildNestedNextPageType(nextPageParam) {
|
|
133
|
+
const segments = nextPageParam.split(".");
|
|
134
|
+
return segments.reduceRight((acc, segment) => {
|
|
135
|
+
return `{ ${segment}: ${acc} }`;
|
|
136
|
+
}, "number");
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Build useInfiniteQuery hook.
|
|
140
|
+
*/
|
|
141
|
+
export function buildUseInfiniteQueryHook(op, ctx) {
|
|
142
|
+
if (!op.isPaginatable) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const hookName = `use${op.capitalizedMethodName}Infinite`;
|
|
146
|
+
const errorType = getErrorType(op, ctx);
|
|
147
|
+
const baseDataType = `Common.${op.capitalizedMethodName}DefaultResponse`;
|
|
148
|
+
const dataTypeName = getDataTypeName(op, ctx);
|
|
149
|
+
// Infinite queries take a dedicated options type that excludes the page
|
|
150
|
+
// parameter — it is supplied by TanStack Query's pageParam mechanism
|
|
151
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
152
|
+
const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`;
|
|
153
|
+
// Build the queryFn with pageParam handling
|
|
154
|
+
const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number } } as Options<${dataTypeName}, true>).then(response => response.data as TData) as TData`;
|
|
155
|
+
// Build getNextPageParam with nested type
|
|
156
|
+
const nestedType = buildNestedNextPageType(ctx.nextPageParam);
|
|
157
|
+
const getNextPageParam = `getNextPageParam: (response) => (response as ${nestedType}).${ctx.nextPageParam}`;
|
|
158
|
+
// initialPageParam is a string literal
|
|
159
|
+
const infiniteOptions = `initialPageParam: "${ctx.initialPageParam}", ${getNextPageParam}`;
|
|
160
|
+
const body = `useInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
|
|
161
|
+
return {
|
|
162
|
+
kind: StructureKind.VariableStatement,
|
|
163
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
164
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
165
|
+
isExported: true,
|
|
166
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
167
|
+
declarations: [
|
|
168
|
+
{
|
|
169
|
+
name: hookName,
|
|
170
|
+
initializer: `<TData = InfiniteData<${baseDataType}>, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit<UseInfiniteQueryOptions<TData, TError>, "queryKey" | "queryFn">) => ${body}`,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Build prefetch function.
|
|
177
|
+
* Example:
|
|
178
|
+
* export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}) =>
|
|
179
|
+
* queryClient.prefetchQuery({
|
|
180
|
+
* queryKey: Common.UseFindPetsKeyFn(clientOptions),
|
|
181
|
+
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data)
|
|
182
|
+
* });
|
|
183
|
+
*/
|
|
184
|
+
export function buildPrefetchFn(op, ctx) {
|
|
185
|
+
const fnName = `prefetchUse${op.capitalizedMethodName}`;
|
|
186
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
187
|
+
? `${op.capitalizedMethodName}Data`
|
|
188
|
+
: "unknown";
|
|
189
|
+
const hasParams = op.parameters.length > 0;
|
|
190
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
191
|
+
const clientOptionsParam = hasParams
|
|
192
|
+
? `clientOptions: Options<${dataTypeName}, true>${defaultValue}`
|
|
193
|
+
: `clientOptions: Options<${dataTypeName}, true> = {}`;
|
|
194
|
+
const callArgs = "{ ...clientOptions }";
|
|
195
|
+
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data)`;
|
|
196
|
+
const body = `queryClient.prefetchQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn} })`;
|
|
197
|
+
return {
|
|
198
|
+
kind: StructureKind.VariableStatement,
|
|
199
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
200
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
201
|
+
isExported: true,
|
|
202
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
203
|
+
declarations: [
|
|
204
|
+
{
|
|
205
|
+
name: fnName,
|
|
206
|
+
initializer: `(queryClient: QueryClient, ${clientOptionsParam}) => ${body}`,
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Build ensureQueryData function.
|
|
213
|
+
* Example:
|
|
214
|
+
* export const ensureUseFindPetsData = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}) =>
|
|
215
|
+
* queryClient.ensureQueryData({
|
|
216
|
+
* queryKey: Common.UseFindPetsKeyFn(clientOptions),
|
|
217
|
+
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data)
|
|
218
|
+
* });
|
|
219
|
+
*/
|
|
220
|
+
export function buildEnsureQueryDataFn(op, ctx) {
|
|
221
|
+
const fnName = `ensureUse${op.capitalizedMethodName}Data`;
|
|
222
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
223
|
+
? `${op.capitalizedMethodName}Data`
|
|
224
|
+
: "unknown";
|
|
225
|
+
const hasParams = op.parameters.length > 0;
|
|
226
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
227
|
+
const clientOptionsParam = hasParams
|
|
228
|
+
? `clientOptions: Options<${dataTypeName}, true>${defaultValue}`
|
|
229
|
+
: `clientOptions: Options<${dataTypeName}, true> = {}`;
|
|
230
|
+
const callArgs = "{ ...clientOptions }";
|
|
231
|
+
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data)`;
|
|
232
|
+
const body = `queryClient.ensureQueryData({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn} })`;
|
|
233
|
+
return {
|
|
234
|
+
kind: StructureKind.VariableStatement,
|
|
235
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
236
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
237
|
+
isExported: true,
|
|
238
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
239
|
+
declarations: [
|
|
240
|
+
{
|
|
241
|
+
name: fnName,
|
|
242
|
+
initializer: `(queryClient: QueryClient, ${clientOptionsParam}) => ${body}`,
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { StructureKind, VariableDeclarationKind, } from "ts-morph";
|
|
2
|
+
import { buildClientOptionsParam, buildNestedNextPageType, getDataTypeName, } 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 }).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}({ ...clientOptions }).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 Options<FindPaginatedPetsData, true>).then(response => response.data),
|
|
42
|
+
* initialPageParam: 1,
|
|
43
|
+
* getNextPageParam: (response) => (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 dataTypeName = getDataTypeName(op, ctx);
|
|
52
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
53
|
+
const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`;
|
|
54
|
+
const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam } } as Options<${dataTypeName}, true>).then(response => response.data)`;
|
|
55
|
+
// Emit a numeric literal when possible so the inferred pageParam type
|
|
56
|
+
// matches what getNextPageParam returns
|
|
57
|
+
const initialPageParam = /^-?\d+$/.test(ctx.initialPageParam)
|
|
58
|
+
? ctx.initialPageParam
|
|
59
|
+
: JSON.stringify(ctx.initialPageParam);
|
|
60
|
+
const nestedType = buildNestedNextPageType(ctx.nextPageParam);
|
|
61
|
+
const getNextPageParam = `(response) => (response as ${nestedType}).${ctx.nextPageParam}`;
|
|
62
|
+
const body = `infiniteQueryOptions({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, initialPageParam: ${initialPageParam}, getNextPageParam: ${getNextPageParam} })`;
|
|
63
|
+
return {
|
|
64
|
+
kind: StructureKind.VariableStatement,
|
|
65
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
66
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
67
|
+
isExported: true,
|
|
68
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
69
|
+
declarations: [
|
|
70
|
+
{
|
|
71
|
+
name: fnName,
|
|
72
|
+
initializer: `(${clientOptionsParam}, queryKey?: Array<unknown>) => ${body}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { StructureKind, } from "ts-morph";
|
|
2
|
+
import { OpenApiRqFiles } from "../constants.mjs";
|
|
3
|
+
import { buildDefaultResponseType, buildInfiniteClientOptionsType, buildInfiniteQueryKeyConst, buildInfiniteQueryKeyFn, buildMutationKeyConst, buildMutationKeyFn, buildMutationResultType, buildQueryKeyConst, buildQueryKeyFn, buildQueryResultType, } from "./buildCommon.mjs";
|
|
4
|
+
import { buildUseMutationHook } from "./buildMutationHooks.mjs";
|
|
5
|
+
import { buildEnsureQueryDataFn, buildPrefetchFn, buildUseInfiniteQueryHook, buildUseQueryHook, buildUseSuspenseQueryHook, } from "./buildQueryHooks.mjs";
|
|
6
|
+
import { buildInfiniteQueryOptionsFn, buildQueryOptionsFn, } from "./buildQueryOptions.mjs";
|
|
7
|
+
import { buildAxiosErrorImport, buildClientImport, buildCommonImport, buildModelImport, buildQueryImport, buildQueryOptionsImport, buildServiceImport, createGenerationProject, } from "./projectFactory.mjs";
|
|
8
|
+
/**
|
|
9
|
+
* Build imports for common.ts file.
|
|
10
|
+
*/
|
|
11
|
+
function buildCommonFileImports(ctx) {
|
|
12
|
+
const imports = [
|
|
13
|
+
buildClientImport(ctx),
|
|
14
|
+
buildQueryImport(),
|
|
15
|
+
buildServiceImport(ctx),
|
|
16
|
+
];
|
|
17
|
+
const modelImport = buildModelImport(ctx);
|
|
18
|
+
if (modelImport) {
|
|
19
|
+
imports.push(modelImport);
|
|
20
|
+
}
|
|
21
|
+
if (ctx.client === "@hey-api/client-axios") {
|
|
22
|
+
imports.push(buildAxiosErrorImport());
|
|
23
|
+
}
|
|
24
|
+
return imports;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build imports for hook files (queries, suspense, infinite, prefetch, ensure).
|
|
28
|
+
*/
|
|
29
|
+
function buildHookFileImports(ctx) {
|
|
30
|
+
return [buildCommonImport(), ...buildCommonFileImports(ctx)];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generate the index.ts file content.
|
|
34
|
+
*/
|
|
35
|
+
function generateIndexFile(ctx) {
|
|
36
|
+
const project = createGenerationProject();
|
|
37
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.index}.ts`, undefined, { overwrite: true });
|
|
38
|
+
const exports = [
|
|
39
|
+
{
|
|
40
|
+
kind: StructureKind.ExportDeclaration,
|
|
41
|
+
moduleSpecifier: "./common",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
kind: StructureKind.ExportDeclaration,
|
|
45
|
+
moduleSpecifier: "./queries",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
kind: StructureKind.ExportDeclaration,
|
|
49
|
+
moduleSpecifier: "./queryOptions",
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
sourceFile.addExportDeclarations(exports);
|
|
53
|
+
return sourceFile.getFullText();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Generate the common.ts file content.
|
|
57
|
+
*/
|
|
58
|
+
function generateCommonFile(operations, ctx) {
|
|
59
|
+
const project = createGenerationProject();
|
|
60
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.common}.ts`, undefined, { overwrite: true });
|
|
61
|
+
// Add imports
|
|
62
|
+
sourceFile.addImportDeclarations(buildCommonFileImports(ctx));
|
|
63
|
+
// Group operations by HTTP method
|
|
64
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
65
|
+
const mutationOperations = operations.filter((op) => ["POST", "PUT", "PATCH", "DELETE"].includes(op.httpMethod));
|
|
66
|
+
// Add query types and keys
|
|
67
|
+
for (const op of getOperations) {
|
|
68
|
+
sourceFile.addTypeAlias(buildDefaultResponseType(op));
|
|
69
|
+
sourceFile.addTypeAlias(buildQueryResultType(op));
|
|
70
|
+
sourceFile.addVariableStatement(buildQueryKeyConst(op));
|
|
71
|
+
sourceFile.addVariableStatement(buildQueryKeyFn(op, ctx));
|
|
72
|
+
}
|
|
73
|
+
// Add dedicated infinite query types and keys for paginatable operations
|
|
74
|
+
for (const op of getOperations.filter((o) => o.isPaginatable)) {
|
|
75
|
+
sourceFile.addTypeAlias(buildInfiniteClientOptionsType(op, ctx));
|
|
76
|
+
sourceFile.addVariableStatement(buildInfiniteQueryKeyConst(op));
|
|
77
|
+
sourceFile.addVariableStatement(buildInfiniteQueryKeyFn(op));
|
|
78
|
+
}
|
|
79
|
+
// Add mutation types and keys
|
|
80
|
+
for (const op of mutationOperations) {
|
|
81
|
+
sourceFile.addTypeAlias(buildMutationResultType(op));
|
|
82
|
+
sourceFile.addVariableStatement(buildMutationKeyConst(op));
|
|
83
|
+
sourceFile.addVariableStatement(buildMutationKeyFn(op));
|
|
84
|
+
}
|
|
85
|
+
return sourceFile.getFullText();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Generate the queries.ts file content.
|
|
89
|
+
*/
|
|
90
|
+
function generateQueriesFile(operations, ctx) {
|
|
91
|
+
const project = createGenerationProject();
|
|
92
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.queries}.ts`, undefined, { overwrite: true });
|
|
93
|
+
// Add imports
|
|
94
|
+
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
|
|
95
|
+
// Group operations
|
|
96
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
97
|
+
const mutationOperations = operations.filter((op) => ["POST", "PUT", "PATCH", "DELETE"].includes(op.httpMethod));
|
|
98
|
+
// Add useQuery hooks
|
|
99
|
+
for (const op of getOperations) {
|
|
100
|
+
sourceFile.addVariableStatement(buildUseQueryHook(op, ctx));
|
|
101
|
+
}
|
|
102
|
+
// Add useMutation hooks
|
|
103
|
+
for (const op of mutationOperations) {
|
|
104
|
+
sourceFile.addVariableStatement(buildUseMutationHook(op, ctx));
|
|
105
|
+
}
|
|
106
|
+
return sourceFile.getFullText();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Generate the queryOptions.ts file content.
|
|
110
|
+
*/
|
|
111
|
+
function generateQueryOptionsFile(operations, ctx) {
|
|
112
|
+
const project = createGenerationProject();
|
|
113
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.queryOptions}.ts`, undefined, { overwrite: true });
|
|
114
|
+
// Add imports
|
|
115
|
+
const imports = [
|
|
116
|
+
buildCommonImport(),
|
|
117
|
+
buildQueryOptionsImport(),
|
|
118
|
+
buildClientImport(ctx),
|
|
119
|
+
buildServiceImport(ctx),
|
|
120
|
+
];
|
|
121
|
+
const modelImport = buildModelImport(ctx);
|
|
122
|
+
if (modelImport) {
|
|
123
|
+
imports.push(modelImport);
|
|
124
|
+
}
|
|
125
|
+
sourceFile.addImportDeclarations(imports);
|
|
126
|
+
// Only GET operations have query options
|
|
127
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
128
|
+
for (const op of getOperations) {
|
|
129
|
+
sourceFile.addVariableStatement(buildQueryOptionsFn(op, ctx));
|
|
130
|
+
}
|
|
131
|
+
for (const op of getOperations) {
|
|
132
|
+
const infiniteOptions = buildInfiniteQueryOptionsFn(op, ctx);
|
|
133
|
+
if (infiniteOptions) {
|
|
134
|
+
sourceFile.addVariableStatement(infiniteOptions);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return sourceFile.getFullText();
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Generate the suspense.ts file content.
|
|
141
|
+
*/
|
|
142
|
+
function generateSuspenseFile(operations, ctx) {
|
|
143
|
+
const project = createGenerationProject();
|
|
144
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.suspense}.ts`, undefined, { overwrite: true });
|
|
145
|
+
// Add imports
|
|
146
|
+
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
|
|
147
|
+
// Only GET operations for suspense
|
|
148
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
149
|
+
// Add useSuspenseQuery hooks
|
|
150
|
+
for (const op of getOperations) {
|
|
151
|
+
sourceFile.addVariableStatement(buildUseSuspenseQueryHook(op, ctx));
|
|
152
|
+
}
|
|
153
|
+
return sourceFile.getFullText();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Generate the infiniteQueries.ts file content.
|
|
157
|
+
*/
|
|
158
|
+
function generateInfiniteQueriesFile(operations, ctx) {
|
|
159
|
+
const project = createGenerationProject();
|
|
160
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.infiniteQueries}.ts`, undefined, { overwrite: true });
|
|
161
|
+
// Add imports
|
|
162
|
+
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
|
|
163
|
+
// Only paginatable GET operations
|
|
164
|
+
const paginatableOperations = operations.filter((op) => op.httpMethod === "GET" && op.isPaginatable);
|
|
165
|
+
// Add useInfiniteQuery hooks
|
|
166
|
+
for (const op of paginatableOperations) {
|
|
167
|
+
const hook = buildUseInfiniteQueryHook(op, ctx);
|
|
168
|
+
if (hook) {
|
|
169
|
+
sourceFile.addVariableStatement(hook);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return sourceFile.getFullText();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Generate the prefetch.ts file content.
|
|
176
|
+
*/
|
|
177
|
+
function generatePrefetchFile(operations, ctx) {
|
|
178
|
+
const project = createGenerationProject();
|
|
179
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.prefetch}.ts`, undefined, { overwrite: true });
|
|
180
|
+
// Add imports
|
|
181
|
+
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
|
|
182
|
+
// Only GET operations for prefetch
|
|
183
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
184
|
+
// Add prefetch functions
|
|
185
|
+
for (const op of getOperations) {
|
|
186
|
+
sourceFile.addVariableStatement(buildPrefetchFn(op, ctx));
|
|
187
|
+
}
|
|
188
|
+
return sourceFile.getFullText();
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Generate the ensureQueryData.ts file content.
|
|
192
|
+
*/
|
|
193
|
+
function generateEnsureQueryDataFile(operations, ctx) {
|
|
194
|
+
const project = createGenerationProject();
|
|
195
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.ensureQueryData}.ts`, undefined, { overwrite: true });
|
|
196
|
+
// Add imports
|
|
197
|
+
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
|
|
198
|
+
// Only GET operations for ensure
|
|
199
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
200
|
+
// Add ensureQueryData functions
|
|
201
|
+
for (const op of getOperations) {
|
|
202
|
+
sourceFile.addVariableStatement(buildEnsureQueryDataFn(op, ctx));
|
|
203
|
+
}
|
|
204
|
+
return sourceFile.getFullText();
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Add the generated header comment to file content.
|
|
208
|
+
*/
|
|
209
|
+
function addHeaderComment(content, version) {
|
|
210
|
+
const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
|
|
211
|
+
return comment + content;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Generate all files using ts-morph.
|
|
215
|
+
*/
|
|
216
|
+
export function generateAllFiles(operations, ctx) {
|
|
217
|
+
return [
|
|
218
|
+
{
|
|
219
|
+
name: `${OpenApiRqFiles.index}.ts`,
|
|
220
|
+
content: addHeaderComment(generateIndexFile(ctx), ctx.version),
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: `${OpenApiRqFiles.common}.ts`,
|
|
224
|
+
content: addHeaderComment(generateCommonFile(operations, ctx), ctx.version),
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: `${OpenApiRqFiles.queries}.ts`,
|
|
228
|
+
content: addHeaderComment(generateQueriesFile(operations, ctx), ctx.version),
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: `${OpenApiRqFiles.queryOptions}.ts`,
|
|
232
|
+
content: addHeaderComment(generateQueryOptionsFile(operations, ctx), ctx.version),
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: `${OpenApiRqFiles.suspense}.ts`,
|
|
236
|
+
content: addHeaderComment(generateSuspenseFile(operations, ctx), ctx.version),
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: `${OpenApiRqFiles.infiniteQueries}.ts`,
|
|
240
|
+
content: addHeaderComment(generateInfiniteQueriesFile(operations, ctx), ctx.version),
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: `${OpenApiRqFiles.prefetch}.ts`,
|
|
244
|
+
content: addHeaderComment(generatePrefetchFile(operations, ctx), ctx.version),
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: `${OpenApiRqFiles.ensureQueryData}.ts`,
|
|
248
|
+
content: addHeaderComment(generateEnsureQueryDataFile(operations, ctx), ctx.version),
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
}
|