@7nohe/openapi-react-query-codegen 2.1.0 → 3.0.0-beta.1
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/createSource.mjs +13 -107
- package/dist/generate.mjs +5 -2
- package/dist/parseOperations.mjs +122 -0
- package/dist/tsmorph/buildCommon.mjs +119 -0
- package/dist/tsmorph/buildKeys.mjs +110 -0
- package/dist/tsmorph/buildMutationHooks.mjs +51 -0
- package/dist/tsmorph/buildQueryHooks.mjs +237 -0
- package/dist/tsmorph/generateFiles.mjs +205 -0
- package/dist/tsmorph/index.mjs +5 -0
- package/dist/tsmorph/projectFactory.mjs +121 -0
- package/dist/types.mjs +1 -0
- package/package.json +7 -7
- 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,237 @@
|
|
|
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
|
+
* Build the client options parameter string.
|
|
44
|
+
*/
|
|
45
|
+
function buildClientOptionsParam(op, ctx) {
|
|
46
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
47
|
+
? `${op.capitalizedMethodName}Data`
|
|
48
|
+
: "unknown";
|
|
49
|
+
const hasParams = op.parameters.length > 0;
|
|
50
|
+
if (!hasParams) {
|
|
51
|
+
return `clientOptions: Options<${dataTypeName}, true> = {}`;
|
|
52
|
+
}
|
|
53
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
54
|
+
return `clientOptions: Options<${dataTypeName}, true>${defaultValue}`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Build useQuery hook.
|
|
58
|
+
* Example:
|
|
59
|
+
* export const useFindPets = <TData = Common.FindPetsDefaultResponse, TError = FindPetsError, TQueryKey extends Array<unknown> = unknown[]>(
|
|
60
|
+
* clientOptions: Options<FindPetsData, true> = {},
|
|
61
|
+
* queryKey?: TQueryKey,
|
|
62
|
+
* options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">
|
|
63
|
+
* ) => useQuery<TData, TError>({
|
|
64
|
+
* queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey),
|
|
65
|
+
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data as TData) as TData,
|
|
66
|
+
* ...options
|
|
67
|
+
* });
|
|
68
|
+
*/
|
|
69
|
+
export function buildUseQueryHook(op, ctx) {
|
|
70
|
+
const hookName = `use${op.capitalizedMethodName}`;
|
|
71
|
+
const errorType = getErrorType(op, ctx);
|
|
72
|
+
const dataTypeDefault = getDataTypeDefault(op, "useQuery");
|
|
73
|
+
const clientOptionsParam = buildClientOptionsParam(op, ctx);
|
|
74
|
+
const hasParams = op.parameters.length > 0;
|
|
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`;
|
|
78
|
+
const body = `useQuery<TData, TError>({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;
|
|
79
|
+
return {
|
|
80
|
+
kind: StructureKind.VariableStatement,
|
|
81
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
82
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
83
|
+
isExported: true,
|
|
84
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
85
|
+
declarations: [
|
|
86
|
+
{
|
|
87
|
+
name: hookName,
|
|
88
|
+
initializer: `<TData = ${dataTypeDefault}, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => ${body}`,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build useSuspenseQuery hook.
|
|
95
|
+
*/
|
|
96
|
+
export function buildUseSuspenseQueryHook(op, ctx) {
|
|
97
|
+
const hookName = `use${op.capitalizedMethodName}Suspense`;
|
|
98
|
+
const errorType = getErrorType(op, ctx);
|
|
99
|
+
const dataTypeDefault = getDataTypeDefault(op, "useSuspenseQuery");
|
|
100
|
+
const clientOptionsParam = buildClientOptionsParam(op, ctx);
|
|
101
|
+
const hasParams = op.parameters.length > 0;
|
|
102
|
+
const callArgs = hasParams ? "{ ...clientOptions }" : "{ ...clientOptions }";
|
|
103
|
+
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data as TData) as TData`;
|
|
104
|
+
const body = `useSuspenseQuery<TData, TError>({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;
|
|
105
|
+
return {
|
|
106
|
+
kind: StructureKind.VariableStatement,
|
|
107
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
108
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
109
|
+
isExported: true,
|
|
110
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
111
|
+
declarations: [
|
|
112
|
+
{
|
|
113
|
+
name: hookName,
|
|
114
|
+
initializer: `<TData = ${dataTypeDefault}, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit<UseSuspenseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => ${body}`,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build the nested type for getNextPageParam.
|
|
121
|
+
* E.g., "meta.next" becomes "{ meta: { next: number } }"
|
|
122
|
+
*/
|
|
123
|
+
function buildNestedNextPageType(nextPageParam) {
|
|
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) {
|
|
133
|
+
if (!op.isPaginatable) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const hookName = `use${op.capitalizedMethodName}Infinite`;
|
|
137
|
+
const errorType = getErrorType(op, ctx);
|
|
138
|
+
const baseDataType = `Common.${op.capitalizedMethodName}DefaultResponse`;
|
|
139
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
140
|
+
? `${op.capitalizedMethodName}Data`
|
|
141
|
+
: "unknown";
|
|
142
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
143
|
+
const clientOptionsParam = `clientOptions: Options<${dataTypeName}, true>${defaultValue}`;
|
|
144
|
+
// Build the queryFn with pageParam handling
|
|
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 })`;
|
|
152
|
+
return {
|
|
153
|
+
kind: StructureKind.VariableStatement,
|
|
154
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
155
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
156
|
+
isExported: true,
|
|
157
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
158
|
+
declarations: [
|
|
159
|
+
{
|
|
160
|
+
name: hookName,
|
|
161
|
+
initializer: `<TData = InfiniteData<${baseDataType}>, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit<UseInfiniteQueryOptions<TData, TError>, "queryKey" | "queryFn">) => ${body}`,
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Build prefetch function.
|
|
168
|
+
* Example:
|
|
169
|
+
* export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}) =>
|
|
170
|
+
* queryClient.prefetchQuery({
|
|
171
|
+
* queryKey: Common.UseFindPetsKeyFn(clientOptions),
|
|
172
|
+
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data)
|
|
173
|
+
* });
|
|
174
|
+
*/
|
|
175
|
+
export function buildPrefetchFn(op, ctx) {
|
|
176
|
+
const fnName = `prefetchUse${op.capitalizedMethodName}`;
|
|
177
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
178
|
+
? `${op.capitalizedMethodName}Data`
|
|
179
|
+
: "unknown";
|
|
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} })`;
|
|
188
|
+
return {
|
|
189
|
+
kind: StructureKind.VariableStatement,
|
|
190
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
191
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
192
|
+
isExported: true,
|
|
193
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
194
|
+
declarations: [
|
|
195
|
+
{
|
|
196
|
+
name: fnName,
|
|
197
|
+
initializer: `(queryClient: QueryClient, ${clientOptionsParam}) => ${body}`,
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Build ensureQueryData function.
|
|
204
|
+
* Example:
|
|
205
|
+
* export const ensureUseFindPetsData = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}) =>
|
|
206
|
+
* queryClient.ensureQueryData({
|
|
207
|
+
* queryKey: Common.UseFindPetsKeyFn(clientOptions),
|
|
208
|
+
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data)
|
|
209
|
+
* });
|
|
210
|
+
*/
|
|
211
|
+
export function buildEnsureQueryDataFn(op, ctx) {
|
|
212
|
+
const fnName = `ensureUse${op.capitalizedMethodName}Data`;
|
|
213
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
214
|
+
? `${op.capitalizedMethodName}Data`
|
|
215
|
+
: "unknown";
|
|
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} })`;
|
|
224
|
+
return {
|
|
225
|
+
kind: StructureKind.VariableStatement,
|
|
226
|
+
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
|
|
227
|
+
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
|
|
228
|
+
isExported: true,
|
|
229
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
230
|
+
declarations: [
|
|
231
|
+
{
|
|
232
|
+
name: fnName,
|
|
233
|
+
initializer: `(queryClient: QueryClient, ${clientOptionsParam}) => ${body}`,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { StructureKind, } from "ts-morph";
|
|
2
|
+
import { OpenApiRqFiles } from "../constants.mjs";
|
|
3
|
+
import { buildDefaultResponseType, 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 { buildAxiosErrorImport, buildClientImport, buildCommonImport, buildModelImport, buildQueryImport, buildServiceImport, createGenerationProject, } from "./projectFactory.mjs";
|
|
7
|
+
/**
|
|
8
|
+
* Build imports for common.ts file.
|
|
9
|
+
*/
|
|
10
|
+
function buildCommonFileImports(ctx) {
|
|
11
|
+
const imports = [
|
|
12
|
+
buildClientImport(ctx),
|
|
13
|
+
buildQueryImport(),
|
|
14
|
+
buildServiceImport(ctx),
|
|
15
|
+
];
|
|
16
|
+
const modelImport = buildModelImport(ctx);
|
|
17
|
+
if (modelImport) {
|
|
18
|
+
imports.push(modelImport);
|
|
19
|
+
}
|
|
20
|
+
if (ctx.client === "@hey-api/client-axios") {
|
|
21
|
+
imports.push(buildAxiosErrorImport());
|
|
22
|
+
}
|
|
23
|
+
return imports;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build imports for hook files (queries, suspense, infinite, prefetch, ensure).
|
|
27
|
+
*/
|
|
28
|
+
function buildHookFileImports(ctx) {
|
|
29
|
+
return [buildCommonImport(), ...buildCommonFileImports(ctx)];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate the index.ts file content.
|
|
33
|
+
*/
|
|
34
|
+
function generateIndexFile(ctx) {
|
|
35
|
+
const project = createGenerationProject();
|
|
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();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generate the common.ts file content.
|
|
52
|
+
*/
|
|
53
|
+
function generateCommonFile(operations, ctx) {
|
|
54
|
+
const project = createGenerationProject();
|
|
55
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.common}.ts`, undefined, { overwrite: true });
|
|
56
|
+
// Add imports
|
|
57
|
+
sourceFile.addImportDeclarations(buildCommonFileImports(ctx));
|
|
58
|
+
// Group operations by HTTP method
|
|
59
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
60
|
+
const mutationOperations = operations.filter((op) => ["POST", "PUT", "PATCH", "DELETE"].includes(op.httpMethod));
|
|
61
|
+
// Add query types and keys
|
|
62
|
+
for (const op of getOperations) {
|
|
63
|
+
sourceFile.addTypeAlias(buildDefaultResponseType(op));
|
|
64
|
+
sourceFile.addTypeAlias(buildQueryResultType(op));
|
|
65
|
+
sourceFile.addVariableStatement(buildQueryKeyConst(op));
|
|
66
|
+
sourceFile.addVariableStatement(buildQueryKeyFn(op, ctx));
|
|
67
|
+
}
|
|
68
|
+
// Add mutation types and keys
|
|
69
|
+
for (const op of mutationOperations) {
|
|
70
|
+
sourceFile.addTypeAlias(buildMutationResultType(op));
|
|
71
|
+
sourceFile.addVariableStatement(buildMutationKeyConst(op));
|
|
72
|
+
sourceFile.addVariableStatement(buildMutationKeyFn(op));
|
|
73
|
+
}
|
|
74
|
+
return sourceFile.getFullText();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Generate the queries.ts file content.
|
|
78
|
+
*/
|
|
79
|
+
function generateQueriesFile(operations, ctx) {
|
|
80
|
+
const project = createGenerationProject();
|
|
81
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.queries}.ts`, undefined, { overwrite: true });
|
|
82
|
+
// Add imports
|
|
83
|
+
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
|
|
84
|
+
// Group operations
|
|
85
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
86
|
+
const mutationOperations = operations.filter((op) => ["POST", "PUT", "PATCH", "DELETE"].includes(op.httpMethod));
|
|
87
|
+
// Add useQuery hooks
|
|
88
|
+
for (const op of getOperations) {
|
|
89
|
+
sourceFile.addVariableStatement(buildUseQueryHook(op, ctx));
|
|
90
|
+
}
|
|
91
|
+
// Add useMutation hooks
|
|
92
|
+
for (const op of mutationOperations) {
|
|
93
|
+
sourceFile.addVariableStatement(buildUseMutationHook(op, ctx));
|
|
94
|
+
}
|
|
95
|
+
return sourceFile.getFullText();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate the suspense.ts file content.
|
|
99
|
+
*/
|
|
100
|
+
function generateSuspenseFile(operations, ctx) {
|
|
101
|
+
const project = createGenerationProject();
|
|
102
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.suspense}.ts`, undefined, { overwrite: true });
|
|
103
|
+
// Add imports
|
|
104
|
+
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
|
|
105
|
+
// Only GET operations for suspense
|
|
106
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
107
|
+
// Add useSuspenseQuery hooks
|
|
108
|
+
for (const op of getOperations) {
|
|
109
|
+
sourceFile.addVariableStatement(buildUseSuspenseQueryHook(op, ctx));
|
|
110
|
+
}
|
|
111
|
+
return sourceFile.getFullText();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Generate the infiniteQueries.ts file content.
|
|
115
|
+
*/
|
|
116
|
+
function generateInfiniteQueriesFile(operations, ctx) {
|
|
117
|
+
const project = createGenerationProject();
|
|
118
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.infiniteQueries}.ts`, undefined, { overwrite: true });
|
|
119
|
+
// Add imports
|
|
120
|
+
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
|
|
121
|
+
// Only paginatable GET operations
|
|
122
|
+
const paginatableOperations = operations.filter((op) => op.httpMethod === "GET" && op.isPaginatable);
|
|
123
|
+
// Add useInfiniteQuery hooks
|
|
124
|
+
for (const op of paginatableOperations) {
|
|
125
|
+
const hook = buildUseInfiniteQueryHook(op, ctx);
|
|
126
|
+
if (hook) {
|
|
127
|
+
sourceFile.addVariableStatement(hook);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return sourceFile.getFullText();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Generate the prefetch.ts file content.
|
|
134
|
+
*/
|
|
135
|
+
function generatePrefetchFile(operations, ctx) {
|
|
136
|
+
const project = createGenerationProject();
|
|
137
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.prefetch}.ts`, undefined, { overwrite: true });
|
|
138
|
+
// Add imports
|
|
139
|
+
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
|
|
140
|
+
// Only GET operations for prefetch
|
|
141
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
142
|
+
// Add prefetch functions
|
|
143
|
+
for (const op of getOperations) {
|
|
144
|
+
sourceFile.addVariableStatement(buildPrefetchFn(op, ctx));
|
|
145
|
+
}
|
|
146
|
+
return sourceFile.getFullText();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Generate the ensureQueryData.ts file content.
|
|
150
|
+
*/
|
|
151
|
+
function generateEnsureQueryDataFile(operations, ctx) {
|
|
152
|
+
const project = createGenerationProject();
|
|
153
|
+
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.ensureQueryData}.ts`, undefined, { overwrite: true });
|
|
154
|
+
// Add imports
|
|
155
|
+
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
|
|
156
|
+
// Only GET operations for ensure
|
|
157
|
+
const getOperations = operations.filter((op) => op.httpMethod === "GET");
|
|
158
|
+
// Add ensureQueryData functions
|
|
159
|
+
for (const op of getOperations) {
|
|
160
|
+
sourceFile.addVariableStatement(buildEnsureQueryDataFn(op, ctx));
|
|
161
|
+
}
|
|
162
|
+
return sourceFile.getFullText();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Add the generated header comment to file content.
|
|
166
|
+
*/
|
|
167
|
+
function addHeaderComment(content, version) {
|
|
168
|
+
const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
|
|
169
|
+
return comment + content;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Generate all files using ts-morph.
|
|
173
|
+
*/
|
|
174
|
+
export function generateAllFiles(operations, ctx) {
|
|
175
|
+
return [
|
|
176
|
+
{
|
|
177
|
+
name: `${OpenApiRqFiles.index}.ts`,
|
|
178
|
+
content: addHeaderComment(generateIndexFile(ctx), ctx.version),
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: `${OpenApiRqFiles.common}.ts`,
|
|
182
|
+
content: addHeaderComment(generateCommonFile(operations, ctx), ctx.version),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: `${OpenApiRqFiles.queries}.ts`,
|
|
186
|
+
content: addHeaderComment(generateQueriesFile(operations, ctx), ctx.version),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: `${OpenApiRqFiles.suspense}.ts`,
|
|
190
|
+
content: addHeaderComment(generateSuspenseFile(operations, ctx), ctx.version),
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: `${OpenApiRqFiles.infiniteQueries}.ts`,
|
|
194
|
+
content: addHeaderComment(generateInfiniteQueriesFile(operations, ctx), ctx.version),
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: `${OpenApiRqFiles.prefetch}.ts`,
|
|
198
|
+
content: addHeaderComment(generatePrefetchFile(operations, ctx), ctx.version),
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: `${OpenApiRqFiles.ensureQueryData}.ts`,
|
|
202
|
+
content: addHeaderComment(generateEnsureQueryDataFile(operations, ctx), ctx.version),
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { IndentationText, NewLineKind, Project, QuoteKind, StructureKind, } from "ts-morph";
|
|
2
|
+
/**
|
|
3
|
+
* Create a shared ts-morph Project for code generation.
|
|
4
|
+
* Uses consistent formatting settings to match existing output.
|
|
5
|
+
*/
|
|
6
|
+
export function createGenerationProject() {
|
|
7
|
+
return new Project({
|
|
8
|
+
useInMemoryFileSystem: true,
|
|
9
|
+
compilerOptions: {
|
|
10
|
+
strict: true,
|
|
11
|
+
},
|
|
12
|
+
manipulationSettings: {
|
|
13
|
+
indentationText: IndentationText.TwoSpaces,
|
|
14
|
+
newLineKind: NewLineKind.LineFeed,
|
|
15
|
+
quoteKind: QuoteKind.Double,
|
|
16
|
+
useTrailingCommas: true,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build import structure for the Options type.
|
|
22
|
+
* sdk.gen re-exports Options extended with `client` and `meta`, which the
|
|
23
|
+
* base client Options lacks; hooks must accept those properties.
|
|
24
|
+
*/
|
|
25
|
+
export function buildClientImport(_ctx) {
|
|
26
|
+
return {
|
|
27
|
+
kind: StructureKind.ImportDeclaration,
|
|
28
|
+
moduleSpecifier: "../requests/sdk.gen",
|
|
29
|
+
namedImports: [{ name: "Options", isTypeOnly: true }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build import structure for TanStack Query.
|
|
34
|
+
*/
|
|
35
|
+
export function buildQueryImport() {
|
|
36
|
+
return {
|
|
37
|
+
kind: StructureKind.ImportDeclaration,
|
|
38
|
+
moduleSpecifier: "@tanstack/react-query",
|
|
39
|
+
namedImports: [
|
|
40
|
+
{ name: "QueryClient", isTypeOnly: true },
|
|
41
|
+
{ name: "useQuery" },
|
|
42
|
+
{ name: "useSuspenseQuery" },
|
|
43
|
+
{ name: "useInfiniteQuery" },
|
|
44
|
+
{ name: "useMutation" },
|
|
45
|
+
{ name: "UseQueryResult" },
|
|
46
|
+
{ name: "UseQueryOptions" },
|
|
47
|
+
{ name: "UseInfiniteQueryOptions" },
|
|
48
|
+
{ name: "UseMutationOptions" },
|
|
49
|
+
{ name: "UseMutationResult" },
|
|
50
|
+
{ name: "UseSuspenseQueryOptions" },
|
|
51
|
+
{ name: "InfiniteData" },
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build import structure for services.
|
|
57
|
+
*/
|
|
58
|
+
export function buildServiceImport(ctx) {
|
|
59
|
+
return {
|
|
60
|
+
kind: StructureKind.ImportDeclaration,
|
|
61
|
+
moduleSpecifier: "../requests/sdk.gen",
|
|
62
|
+
namedImports: ctx.serviceNames.map((name) => ({ name })),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build import structure for models.
|
|
67
|
+
*/
|
|
68
|
+
export function buildModelImport(ctx) {
|
|
69
|
+
if (ctx.modelNames.length === 0) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
kind: StructureKind.ImportDeclaration,
|
|
74
|
+
moduleSpecifier: "../requests/types.gen",
|
|
75
|
+
namedImports: ctx.modelNames.map((name) => ({ name })),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build import structure for axios error type.
|
|
80
|
+
*/
|
|
81
|
+
export function buildAxiosErrorImport() {
|
|
82
|
+
return {
|
|
83
|
+
kind: StructureKind.ImportDeclaration,
|
|
84
|
+
moduleSpecifier: "axios",
|
|
85
|
+
namedImports: [{ name: "AxiosError" }],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build import for Common namespace.
|
|
90
|
+
*/
|
|
91
|
+
export function buildCommonImport() {
|
|
92
|
+
return {
|
|
93
|
+
kind: StructureKind.ImportDeclaration,
|
|
94
|
+
moduleSpecifier: "./common",
|
|
95
|
+
namespaceImport: "Common",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build all imports needed for the common file.
|
|
100
|
+
*/
|
|
101
|
+
export function buildCommonFileImports(ctx) {
|
|
102
|
+
const imports = [
|
|
103
|
+
buildClientImport(ctx),
|
|
104
|
+
buildQueryImport(),
|
|
105
|
+
buildServiceImport(ctx),
|
|
106
|
+
];
|
|
107
|
+
const modelImport = buildModelImport(ctx);
|
|
108
|
+
if (modelImport) {
|
|
109
|
+
imports.push(modelImport);
|
|
110
|
+
}
|
|
111
|
+
if (ctx.client === "@hey-api/client-axios") {
|
|
112
|
+
imports.push(buildAxiosErrorImport());
|
|
113
|
+
}
|
|
114
|
+
return imports;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Build all imports needed for hook files (queries, suspense, infinite).
|
|
118
|
+
*/
|
|
119
|
+
export function buildHookFileImports(ctx) {
|
|
120
|
+
return [buildCommonImport(), ...buildCommonFileImports(ctx)];
|
|
121
|
+
}
|
package/dist/types.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7nohe/openapi-react-query-codegen",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-beta.1",
|
|
4
4
|
"description": "OpenAPI React Query Codegen",
|
|
5
5
|
"bin": {
|
|
6
6
|
"openapi-rq": "dist/cli.mjs"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"author": "Daiki Urata (@7nohe)",
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@hey-api/openapi-ts": "0.
|
|
42
|
+
"@hey-api/openapi-ts": "0.99.0",
|
|
43
43
|
"cross-spawn": "^7.0.3"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
@@ -51,17 +51,17 @@
|
|
|
51
51
|
"commander": "^12.0.0",
|
|
52
52
|
"lefthook": "^1.6.10",
|
|
53
53
|
"rimraf": "^5.0.5",
|
|
54
|
-
"ts-morph": "^
|
|
55
|
-
"typescript": "^
|
|
54
|
+
"ts-morph": "^28.0.0",
|
|
55
|
+
"typescript": "^6.0.3",
|
|
56
56
|
"vitest": "^1.5.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"commander": "12.x",
|
|
60
|
-
"ts-morph": "
|
|
61
|
-
"typescript": "5.x"
|
|
60
|
+
"ts-morph": "28.x",
|
|
61
|
+
"typescript": "5.x || 6.x"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
64
|
-
"node": ">=
|
|
64
|
+
"node": ">=22.18.0",
|
|
65
65
|
"pnpm": ">=9"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|