@constructive-io/graphql-codegen 2.23.3 → 2.24.0

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.
Files changed (86) hide show
  1. package/README.md +147 -2
  2. package/cli/codegen/babel-ast.d.ts +46 -0
  3. package/cli/codegen/babel-ast.js +145 -0
  4. package/cli/codegen/barrel.d.ts +7 -2
  5. package/cli/codegen/barrel.js +159 -97
  6. package/cli/codegen/client.js +61 -0
  7. package/cli/codegen/custom-mutations.d.ts +2 -12
  8. package/cli/codegen/custom-mutations.js +116 -124
  9. package/cli/codegen/custom-queries.d.ts +2 -10
  10. package/cli/codegen/custom-queries.js +246 -335
  11. package/cli/codegen/index.d.ts +3 -0
  12. package/cli/codegen/index.js +72 -3
  13. package/cli/codegen/invalidation.d.ts +20 -0
  14. package/cli/codegen/invalidation.js +327 -0
  15. package/cli/codegen/mutation-keys.d.ts +24 -0
  16. package/cli/codegen/mutation-keys.js +247 -0
  17. package/cli/codegen/mutations.d.ts +3 -19
  18. package/cli/codegen/mutations.js +372 -383
  19. package/cli/codegen/orm/barrel.d.ts +1 -1
  20. package/cli/codegen/orm/barrel.js +42 -10
  21. package/cli/codegen/orm/client-generator.d.ts +1 -19
  22. package/cli/codegen/orm/client-generator.js +108 -77
  23. package/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  24. package/cli/codegen/orm/custom-ops-generator.js +192 -235
  25. package/cli/codegen/orm/input-types-generator.d.ts +13 -1
  26. package/cli/codegen/orm/input-types-generator.js +403 -147
  27. package/cli/codegen/orm/model-generator.d.ts +1 -19
  28. package/cli/codegen/orm/model-generator.js +229 -234
  29. package/cli/codegen/queries.d.ts +3 -11
  30. package/cli/codegen/queries.js +582 -389
  31. package/cli/codegen/query-keys.d.ts +15 -0
  32. package/cli/codegen/query-keys.js +477 -0
  33. package/cli/codegen/scalars.js +1 -0
  34. package/cli/codegen/schema-types-generator.d.ts +15 -10
  35. package/cli/codegen/schema-types-generator.js +87 -175
  36. package/cli/codegen/type-resolver.d.ts +1 -30
  37. package/cli/codegen/type-resolver.js +0 -53
  38. package/cli/codegen/types.d.ts +1 -1
  39. package/cli/codegen/types.js +76 -21
  40. package/esm/cli/codegen/babel-ast.d.ts +46 -0
  41. package/esm/cli/codegen/babel-ast.js +97 -0
  42. package/esm/cli/codegen/barrel.d.ts +7 -2
  43. package/esm/cli/codegen/barrel.js +126 -97
  44. package/esm/cli/codegen/client.js +61 -0
  45. package/esm/cli/codegen/custom-mutations.d.ts +2 -12
  46. package/esm/cli/codegen/custom-mutations.js +83 -124
  47. package/esm/cli/codegen/custom-queries.d.ts +2 -10
  48. package/esm/cli/codegen/custom-queries.js +214 -336
  49. package/esm/cli/codegen/index.d.ts +3 -0
  50. package/esm/cli/codegen/index.js +68 -2
  51. package/esm/cli/codegen/invalidation.d.ts +20 -0
  52. package/esm/cli/codegen/invalidation.js +291 -0
  53. package/esm/cli/codegen/mutation-keys.d.ts +24 -0
  54. package/esm/cli/codegen/mutation-keys.js +211 -0
  55. package/esm/cli/codegen/mutations.d.ts +3 -19
  56. package/esm/cli/codegen/mutations.js +340 -384
  57. package/esm/cli/codegen/orm/barrel.d.ts +1 -1
  58. package/esm/cli/codegen/orm/barrel.js +10 -11
  59. package/esm/cli/codegen/orm/client-generator.d.ts +1 -19
  60. package/esm/cli/codegen/orm/client-generator.js +76 -78
  61. package/esm/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  62. package/esm/cli/codegen/orm/custom-ops-generator.js +160 -236
  63. package/esm/cli/codegen/orm/input-types-generator.d.ts +13 -1
  64. package/esm/cli/codegen/orm/input-types-generator.js +371 -148
  65. package/esm/cli/codegen/orm/model-generator.d.ts +1 -19
  66. package/esm/cli/codegen/orm/model-generator.js +197 -235
  67. package/esm/cli/codegen/queries.d.ts +3 -11
  68. package/esm/cli/codegen/queries.js +550 -390
  69. package/esm/cli/codegen/query-keys.d.ts +15 -0
  70. package/esm/cli/codegen/query-keys.js +441 -0
  71. package/esm/cli/codegen/scalars.js +1 -0
  72. package/esm/cli/codegen/schema-types-generator.d.ts +15 -10
  73. package/esm/cli/codegen/schema-types-generator.js +54 -175
  74. package/esm/cli/codegen/type-resolver.d.ts +1 -30
  75. package/esm/cli/codegen/type-resolver.js +0 -49
  76. package/esm/cli/codegen/types.d.ts +1 -1
  77. package/esm/cli/codegen/types.js +44 -22
  78. package/esm/types/config.d.ts +75 -0
  79. package/esm/types/config.js +18 -0
  80. package/package.json +6 -4
  81. package/types/config.d.ts +75 -0
  82. package/types/config.js +19 -1
  83. package/cli/codegen/ts-ast.d.ts +0 -124
  84. package/cli/codegen/ts-ast.js +0 -280
  85. package/esm/cli/codegen/ts-ast.d.ts +0 -124
  86. package/esm/cli/codegen/ts-ast.js +0 -260
@@ -1,381 +1,258 @@
1
- import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, } from './ts-ast';
1
+ import * as t from '@babel/types';
2
+ import { generateCode, addJSDocComment, typedParam } from './babel-ast';
2
3
  import { buildCustomQueryString } from './schema-gql-ast';
3
4
  import { typeRefToTsType, isTypeRequired, getOperationHookName, getOperationFileName, getOperationVariablesTypeName, getOperationResultTypeName, getDocumentConstName, getQueryKeyName, createTypeTracker, } from './type-resolver';
4
- import { ucFirst } from './utils';
5
- /**
6
- * Generate a custom query hook file
7
- */
5
+ import { ucFirst, getGeneratedFileHeader } from './utils';
6
+ function generateVariablesProperties(args, tracker) {
7
+ return args.map((arg) => ({
8
+ name: arg.name,
9
+ type: typeRefToTsType(arg.type, tracker),
10
+ optional: !isTypeRequired(arg.type),
11
+ docs: arg.description ? [arg.description] : undefined,
12
+ }));
13
+ }
8
14
  export function generateCustomQueryHook(options) {
9
- const { operation, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames } = options;
10
- const project = createProject();
15
+ const { operation, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames, useCentralizedKeys = true, } = options;
11
16
  const hookName = getOperationHookName(operation.name, 'query');
12
17
  const fileName = getOperationFileName(operation.name, 'query');
13
18
  const variablesTypeName = getOperationVariablesTypeName(operation.name, 'query');
14
19
  const resultTypeName = getOperationResultTypeName(operation.name, 'query');
15
20
  const documentConstName = getDocumentConstName(operation.name, 'query');
16
21
  const queryKeyName = getQueryKeyName(operation.name);
17
- // Create type tracker to collect referenced types (with table type awareness)
18
22
  const tracker = createTypeTracker({ tableTypeNames });
19
- // Generate GraphQL document
20
23
  const queryDocument = buildCustomQueryString({
21
24
  operation,
22
25
  typeRegistry,
23
26
  maxDepth,
24
27
  skipQueryField,
25
28
  });
26
- const sourceFile = createSourceFile(project, fileName);
27
- // Add file header
28
- const headerText = reactQueryEnabled
29
- ? `Custom query hook for ${operation.name}`
30
- : `Custom query functions for ${operation.name}`;
31
- sourceFile.insertText(0, createFileHeader(headerText) + '\n\n');
32
- // Generate variables interface if there are arguments (with tracking)
33
- let variablesProps = [];
34
- if (operation.args.length > 0) {
35
- variablesProps = generateVariablesProperties(operation.args, tracker);
36
- }
37
- // Generate result interface (with tracking)
29
+ const statements = [];
30
+ const variablesProps = operation.args.length > 0
31
+ ? generateVariablesProperties(operation.args, tracker)
32
+ : [];
38
33
  const resultType = typeRefToTsType(operation.returnType, tracker);
39
- const resultProps = [
40
- { name: operation.name, type: resultType },
41
- ];
42
- // Get importable types from tracker (separated by source)
43
- const schemaTypes = tracker.getImportableTypes(); // From schema-types.ts
44
- const tableTypes = tracker.getTableTypes(); // From types.ts
45
- // Add imports - conditionally include React Query imports
46
- const imports = [];
34
+ const schemaTypes = tracker.getImportableTypes();
35
+ const tableTypes = tracker.getTableTypes();
47
36
  if (reactQueryEnabled) {
48
- imports.push(createImport({
49
- moduleSpecifier: '@tanstack/react-query',
50
- namedImports: ['useQuery'],
51
- typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
52
- }));
37
+ const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
38
+ statements.push(reactQueryImport);
39
+ const reactQueryTypeImport = t.importDeclaration([
40
+ t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
41
+ t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
42
+ ], t.stringLiteral('@tanstack/react-query'));
43
+ reactQueryTypeImport.importKind = 'type';
44
+ statements.push(reactQueryTypeImport);
53
45
  }
54
- imports.push(createImport({
55
- moduleSpecifier: '../client',
56
- namedImports: ['execute'],
57
- typeOnlyNamedImports: ['ExecuteOptions'],
58
- }));
59
- // Add types.ts import for table entity types
46
+ const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
47
+ statements.push(clientImport);
48
+ const clientTypeImport = t.importDeclaration([t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions'))], t.stringLiteral('../client'));
49
+ clientTypeImport.importKind = 'type';
50
+ statements.push(clientTypeImport);
60
51
  if (tableTypes.length > 0) {
61
- imports.push(createImport({
62
- moduleSpecifier: '../types',
63
- typeOnlyNamedImports: tableTypes,
64
- }));
52
+ const typesImport = t.importDeclaration(tableTypes.map((tt) => t.importSpecifier(t.identifier(tt), t.identifier(tt))), t.stringLiteral('../types'));
53
+ typesImport.importKind = 'type';
54
+ statements.push(typesImport);
65
55
  }
66
- // Add schema-types import for Input/Payload/Enum types
67
56
  if (schemaTypes.length > 0) {
68
- imports.push(createImport({
69
- moduleSpecifier: '../schema-types',
70
- typeOnlyNamedImports: schemaTypes,
71
- }));
57
+ const schemaTypesImport = t.importDeclaration(schemaTypes.map((st) => t.importSpecifier(t.identifier(st), t.identifier(st))), t.stringLiteral('../schema-types'));
58
+ schemaTypesImport.importKind = 'type';
59
+ statements.push(schemaTypesImport);
72
60
  }
73
- sourceFile.addImportDeclarations(imports);
74
- // Add query document constant
75
- sourceFile.addVariableStatement(createConst(documentConstName, '`\n' + queryDocument + '`', {
76
- docs: ['GraphQL query document'],
77
- }));
78
- // Add variables interface
79
- if (operation.args.length > 0) {
80
- sourceFile.addInterface(createInterface(variablesTypeName, variablesProps));
61
+ if (useCentralizedKeys) {
62
+ const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier('customQueryKeys'), t.identifier('customQueryKeys'))], t.stringLiteral('../query-keys'));
63
+ statements.push(queryKeyImport);
81
64
  }
82
- // Add result interface
83
- sourceFile.addInterface(createInterface(resultTypeName, resultProps));
84
- // Query key factory
65
+ const queryDocConst = t.variableDeclaration('const', [
66
+ t.variableDeclarator(t.identifier(documentConstName), t.templateLiteral([t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true)], [])),
67
+ ]);
68
+ const queryDocExport = t.exportNamedDeclaration(queryDocConst);
69
+ addJSDocComment(queryDocExport, ['GraphQL query document']);
70
+ statements.push(queryDocExport);
85
71
  if (operation.args.length > 0) {
86
- sourceFile.addVariableStatement(createConst(queryKeyName, `(variables?: ${variablesTypeName}) =>
87
- ['${operation.name}', variables] as const`, { docs: ['Query key factory for caching'] }));
72
+ const variablesInterfaceProps = variablesProps.map((vp) => {
73
+ const prop = t.tsPropertySignature(t.identifier(vp.name), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(vp.type))));
74
+ prop.optional = vp.optional;
75
+ return prop;
76
+ });
77
+ const variablesInterface = t.tsInterfaceDeclaration(t.identifier(variablesTypeName), null, null, t.tsInterfaceBody(variablesInterfaceProps));
78
+ statements.push(t.exportNamedDeclaration(variablesInterface));
79
+ }
80
+ const resultInterfaceBody = t.tsInterfaceBody([
81
+ t.tsPropertySignature(t.identifier(operation.name), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(resultType)))),
82
+ ]);
83
+ const resultInterface = t.tsInterfaceDeclaration(t.identifier(resultTypeName), null, null, resultInterfaceBody);
84
+ statements.push(t.exportNamedDeclaration(resultInterface));
85
+ if (useCentralizedKeys) {
86
+ const queryKeyConst = t.variableDeclaration('const', [
87
+ t.variableDeclarator(t.identifier(queryKeyName), t.memberExpression(t.identifier('customQueryKeys'), t.identifier(operation.name))),
88
+ ]);
89
+ const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
90
+ addJSDocComment(queryKeyExport, ['Query key factory - re-exported from query-keys.ts']);
91
+ statements.push(queryKeyExport);
92
+ }
93
+ else if (operation.args.length > 0) {
94
+ const queryKeyArrow = t.arrowFunctionExpression([typedParam('variables', t.tsTypeReference(t.identifier(variablesTypeName)), true)], t.tsAsExpression(t.arrayExpression([t.stringLiteral(operation.name), t.identifier('variables')]), t.tsTypeReference(t.identifier('const'))));
95
+ const queryKeyConst = t.variableDeclaration('const', [
96
+ t.variableDeclarator(t.identifier(queryKeyName), queryKeyArrow),
97
+ ]);
98
+ const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
99
+ addJSDocComment(queryKeyExport, ['Query key factory for caching']);
100
+ statements.push(queryKeyExport);
88
101
  }
89
102
  else {
90
- sourceFile.addVariableStatement(createConst(queryKeyName, `() => ['${operation.name}'] as const`, {
91
- docs: ['Query key factory for caching'],
92
- }));
103
+ const queryKeyArrow = t.arrowFunctionExpression([], t.tsAsExpression(t.arrayExpression([t.stringLiteral(operation.name)]), t.tsTypeReference(t.identifier('const'))));
104
+ const queryKeyConst = t.variableDeclaration('const', [
105
+ t.variableDeclarator(t.identifier(queryKeyName), queryKeyArrow),
106
+ ]);
107
+ const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
108
+ addJSDocComment(queryKeyExport, ['Query key factory for caching']);
109
+ statements.push(queryKeyExport);
93
110
  }
94
- // Generate hook function (only if React Query is enabled)
95
111
  if (reactQueryEnabled) {
96
- const hookParams = generateHookParameters(operation, variablesTypeName, resultTypeName);
97
- const hookBody = generateHookBody(operation, documentConstName, queryKeyName, variablesTypeName, resultTypeName);
98
- const hookDoc = generateHookDoc(operation, hookName);
99
- sourceFile.addFunction({
100
- name: hookName,
101
- isExported: true,
102
- parameters: hookParams,
103
- statements: hookBody,
104
- docs: [{ description: hookDoc }],
105
- });
112
+ const hasArgs = operation.args.length > 0;
113
+ const hasRequiredArgs = operation.args.some((arg) => isTypeRequired(arg.type));
114
+ const hookBodyStatements = [];
115
+ const useQueryOptions = [];
116
+ if (hasArgs) {
117
+ useQueryOptions.push(t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(queryKeyName), [t.identifier('variables')])));
118
+ useQueryOptions.push(t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
119
+ t.identifier(documentConstName),
120
+ t.identifier('variables'),
121
+ ]))));
122
+ if (hasRequiredArgs) {
123
+ useQueryOptions.push(t.objectProperty(t.identifier('enabled'), t.logicalExpression('&&', t.unaryExpression('!', t.unaryExpression('!', t.identifier('variables'))), t.binaryExpression('!==', t.optionalMemberExpression(t.identifier('options'), t.identifier('enabled'), false, true), t.booleanLiteral(false)))));
124
+ }
125
+ }
126
+ else {
127
+ useQueryOptions.push(t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(queryKeyName), [])));
128
+ useQueryOptions.push(t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [t.identifier(documentConstName)]))));
129
+ }
130
+ useQueryOptions.push(t.spreadElement(t.identifier('options')));
131
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [t.objectExpression(useQueryOptions)])));
132
+ const hookParams = [];
133
+ if (hasArgs) {
134
+ hookParams.push(typedParam('variables', t.tsTypeReference(t.identifier(variablesTypeName)), !hasRequiredArgs));
135
+ }
136
+ const optionsTypeStr = `Omit<UseQueryOptions<${resultTypeName}, Error>, 'queryKey' | 'queryFn'>`;
137
+ const optionsParam = t.identifier('options');
138
+ optionsParam.optional = true;
139
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
140
+ hookParams.push(optionsParam);
141
+ const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
142
+ const hookExport = t.exportNamedDeclaration(hookFunc);
143
+ const description = operation.description || `Query hook for ${operation.name}`;
144
+ const argNames = operation.args.map((a) => a.name).join(', ');
145
+ const exampleCall = hasArgs ? `${hookName}({ ${argNames} })` : `${hookName}()`;
146
+ addJSDocComment(hookExport, [
147
+ description,
148
+ '',
149
+ '@example',
150
+ '```tsx',
151
+ `const { data, isLoading } = ${exampleCall};`,
152
+ '',
153
+ `if (data?.${operation.name}) {`,
154
+ ` console.log(data.${operation.name});`,
155
+ '}',
156
+ '```',
157
+ ]);
158
+ statements.push(hookExport);
106
159
  }
107
- // Add standalone functions section
108
- sourceFile.addStatements('\n// ============================================================================');
109
- sourceFile.addStatements('// Standalone Functions (non-React)');
110
- sourceFile.addStatements('// ============================================================================\n');
111
- // Generate standalone fetch function
112
160
  const fetchFnName = `fetch${ucFirst(operation.name)}Query`;
113
- const fetchParams = generateFetchParameters(operation, variablesTypeName);
114
- const fetchBody = generateFetchBody(operation, documentConstName, variablesTypeName, resultTypeName);
115
- const fetchDoc = generateFetchDoc(operation, fetchFnName);
116
- sourceFile.addFunction({
117
- name: fetchFnName,
118
- isExported: true,
119
- isAsync: true,
120
- parameters: fetchParams,
121
- returnType: `Promise<${resultTypeName}>`,
122
- statements: fetchBody,
123
- docs: [{ description: fetchDoc }],
124
- });
125
- // Generate prefetch function (only if React Query is enabled)
126
- if (reactQueryEnabled) {
127
- const prefetchFnName = `prefetch${ucFirst(operation.name)}Query`;
128
- const prefetchParams = generatePrefetchParameters(operation, variablesTypeName);
129
- const prefetchBody = generatePrefetchBody(operation, documentConstName, queryKeyName, variablesTypeName, resultTypeName);
130
- const prefetchDoc = generatePrefetchDoc(operation, prefetchFnName);
131
- sourceFile.addFunction({
132
- name: prefetchFnName,
133
- isExported: true,
134
- isAsync: true,
135
- parameters: prefetchParams,
136
- returnType: 'Promise<void>',
137
- statements: prefetchBody,
138
- docs: [{ description: prefetchDoc }],
139
- });
140
- }
141
- return {
142
- fileName,
143
- content: getFormattedOutput(sourceFile),
144
- operationName: operation.name,
145
- };
146
- }
147
- // ============================================================================
148
- // Helper functions
149
- // ============================================================================
150
- /**
151
- * Generate interface properties from CleanArguments
152
- */
153
- function generateVariablesProperties(args, tracker) {
154
- return args.map((arg) => ({
155
- name: arg.name,
156
- type: typeRefToTsType(arg.type, tracker),
157
- optional: !isTypeRequired(arg.type),
158
- docs: arg.description ? [arg.description] : undefined,
159
- }));
160
- }
161
- /**
162
- * Generate hook function parameters
163
- */
164
- function generateHookParameters(operation, variablesTypeName, resultTypeName) {
165
- const params = [];
166
- // Add variables parameter if there are required args
167
- const hasRequiredArgs = operation.args.some((arg) => isTypeRequired(arg.type));
168
- if (operation.args.length > 0) {
169
- params.push({
170
- name: 'variables',
171
- type: variablesTypeName,
172
- hasQuestionToken: !hasRequiredArgs,
173
- });
174
- }
175
- // Add options parameter
176
- params.push({
177
- name: 'options',
178
- type: `Omit<UseQueryOptions<${resultTypeName}, Error>, 'queryKey' | 'queryFn'>`,
179
- hasQuestionToken: true,
180
- });
181
- return params;
182
- }
183
- /**
184
- * Generate hook function body
185
- */
186
- function generateHookBody(operation, documentConstName, queryKeyName, variablesTypeName, resultTypeName) {
187
161
  const hasArgs = operation.args.length > 0;
188
162
  const hasRequiredArgs = operation.args.some((arg) => isTypeRequired(arg.type));
163
+ const fetchBodyStatements = [];
189
164
  if (hasArgs) {
190
- // With variables
191
- const enabledCondition = hasRequiredArgs
192
- ? `enabled: !!variables && (options?.enabled !== false),`
193
- : '';
194
- return `return useQuery({
195
- queryKey: ${queryKeyName}(variables),
196
- queryFn: () => execute<${resultTypeName}, ${variablesTypeName}>(
197
- ${documentConstName},
198
- variables
199
- ),
200
- ${enabledCondition}
201
- ...options,
202
- });`;
165
+ fetchBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('execute'), [
166
+ t.identifier(documentConstName),
167
+ t.identifier('variables'),
168
+ t.identifier('options'),
169
+ ])));
203
170
  }
204
171
  else {
205
- // No variables
206
- return `return useQuery({
207
- queryKey: ${queryKeyName}(),
208
- queryFn: () => execute<${resultTypeName}>(${documentConstName}),
209
- ...options,
210
- });`;
172
+ fetchBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('execute'), [
173
+ t.identifier(documentConstName),
174
+ t.identifier('undefined'),
175
+ t.identifier('options'),
176
+ ])));
211
177
  }
212
- }
213
- /**
214
- * Generate hook JSDoc documentation
215
- */
216
- function generateHookDoc(operation, hookName) {
217
- const description = operation.description
218
- ? operation.description
219
- : `Query hook for ${operation.name}`;
220
- const hasArgs = operation.args.length > 0;
221
- let example;
178
+ const fetchParams = [];
222
179
  if (hasArgs) {
223
- const argNames = operation.args.map((a) => a.name).join(', ');
224
- example = `
225
- @example
226
- \`\`\`tsx
227
- const { data, isLoading } = ${hookName}({ ${argNames} });
228
-
229
- if (data?.${operation.name}) {
230
- console.log(data.${operation.name});
231
- }
232
- \`\`\``;
233
- }
234
- else {
235
- example = `
236
- @example
237
- \`\`\`tsx
238
- const { data, isLoading } = ${hookName}();
239
-
240
- if (data?.${operation.name}) {
241
- console.log(data.${operation.name});
242
- }
243
- \`\`\``;
244
- }
245
- return description + '\n' + example;
246
- }
247
- // ============================================================================
248
- // Standalone function generators
249
- // ============================================================================
250
- /**
251
- * Generate fetch function parameters
252
- */
253
- function generateFetchParameters(operation, variablesTypeName) {
254
- const params = [];
255
- if (operation.args.length > 0) {
256
- const hasRequiredArgs = operation.args.some((arg) => isTypeRequired(arg.type));
257
- params.push({
258
- name: 'variables',
259
- type: variablesTypeName,
260
- hasQuestionToken: !hasRequiredArgs,
261
- });
262
- }
263
- params.push({
264
- name: 'options',
265
- type: 'ExecuteOptions',
266
- hasQuestionToken: true,
267
- });
268
- return params;
269
- }
270
- /**
271
- * Generate fetch function body
272
- */
273
- function generateFetchBody(operation, documentConstName, variablesTypeName, resultTypeName) {
274
- if (operation.args.length > 0) {
275
- return `return execute<${resultTypeName}, ${variablesTypeName}>(
276
- ${documentConstName},
277
- variables,
278
- options
279
- );`;
280
- }
281
- else {
282
- return `return execute<${resultTypeName}>(${documentConstName}, undefined, options);`;
283
- }
284
- }
285
- /**
286
- * Generate fetch function documentation
287
- */
288
- function generateFetchDoc(operation, fnName) {
289
- const description = `Fetch ${operation.name} without React hooks`;
290
- if (operation.args.length > 0) {
291
- const argNames = operation.args.map((a) => a.name).join(', ');
292
- return `${description}
293
-
294
- @example
295
- \`\`\`ts
296
- const data = await ${fnName}({ ${argNames} });
297
- \`\`\``;
298
- }
299
- else {
300
- return `${description}
301
-
302
- @example
303
- \`\`\`ts
304
- const data = await ${fnName}();
305
- \`\`\``;
180
+ fetchParams.push(typedParam('variables', t.tsTypeReference(t.identifier(variablesTypeName)), !hasRequiredArgs));
306
181
  }
307
- }
308
- /**
309
- * Generate prefetch function parameters
310
- */
311
- function generatePrefetchParameters(operation, variablesTypeName) {
312
- const params = [
313
- { name: 'queryClient', type: 'QueryClient' },
314
- ];
315
- if (operation.args.length > 0) {
316
- const hasRequiredArgs = operation.args.some((arg) => isTypeRequired(arg.type));
317
- params.push({
318
- name: 'variables',
319
- type: variablesTypeName,
320
- hasQuestionToken: !hasRequiredArgs,
321
- });
322
- }
323
- params.push({
324
- name: 'options',
325
- type: 'ExecuteOptions',
326
- hasQuestionToken: true,
327
- });
328
- return params;
329
- }
330
- /**
331
- * Generate prefetch function body
332
- */
333
- function generatePrefetchBody(operation, documentConstName, queryKeyName, variablesTypeName, resultTypeName) {
334
- if (operation.args.length > 0) {
335
- return `await queryClient.prefetchQuery({
336
- queryKey: ${queryKeyName}(variables),
337
- queryFn: () => execute<${resultTypeName}, ${variablesTypeName}>(
338
- ${documentConstName},
339
- variables,
340
- options
341
- ),
342
- });`;
343
- }
344
- else {
345
- return `await queryClient.prefetchQuery({
346
- queryKey: ${queryKeyName}(),
347
- queryFn: () => execute<${resultTypeName}>(${documentConstName}, undefined, options),
348
- });`;
349
- }
350
- }
351
- /**
352
- * Generate prefetch function documentation
353
- */
354
- function generatePrefetchDoc(operation, fnName) {
355
- const description = `Prefetch ${operation.name} for SSR or cache warming`;
356
- if (operation.args.length > 0) {
357
- const argNames = operation.args.map((a) => a.name).join(', ');
358
- return `${description}
359
-
360
- @example
361
- \`\`\`ts
362
- await ${fnName}(queryClient, { ${argNames} });
363
- \`\`\``;
364
- }
365
- else {
366
- return `${description}
367
-
368
- @example
369
- \`\`\`ts
370
- await ${fnName}(queryClient);
371
- \`\`\``;
182
+ fetchParams.push(typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
183
+ const fetchFunc = t.functionDeclaration(t.identifier(fetchFnName), fetchParams, t.blockStatement(fetchBodyStatements));
184
+ fetchFunc.async = true;
185
+ fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsTypeReference(t.identifier(resultTypeName))])));
186
+ const fetchExport = t.exportNamedDeclaration(fetchFunc);
187
+ const argNames = operation.args.map((a) => a.name).join(', ');
188
+ const fetchExampleCall = hasArgs ? `${fetchFnName}({ ${argNames} })` : `${fetchFnName}()`;
189
+ addJSDocComment(fetchExport, [
190
+ `Fetch ${operation.name} without React hooks`,
191
+ '',
192
+ '@example',
193
+ '```ts',
194
+ `const data = await ${fetchExampleCall};`,
195
+ '```',
196
+ ]);
197
+ statements.push(fetchExport);
198
+ if (reactQueryEnabled) {
199
+ const prefetchFnName = `prefetch${ucFirst(operation.name)}Query`;
200
+ const prefetchBodyStatements = [];
201
+ const prefetchQueryOptions = [];
202
+ if (hasArgs) {
203
+ prefetchQueryOptions.push(t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(queryKeyName), [t.identifier('variables')])));
204
+ prefetchQueryOptions.push(t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
205
+ t.identifier(documentConstName),
206
+ t.identifier('variables'),
207
+ t.identifier('options'),
208
+ ]))));
209
+ }
210
+ else {
211
+ prefetchQueryOptions.push(t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(queryKeyName), [])));
212
+ prefetchQueryOptions.push(t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
213
+ t.identifier(documentConstName),
214
+ t.identifier('undefined'),
215
+ t.identifier('options'),
216
+ ]))));
217
+ }
218
+ prefetchBodyStatements.push(t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [t.objectExpression(prefetchQueryOptions)]))));
219
+ const prefetchParams = [
220
+ typedParam('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
221
+ ];
222
+ if (hasArgs) {
223
+ prefetchParams.push(typedParam('variables', t.tsTypeReference(t.identifier(variablesTypeName)), !hasRequiredArgs));
224
+ }
225
+ prefetchParams.push(typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
226
+ const prefetchFunc = t.functionDeclaration(t.identifier(prefetchFnName), prefetchParams, t.blockStatement(prefetchBodyStatements));
227
+ prefetchFunc.async = true;
228
+ prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
229
+ const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
230
+ const prefetchExampleCall = hasArgs
231
+ ? `${prefetchFnName}(queryClient, { ${argNames} })`
232
+ : `${prefetchFnName}(queryClient)`;
233
+ addJSDocComment(prefetchExport, [
234
+ `Prefetch ${operation.name} for SSR or cache warming`,
235
+ '',
236
+ '@example',
237
+ '```ts',
238
+ `await ${prefetchExampleCall};`,
239
+ '```',
240
+ ]);
241
+ statements.push(prefetchExport);
372
242
  }
243
+ const code = generateCode(statements);
244
+ const headerText = reactQueryEnabled
245
+ ? `Custom query hook for ${operation.name}`
246
+ : `Custom query functions for ${operation.name}`;
247
+ const content = getGeneratedFileHeader(headerText) + '\n\n' + code;
248
+ return {
249
+ fileName,
250
+ content,
251
+ operationName: operation.name,
252
+ };
373
253
  }
374
- /**
375
- * Generate all custom query hook files
376
- */
377
254
  export function generateAllCustomQueryHooks(options) {
378
- const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames } = options;
255
+ const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames, useCentralizedKeys = true, } = options;
379
256
  return operations
380
257
  .filter((op) => op.kind === 'query')
381
258
  .map((operation) => generateCustomQueryHook({
@@ -385,5 +262,6 @@ export function generateAllCustomQueryHooks(options) {
385
262
  skipQueryField,
386
263
  reactQueryEnabled,
387
264
  tableTypeNames,
265
+ useCentralizedKeys,
388
266
  }));
389
267
  }
@@ -69,3 +69,6 @@ export { generateAllMutationHooks, generateCreateMutationHook, generateUpdateMut
69
69
  export { generateAllCustomQueryHooks, generateCustomQueryHook, } from './custom-queries';
70
70
  export { generateAllCustomMutationHooks, generateCustomMutationHook, } from './custom-mutations';
71
71
  export { generateQueriesBarrel, generateMutationsBarrel, generateMainBarrel, generateCustomQueriesBarrel, generateCustomMutationsBarrel, } from './barrel';
72
+ export { generateQueryKeysFile } from './query-keys';
73
+ export { generateMutationKeysFile } from './mutation-keys';
74
+ export { generateInvalidationFile } from './invalidation';