@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 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
- }
@@ -5,6 +5,7 @@ export const serviceFileName = "sdk.gen";
5
5
  export const modelsFileName = "types.gen";
6
6
  export const OpenApiRqFiles = {
7
7
  queries: "queries",
8
+ queryOptions: "queryOptions",
8
9
  infiniteQueries: "infiniteQueries",
9
10
  common: "common",
10
11
  suspense: "suspense",
@@ -1,115 +1,21 @@
1
1
  import { join } from "node:path";
2
2
  import { Project } from "ts-morph";
3
- import ts from "typescript";
4
- import { OpenApiRqFiles } from "./constants.mjs";
5
- import { createExports } from "./createExports.mjs";
6
- import { createImports } from "./createImports.mjs";
7
- import { getServices } from "./service.mjs";
8
- const createSourceFile = async ({ outputPath, client, pageParam, nextPageParam, initialPageParam, }) => {
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
- const service = await getServices(project);
19
- const imports = createImports({
20
- project,
21
- client,
22
- });
23
- const exports = createExports({
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: formattedOptions.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
+ }