@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
package/dist/common.mjs
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { ArrowFunction } from "ts-morph";
|
|
3
|
+
import { ArrowFunction, } from "ts-morph";
|
|
4
4
|
import ts from "typescript";
|
|
5
5
|
import { queriesOutputPath, requestsOutputPath } from "./constants.mjs";
|
|
6
|
-
export const TData = ts.factory.createIdentifier("TData");
|
|
7
|
-
export const TError = ts.factory.createIdentifier("TError");
|
|
8
|
-
export const TContext = ts.factory.createIdentifier("TContext");
|
|
9
|
-
export const EqualsOrGreaterThanToken = ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken);
|
|
10
|
-
export const QuestionToken = ts.factory.createToken(ts.SyntaxKind.QuestionToken);
|
|
11
|
-
export const queryKeyGenericType = ts.factory.createTypeReferenceNode("TQueryKey");
|
|
12
|
-
export const queryKeyConstraint = ts.factory.createTypeReferenceNode("Array", [
|
|
13
|
-
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
|
|
14
|
-
]);
|
|
15
6
|
export const capitalizeFirstLetter = (str) => {
|
|
16
7
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
17
8
|
};
|
|
@@ -149,67 +140,3 @@ export function buildRequestsOutputPath(outputPath) {
|
|
|
149
140
|
export function buildQueriesOutputPath(outputPath) {
|
|
150
141
|
return path.join(outputPath, queriesOutputPath);
|
|
151
142
|
}
|
|
152
|
-
export function getQueryKeyFnName(queryKey) {
|
|
153
|
-
return `${capitalizeFirstLetter(queryKey)}Fn`;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Create QueryKey/MutationKey exports
|
|
157
|
-
*/
|
|
158
|
-
export function createQueryKeyExport({ methodName, queryKey, }) {
|
|
159
|
-
return ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
160
|
-
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(queryKey), undefined, undefined, ts.factory.createStringLiteral(`${capitalizeFirstLetter(methodName)}`)),
|
|
161
|
-
], ts.NodeFlags.Const));
|
|
162
|
-
}
|
|
163
|
-
export function createQueryKeyFnExport(queryKey, method, type = "query", modelNames = []) {
|
|
164
|
-
// Mutation keys don't require clientOptions
|
|
165
|
-
const params = type === "query"
|
|
166
|
-
? getRequestParamFromMethod(method, undefined, modelNames)
|
|
167
|
-
: null;
|
|
168
|
-
// override key is used to allow the user to override the the queryKey values
|
|
169
|
-
const overrideKey = ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier(type === "query" ? "queryKey" : "mutationKey"), QuestionToken, ts.factory.createTypeReferenceNode("Array<unknown>", []));
|
|
170
|
-
return ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
171
|
-
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(getQueryKeyFnName(queryKey)), undefined, undefined, ts.factory.createArrowFunction(undefined, undefined, params ? [params, overrideKey] : [overrideKey], undefined, EqualsOrGreaterThanToken, type === "query"
|
|
172
|
-
? queryKeyFn(queryKey, method)
|
|
173
|
-
: mutationKeyFn(queryKey))),
|
|
174
|
-
], ts.NodeFlags.Const));
|
|
175
|
-
}
|
|
176
|
-
function queryKeyFn(queryKey, method) {
|
|
177
|
-
return ts.factory.createArrayLiteralExpression([
|
|
178
|
-
ts.factory.createIdentifier(queryKey),
|
|
179
|
-
ts.factory.createSpreadElement(ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression(ts.factory.createIdentifier("queryKey"), ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), getVariableArrowFunctionParameters(method)
|
|
180
|
-
? // [...clientOptions]
|
|
181
|
-
ts.factory.createArrayLiteralExpression([
|
|
182
|
-
ts.factory.createIdentifier("clientOptions"),
|
|
183
|
-
])
|
|
184
|
-
: // []
|
|
185
|
-
ts.factory.createArrayLiteralExpression()))),
|
|
186
|
-
], false);
|
|
187
|
-
}
|
|
188
|
-
function mutationKeyFn(mutationKey) {
|
|
189
|
-
return ts.factory.createArrayLiteralExpression([
|
|
190
|
-
ts.factory.createIdentifier(mutationKey),
|
|
191
|
-
ts.factory.createSpreadElement(ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression(ts.factory.createIdentifier("mutationKey"), ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), ts.factory.createArrayLiteralExpression()))),
|
|
192
|
-
], false);
|
|
193
|
-
}
|
|
194
|
-
export function getRequestParamFromMethod(method, pageParam, modelNames = []) {
|
|
195
|
-
const sdkParams = getVariableArrowFunctionParameters(method);
|
|
196
|
-
if (!sdkParams.length) {
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
const methodName = getNameFromVariable(method);
|
|
200
|
-
// Use the SDK function's parameter optionality as the authoritative check.
|
|
201
|
-
// Generic types like Options<XData, ThrowOnError> may not resolve correctly
|
|
202
|
-
// via extractPropertiesFromObjectParam for type alias properties (path, url).
|
|
203
|
-
const areAllPropertiesOptional = sdkParams[0].isOptional();
|
|
204
|
-
return ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("clientOptions"), undefined, ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Options"), [
|
|
205
|
-
ts.factory.createTypeReferenceNode(modelNames.includes(`${capitalizeFirstLetter(methodName)}Data`)
|
|
206
|
-
? `${capitalizeFirstLetter(methodName)}Data`
|
|
207
|
-
: "unknown"),
|
|
208
|
-
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("true")),
|
|
209
|
-
]),
|
|
210
|
-
// if all params are optional, we create an empty object literal
|
|
211
|
-
// so the hook can be called without any parameters
|
|
212
|
-
areAllPropertiesOptional
|
|
213
|
-
? ts.factory.createObjectLiteralExpression()
|
|
214
|
-
: undefined);
|
|
215
|
-
}
|
package/dist/constants.mjs
CHANGED
package/dist/createSource.mjs
CHANGED
|
@@ -1,115 +1,21 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { Project } from "ts-morph";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
3
|
+
import { buildGenerationContext, parseOperations } from "./parseOperations.mjs";
|
|
4
|
+
import { generateAllFiles } from "./tsmorph/index.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* Create source files using ts-morph based generation.
|
|
7
|
+
*/
|
|
8
|
+
export const createSource = async ({ outputPath, client, version, pageParam, nextPageParam, initialPageParam, }) => {
|
|
9
|
+
// Initialize ts-morph project to read the generated OpenAPI client
|
|
9
10
|
const project = new Project({
|
|
10
|
-
// Optionally specify compiler options, tsconfig.json, in-memory file system, and more here.
|
|
11
|
-
// If you initialize with a tsconfig.json, then it will automatically populate the project
|
|
12
|
-
// with the associated source files.
|
|
13
|
-
// Read more: https://ts-morph.com/setup/
|
|
14
11
|
skipAddingFilesFromTsConfig: true,
|
|
15
12
|
});
|
|
16
13
|
const sourceFiles = join(process.cwd(), outputPath);
|
|
17
14
|
project.addSourceFilesAtPaths(`${sourceFiles}/**/*`);
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
service,
|
|
25
|
-
client,
|
|
26
|
-
project,
|
|
27
|
-
pageParam,
|
|
28
|
-
nextPageParam,
|
|
29
|
-
initialPageParam,
|
|
30
|
-
});
|
|
31
|
-
const commonSource = ts.factory.createSourceFile([...imports, ...exports.allCommon], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
32
|
-
const commonImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, ts.factory.createIdentifier("* as Common"), undefined), ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`), undefined);
|
|
33
|
-
const commonExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`), undefined);
|
|
34
|
-
const queriesExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(`./${OpenApiRqFiles.queries}`), undefined);
|
|
35
|
-
const mainSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.mainExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
36
|
-
const infiniteQueriesSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.infiniteQueriesExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
37
|
-
const suspenseSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.suspenseExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
38
|
-
const indexSource = ts.factory.createSourceFile([commonExport, queriesExport], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
39
|
-
const prefetchSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.allPrefetchExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
40
|
-
const ensureSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.allEnsures], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
41
|
-
return {
|
|
42
|
-
commonSource,
|
|
43
|
-
infiniteQueriesSource,
|
|
44
|
-
mainSource,
|
|
45
|
-
suspenseSource,
|
|
46
|
-
indexSource,
|
|
47
|
-
prefetchSource,
|
|
48
|
-
ensureSource,
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
export const createSource = async ({ outputPath, client, version, pageParam, nextPageParam, initialPageParam, }) => {
|
|
52
|
-
const queriesFile = ts.createSourceFile(`${OpenApiRqFiles.queries}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
53
|
-
const infiniteQueriesFile = ts.createSourceFile(`${OpenApiRqFiles.infiniteQueries}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
54
|
-
const commonFile = ts.createSourceFile(`${OpenApiRqFiles.common}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
55
|
-
const suspenseFile = ts.createSourceFile(`${OpenApiRqFiles.suspense}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
56
|
-
const indexFile = ts.createSourceFile(`${OpenApiRqFiles.index}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
57
|
-
const prefetchFile = ts.createSourceFile(`${OpenApiRqFiles.prefetch}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
58
|
-
const ensureQueryDataFile = ts.createSourceFile(`${OpenApiRqFiles.ensureQueryData}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
59
|
-
const printer = ts.createPrinter({
|
|
60
|
-
newLine: ts.NewLineKind.LineFeed,
|
|
61
|
-
removeComments: false,
|
|
62
|
-
});
|
|
63
|
-
const { commonSource, mainSource, infiniteQueriesSource, suspenseSource, indexSource, prefetchSource, ensureSource, } = await createSourceFile({
|
|
64
|
-
outputPath,
|
|
65
|
-
client,
|
|
66
|
-
pageParam,
|
|
67
|
-
nextPageParam,
|
|
68
|
-
initialPageParam,
|
|
69
|
-
});
|
|
70
|
-
const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
|
|
71
|
-
const commonResult = comment +
|
|
72
|
-
printer.printNode(ts.EmitHint.Unspecified, commonSource, commonFile);
|
|
73
|
-
const mainResult = comment +
|
|
74
|
-
printer.printNode(ts.EmitHint.Unspecified, mainSource, queriesFile);
|
|
75
|
-
const infiniteQueriesResult = comment +
|
|
76
|
-
printer.printNode(ts.EmitHint.Unspecified, infiniteQueriesSource, infiniteQueriesFile);
|
|
77
|
-
const suspenseResult = comment +
|
|
78
|
-
printer.printNode(ts.EmitHint.Unspecified, suspenseSource, suspenseFile);
|
|
79
|
-
const indexResult = comment +
|
|
80
|
-
printer.printNode(ts.EmitHint.Unspecified, indexSource, indexFile);
|
|
81
|
-
const prefetchResult = comment +
|
|
82
|
-
printer.printNode(ts.EmitHint.Unspecified, prefetchSource, prefetchFile);
|
|
83
|
-
const enqureResult = comment +
|
|
84
|
-
printer.printNode(ts.EmitHint.Unspecified, ensureSource, ensureQueryDataFile);
|
|
85
|
-
return [
|
|
86
|
-
{
|
|
87
|
-
name: `${OpenApiRqFiles.index}.ts`,
|
|
88
|
-
content: indexResult,
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
name: `${OpenApiRqFiles.common}.ts`,
|
|
92
|
-
content: commonResult,
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
name: `${OpenApiRqFiles.infiniteQueries}.ts`,
|
|
96
|
-
content: infiniteQueriesResult,
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: `${OpenApiRqFiles.queries}.ts`,
|
|
100
|
-
content: mainResult,
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: `${OpenApiRqFiles.suspense}.ts`,
|
|
104
|
-
content: suspenseResult,
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
name: `${OpenApiRqFiles.prefetch}.ts`,
|
|
108
|
-
content: prefetchResult,
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
name: `${OpenApiRqFiles.ensureQueryData}.ts`,
|
|
112
|
-
content: enqureResult,
|
|
113
|
-
},
|
|
114
|
-
];
|
|
15
|
+
// Parse operations from the service file
|
|
16
|
+
const operations = await parseOperations(project, pageParam);
|
|
17
|
+
// Build generation context
|
|
18
|
+
const ctx = buildGenerationContext(project, client, pageParam, nextPageParam, initialPageParam, version);
|
|
19
|
+
// Generate all files using ts-morph
|
|
20
|
+
return generateAllFiles(operations, ctx);
|
|
115
21
|
};
|
package/dist/generate.mjs
CHANGED
|
@@ -54,7 +54,7 @@ export async function generate(options, version) {
|
|
|
54
54
|
await writeFile(path.join(openApiOutputPath, "services.gen.ts"), shimContent);
|
|
55
55
|
const source = await createSource({
|
|
56
56
|
outputPath: openApiOutputPath,
|
|
57
|
-
client:
|
|
57
|
+
client: clientPlugin,
|
|
58
58
|
version,
|
|
59
59
|
pageParam: formattedOptions.pageParam,
|
|
60
60
|
nextPageParam: formattedOptions.nextPageParam,
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromVariable, getShortType, getVariableArrowFunctionParameters, } from "./common.mjs";
|
|
3
|
+
import { modelsFileName, serviceFileName } from "./constants.mjs";
|
|
4
|
+
import { getServices } from "./service.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* Extract parameter information from a method's variable declaration.
|
|
7
|
+
*/
|
|
8
|
+
function extractParameters(method, pageParam) {
|
|
9
|
+
const arrowParams = getVariableArrowFunctionParameters(method);
|
|
10
|
+
if (!arrowParams.length) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
return arrowParams.flatMap((param) => {
|
|
14
|
+
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
15
|
+
return paramNodes
|
|
16
|
+
.filter((p) => p.name !== pageParam)
|
|
17
|
+
.map((refParam) => ({
|
|
18
|
+
name: refParam.name,
|
|
19
|
+
typeName: getShortType(refParam.type?.getText() ?? ""),
|
|
20
|
+
optional: refParam.optional,
|
|
21
|
+
}));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get paginatable methods by checking if their Data type has the pageParam in query property.
|
|
26
|
+
* Uses TypeScript compiler API for accurate AST traversal.
|
|
27
|
+
*/
|
|
28
|
+
function getPaginatableMethods(project, pageParam) {
|
|
29
|
+
const modelsFile = project
|
|
30
|
+
.getSourceFiles()
|
|
31
|
+
.find((sf) => sf.getFilePath().includes(modelsFileName));
|
|
32
|
+
if (!modelsFile)
|
|
33
|
+
return [];
|
|
34
|
+
const paginatableMethods = [];
|
|
35
|
+
const modelDeclarations = modelsFile.getExportedDeclarations();
|
|
36
|
+
const entries = modelDeclarations.entries();
|
|
37
|
+
for (const [key, value] of entries) {
|
|
38
|
+
// Check if this is a *Data type (e.g., FindPetsData)
|
|
39
|
+
if (!key.endsWith("Data"))
|
|
40
|
+
continue;
|
|
41
|
+
const node = value[0].compilerNode;
|
|
42
|
+
if (!ts.isTypeAliasDeclaration(node))
|
|
43
|
+
continue;
|
|
44
|
+
const typeAliasDeclaration = node.type;
|
|
45
|
+
if (typeAliasDeclaration.kind !== ts.SyntaxKind.TypeLiteral)
|
|
46
|
+
continue;
|
|
47
|
+
// Look for 'query' property in the type literal
|
|
48
|
+
const query = typeAliasDeclaration.members.find((m) => m.kind === ts.SyntaxKind.PropertySignature &&
|
|
49
|
+
m.name?.getText() === "query");
|
|
50
|
+
if (!query)
|
|
51
|
+
continue;
|
|
52
|
+
// Check if query type has the pageParam
|
|
53
|
+
const queryType = query.type;
|
|
54
|
+
if (!queryType || queryType.kind !== ts.SyntaxKind.TypeLiteral)
|
|
55
|
+
continue;
|
|
56
|
+
const hasPageParam = queryType.members.some((m) => m.name?.getText() === pageParam);
|
|
57
|
+
if (hasPageParam) {
|
|
58
|
+
// Extract method name from Data type name (e.g., "FindPetsData" -> "findPets")
|
|
59
|
+
const methodName = key.slice(0, -4); // Remove "Data" suffix
|
|
60
|
+
// Convert first letter to lowercase
|
|
61
|
+
const methodNameLower = methodName.charAt(0).toLowerCase() + methodName.slice(1);
|
|
62
|
+
paginatableMethods.push(methodNameLower);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return paginatableMethods;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parse operations from the OpenAPI-generated service file and return normalized DTOs.
|
|
69
|
+
*/
|
|
70
|
+
export async function parseOperations(project, pageParam) {
|
|
71
|
+
const service = await getServices(project);
|
|
72
|
+
const { methods } = service;
|
|
73
|
+
const paginatableMethods = getPaginatableMethods(project, pageParam);
|
|
74
|
+
return methods.map((desc) => {
|
|
75
|
+
const methodName = getNameFromVariable(desc.method);
|
|
76
|
+
const httpMethod = desc.httpMethodName.toUpperCase();
|
|
77
|
+
const parameters = extractParameters(desc.method);
|
|
78
|
+
// Use the SDK function's parameter optionality as the authoritative check.
|
|
79
|
+
// Generic types like Options<XData, ThrowOnError> may not resolve correctly
|
|
80
|
+
// via extractPropertiesFromObjectParam for type alias properties (path, url).
|
|
81
|
+
const sdkParams = getVariableArrowFunctionParameters(desc.method);
|
|
82
|
+
const allParamsOptional = sdkParams.length === 0 || sdkParams[0].isOptional();
|
|
83
|
+
const isPaginatable = httpMethod === "GET" && paginatableMethods.includes(methodName);
|
|
84
|
+
return {
|
|
85
|
+
methodName,
|
|
86
|
+
capitalizedMethodName: capitalizeFirstLetter(methodName),
|
|
87
|
+
httpMethod,
|
|
88
|
+
jsDoc: desc.jsDoc,
|
|
89
|
+
isDeprecated: desc.isDeprecated,
|
|
90
|
+
parameters,
|
|
91
|
+
allParamsOptional,
|
|
92
|
+
isPaginatable,
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build generation context from project configuration.
|
|
98
|
+
*/
|
|
99
|
+
export function buildGenerationContext(project, client, pageParam, nextPageParam, initialPageParam, version) {
|
|
100
|
+
const modelsFile = project
|
|
101
|
+
.getSourceFiles()
|
|
102
|
+
.find((sf) => sf.getFilePath().includes(modelsFileName));
|
|
103
|
+
const serviceFile = project
|
|
104
|
+
.getSourceFiles()
|
|
105
|
+
.find((sf) => sf.getFilePath().includes(serviceFileName));
|
|
106
|
+
if (!serviceFile) {
|
|
107
|
+
throw new Error("No service node found");
|
|
108
|
+
}
|
|
109
|
+
const modelNames = modelsFile
|
|
110
|
+
? Array.from(modelsFile.getExportedDeclarations().keys())
|
|
111
|
+
: [];
|
|
112
|
+
const serviceNames = Array.from(serviceFile.getExportedDeclarations().keys());
|
|
113
|
+
return {
|
|
114
|
+
client,
|
|
115
|
+
modelNames,
|
|
116
|
+
serviceNames,
|
|
117
|
+
pageParam,
|
|
118
|
+
nextPageParam,
|
|
119
|
+
initialPageParam,
|
|
120
|
+
version,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { StructureKind, VariableDeclarationKind, } from "ts-morph";
|
|
2
|
+
/**
|
|
3
|
+
* Build the default response type alias.
|
|
4
|
+
* Example: export type FindPetsDefaultResponse = Awaited<ReturnType<typeof findPets>>["data"];
|
|
5
|
+
*/
|
|
6
|
+
export function buildDefaultResponseType(op) {
|
|
7
|
+
return {
|
|
8
|
+
kind: StructureKind.TypeAlias,
|
|
9
|
+
isExported: true,
|
|
10
|
+
name: `${op.capitalizedMethodName}DefaultResponse`,
|
|
11
|
+
type: `Awaited<ReturnType<typeof ${op.methodName}>>["data"]`,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build the query result type alias.
|
|
16
|
+
* Example: export type FindPetsQueryResult<TData = FindPetsDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
|
|
17
|
+
*/
|
|
18
|
+
export function buildQueryResultType(op) {
|
|
19
|
+
return {
|
|
20
|
+
kind: StructureKind.TypeAlias,
|
|
21
|
+
isExported: true,
|
|
22
|
+
name: `${op.capitalizedMethodName}QueryResult`,
|
|
23
|
+
typeParameters: [
|
|
24
|
+
{ name: "TData", default: `${op.capitalizedMethodName}DefaultResponse` },
|
|
25
|
+
{ name: "TError", default: "unknown" },
|
|
26
|
+
],
|
|
27
|
+
type: "UseQueryResult<TData, TError>",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build the mutation result type alias.
|
|
32
|
+
* Example: export type AddPetMutationResult = Awaited<ReturnType<typeof addPet>>;
|
|
33
|
+
*/
|
|
34
|
+
export function buildMutationResultType(op) {
|
|
35
|
+
return {
|
|
36
|
+
kind: StructureKind.TypeAlias,
|
|
37
|
+
isExported: true,
|
|
38
|
+
name: `${op.capitalizedMethodName}MutationResult`,
|
|
39
|
+
type: `Awaited<ReturnType<typeof ${op.methodName}>>`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build query key constant.
|
|
44
|
+
* Example: export const useFindPetsKey = "FindPets";
|
|
45
|
+
*/
|
|
46
|
+
export function buildQueryKeyConst(op) {
|
|
47
|
+
return {
|
|
48
|
+
kind: StructureKind.VariableStatement,
|
|
49
|
+
isExported: true,
|
|
50
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
51
|
+
declarations: [
|
|
52
|
+
{
|
|
53
|
+
name: `use${op.capitalizedMethodName}Key`,
|
|
54
|
+
initializer: `"${op.capitalizedMethodName}"`,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build mutation key constant.
|
|
61
|
+
* Example: export const useAddPetKey = "AddPet";
|
|
62
|
+
*/
|
|
63
|
+
export function buildMutationKeyConst(op) {
|
|
64
|
+
return {
|
|
65
|
+
kind: StructureKind.VariableStatement,
|
|
66
|
+
isExported: true,
|
|
67
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
68
|
+
declarations: [
|
|
69
|
+
{
|
|
70
|
+
name: `use${op.capitalizedMethodName}Key`,
|
|
71
|
+
initializer: `"${op.capitalizedMethodName}"`,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build query key function.
|
|
78
|
+
* Example: export const UseFindPetsKeyFn = (clientOptions: Options<FindPetsData, true> = {}, queryKey?: Array<unknown>) =>
|
|
79
|
+
* [useFindPetsKey, ...(queryKey ?? [clientOptions])];
|
|
80
|
+
*/
|
|
81
|
+
export function buildQueryKeyFn(op, ctx) {
|
|
82
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
83
|
+
? `${op.capitalizedMethodName}Data`
|
|
84
|
+
: "unknown";
|
|
85
|
+
const params = [];
|
|
86
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
87
|
+
params.push(`clientOptions: Options<${dataTypeName}, true>${defaultValue}`);
|
|
88
|
+
params.push("queryKey?: Array<unknown>");
|
|
89
|
+
const fallbackArray = "[clientOptions]";
|
|
90
|
+
return {
|
|
91
|
+
kind: StructureKind.VariableStatement,
|
|
92
|
+
isExported: true,
|
|
93
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
94
|
+
declarations: [
|
|
95
|
+
{
|
|
96
|
+
name: `Use${op.capitalizedMethodName}KeyFn`,
|
|
97
|
+
initializer: `(${params.join(", ")}) => [use${op.capitalizedMethodName}Key, ...(queryKey ?? ${fallbackArray})]`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Build mutation key function.
|
|
104
|
+
* Example: export const UseAddPetKeyFn = (mutationKey?: Array<unknown>) =>
|
|
105
|
+
* [useAddPetKey, ...(mutationKey ?? [])];
|
|
106
|
+
*/
|
|
107
|
+
export function buildMutationKeyFn(op) {
|
|
108
|
+
return {
|
|
109
|
+
kind: StructureKind.VariableStatement,
|
|
110
|
+
isExported: true,
|
|
111
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
112
|
+
declarations: [
|
|
113
|
+
{
|
|
114
|
+
name: `Use${op.capitalizedMethodName}KeyFn`,
|
|
115
|
+
initializer: `(mutationKey?: Array<unknown>) => [use${op.capitalizedMethodName}Key, ...(mutationKey ?? [])]`,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
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
|
+
* Kept distinct from the plain query key so cached InfiniteData never
|
|
145
|
+
* collides with plain query data for the same operation (#140).
|
|
146
|
+
* Example: export const useFindPaginatedPetsInfiniteKey = "FindPaginatedPetsInfinite";
|
|
147
|
+
*/
|
|
148
|
+
export function buildInfiniteQueryKeyConst(op) {
|
|
149
|
+
return {
|
|
150
|
+
kind: StructureKind.VariableStatement,
|
|
151
|
+
isExported: true,
|
|
152
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
153
|
+
declarations: [
|
|
154
|
+
{
|
|
155
|
+
name: `use${op.capitalizedMethodName}InfiniteKey`,
|
|
156
|
+
initializer: `"${op.capitalizedMethodName}Infinite"`,
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Build the infinite query key function.
|
|
163
|
+
* Example: export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array<unknown>) =>
|
|
164
|
+
* [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])];
|
|
165
|
+
*/
|
|
166
|
+
export function buildInfiniteQueryKeyFn(op) {
|
|
167
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
168
|
+
const params = [
|
|
169
|
+
`clientOptions: ${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`,
|
|
170
|
+
"queryKey?: Array<unknown>",
|
|
171
|
+
];
|
|
172
|
+
return {
|
|
173
|
+
kind: StructureKind.VariableStatement,
|
|
174
|
+
isExported: true,
|
|
175
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
176
|
+
declarations: [
|
|
177
|
+
{
|
|
178
|
+
name: `Use${op.capitalizedMethodName}InfiniteKeyFn`,
|
|
179
|
+
initializer: `(${params.join(", ")}) => [use${op.capitalizedMethodName}InfiniteKey, ...(queryKey ?? [clientOptions])]`,
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { StructureKind, VariableDeclarationKind, } from "ts-morph";
|
|
2
|
+
/**
|
|
3
|
+
* Build query key constant name (e.g., "findPetsQueryKey").
|
|
4
|
+
*/
|
|
5
|
+
export function getQueryKeyName(op) {
|
|
6
|
+
return `${op.methodName}QueryKey`;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Build mutation key constant name (e.g., "addPetMutationKey").
|
|
10
|
+
*/
|
|
11
|
+
export function getMutationKeyName(op) {
|
|
12
|
+
return `${op.methodName}MutationKey`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build query key fn name (e.g., "FindPetsQueryKeyFn").
|
|
16
|
+
*/
|
|
17
|
+
export function getQueryKeyFnName(op) {
|
|
18
|
+
return `${op.capitalizedMethodName}QueryKeyFn`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build mutation key fn name (e.g., "AddPetMutationKeyFn").
|
|
22
|
+
*/
|
|
23
|
+
export function getMutationKeyFnName(op) {
|
|
24
|
+
return `${op.capitalizedMethodName}MutationKeyFn`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build the query key constant export.
|
|
28
|
+
* Example: export const findPetsQueryKey = "FindPets";
|
|
29
|
+
*/
|
|
30
|
+
export function buildQueryKeyExport(op) {
|
|
31
|
+
return {
|
|
32
|
+
kind: StructureKind.VariableStatement,
|
|
33
|
+
isExported: true,
|
|
34
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
35
|
+
declarations: [
|
|
36
|
+
{
|
|
37
|
+
name: getQueryKeyName(op),
|
|
38
|
+
initializer: `"${op.capitalizedMethodName}"`,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build the mutation key constant export.
|
|
45
|
+
* Example: export const addPetMutationKey = "AddPet";
|
|
46
|
+
*/
|
|
47
|
+
export function buildMutationKeyExport(op) {
|
|
48
|
+
return {
|
|
49
|
+
kind: StructureKind.VariableStatement,
|
|
50
|
+
isExported: true,
|
|
51
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
52
|
+
declarations: [
|
|
53
|
+
{
|
|
54
|
+
name: getMutationKeyName(op),
|
|
55
|
+
initializer: `"${op.capitalizedMethodName}"`,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build the query key function export.
|
|
62
|
+
* Example:
|
|
63
|
+
* export const FindPetsQueryKeyFn = (clientOptions: Options<FindPetsData, true>, queryKey?: Array<unknown>) =>
|
|
64
|
+
* [findPetsQueryKey, ...(queryKey ?? [clientOptions])] as const;
|
|
65
|
+
*/
|
|
66
|
+
export function buildQueryKeyFnExport(op, ctx) {
|
|
67
|
+
const hasParams = op.parameters.length > 0;
|
|
68
|
+
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
|
|
69
|
+
? `${op.capitalizedMethodName}Data`
|
|
70
|
+
: "unknown";
|
|
71
|
+
const params = [];
|
|
72
|
+
if (hasParams) {
|
|
73
|
+
const defaultValue = op.allParamsOptional ? " = {}" : "";
|
|
74
|
+
params.push(`clientOptions: Options<${dataTypeName}, true>${defaultValue}`);
|
|
75
|
+
}
|
|
76
|
+
params.push("queryKey?: Array<unknown>");
|
|
77
|
+
const fallbackArray = hasParams ? "[clientOptions]" : "[]";
|
|
78
|
+
const body = `[${getQueryKeyName(op)}, ...(queryKey ?? ${fallbackArray})] as const`;
|
|
79
|
+
return {
|
|
80
|
+
kind: StructureKind.VariableStatement,
|
|
81
|
+
isExported: true,
|
|
82
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
83
|
+
declarations: [
|
|
84
|
+
{
|
|
85
|
+
name: getQueryKeyFnName(op),
|
|
86
|
+
initializer: `(${params.join(", ")}) => ${body}`,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build the mutation key function export.
|
|
93
|
+
* Example:
|
|
94
|
+
* export const AddPetMutationKeyFn = (mutationKey?: Array<unknown>) =>
|
|
95
|
+
* [addPetMutationKey, ...(mutationKey ?? [])] as const;
|
|
96
|
+
*/
|
|
97
|
+
export function buildMutationKeyFnExport(op) {
|
|
98
|
+
const body = `[${getMutationKeyName(op)}, ...(mutationKey ?? [])] as const`;
|
|
99
|
+
return {
|
|
100
|
+
kind: StructureKind.VariableStatement,
|
|
101
|
+
isExported: true,
|
|
102
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
103
|
+
declarations: [
|
|
104
|
+
{
|
|
105
|
+
name: getMutationKeyFnName(op),
|
|
106
|
+
initializer: `(mutationKey?: Array<unknown>) => ${body}`,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
}
|