@constructive-io/graphql-codegen 2.23.3 → 2.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +147 -2
  2. package/cli/codegen/babel-ast.d.ts +53 -0
  3. package/cli/codegen/babel-ast.js +160 -0
  4. package/cli/codegen/barrel.d.ts +7 -2
  5. package/cli/codegen/barrel.js +193 -102
  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 +236 -335
  11. package/cli/codegen/gql-ast.js +22 -1
  12. package/cli/codegen/index.d.ts +3 -0
  13. package/cli/codegen/index.js +73 -3
  14. package/cli/codegen/invalidation.d.ts +20 -0
  15. package/cli/codegen/invalidation.js +327 -0
  16. package/cli/codegen/mutation-keys.d.ts +24 -0
  17. package/cli/codegen/mutation-keys.js +247 -0
  18. package/cli/codegen/mutations.d.ts +5 -19
  19. package/cli/codegen/mutations.js +385 -383
  20. package/cli/codegen/orm/barrel.d.ts +1 -1
  21. package/cli/codegen/orm/barrel.js +42 -10
  22. package/cli/codegen/orm/client-generator.d.ts +1 -19
  23. package/cli/codegen/orm/client-generator.js +108 -77
  24. package/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  25. package/cli/codegen/orm/custom-ops-generator.js +192 -235
  26. package/cli/codegen/orm/input-types-generator.d.ts +13 -1
  27. package/cli/codegen/orm/input-types-generator.js +425 -147
  28. package/cli/codegen/orm/model-generator.d.ts +1 -19
  29. package/cli/codegen/orm/model-generator.js +229 -234
  30. package/cli/codegen/queries.d.ts +4 -12
  31. package/cli/codegen/queries.js +660 -390
  32. package/cli/codegen/query-keys.d.ts +15 -0
  33. package/cli/codegen/query-keys.js +477 -0
  34. package/cli/codegen/scalars.js +1 -0
  35. package/cli/codegen/schema-types-generator.d.ts +15 -10
  36. package/cli/codegen/schema-types-generator.js +87 -175
  37. package/cli/codegen/type-resolver.d.ts +1 -30
  38. package/cli/codegen/type-resolver.js +0 -53
  39. package/cli/codegen/types.d.ts +1 -1
  40. package/cli/codegen/types.js +76 -21
  41. package/cli/codegen/utils.d.ts +6 -0
  42. package/cli/codegen/utils.js +19 -0
  43. package/esm/cli/codegen/babel-ast.d.ts +53 -0
  44. package/esm/cli/codegen/babel-ast.js +111 -0
  45. package/esm/cli/codegen/barrel.d.ts +7 -2
  46. package/esm/cli/codegen/barrel.js +161 -103
  47. package/esm/cli/codegen/client.js +61 -0
  48. package/esm/cli/codegen/custom-mutations.d.ts +2 -12
  49. package/esm/cli/codegen/custom-mutations.js +83 -124
  50. package/esm/cli/codegen/custom-queries.d.ts +2 -10
  51. package/esm/cli/codegen/custom-queries.js +204 -336
  52. package/esm/cli/codegen/gql-ast.js +23 -2
  53. package/esm/cli/codegen/index.d.ts +3 -0
  54. package/esm/cli/codegen/index.js +69 -2
  55. package/esm/cli/codegen/invalidation.d.ts +20 -0
  56. package/esm/cli/codegen/invalidation.js +291 -0
  57. package/esm/cli/codegen/mutation-keys.d.ts +24 -0
  58. package/esm/cli/codegen/mutation-keys.js +211 -0
  59. package/esm/cli/codegen/mutations.d.ts +5 -19
  60. package/esm/cli/codegen/mutations.js +353 -384
  61. package/esm/cli/codegen/orm/barrel.d.ts +1 -1
  62. package/esm/cli/codegen/orm/barrel.js +10 -11
  63. package/esm/cli/codegen/orm/client-generator.d.ts +1 -19
  64. package/esm/cli/codegen/orm/client-generator.js +76 -78
  65. package/esm/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  66. package/esm/cli/codegen/orm/custom-ops-generator.js +160 -236
  67. package/esm/cli/codegen/orm/input-types-generator.d.ts +13 -1
  68. package/esm/cli/codegen/orm/input-types-generator.js +393 -148
  69. package/esm/cli/codegen/orm/model-generator.d.ts +1 -19
  70. package/esm/cli/codegen/orm/model-generator.js +197 -235
  71. package/esm/cli/codegen/queries.d.ts +4 -12
  72. package/esm/cli/codegen/queries.js +628 -391
  73. package/esm/cli/codegen/query-keys.d.ts +15 -0
  74. package/esm/cli/codegen/query-keys.js +441 -0
  75. package/esm/cli/codegen/scalars.js +1 -0
  76. package/esm/cli/codegen/schema-types-generator.d.ts +15 -10
  77. package/esm/cli/codegen/schema-types-generator.js +54 -175
  78. package/esm/cli/codegen/type-resolver.d.ts +1 -30
  79. package/esm/cli/codegen/type-resolver.js +0 -49
  80. package/esm/cli/codegen/types.d.ts +1 -1
  81. package/esm/cli/codegen/types.js +44 -22
  82. package/esm/cli/codegen/utils.d.ts +6 -0
  83. package/esm/cli/codegen/utils.js +18 -0
  84. package/esm/types/config.d.ts +75 -0
  85. package/esm/types/config.js +18 -0
  86. package/package.json +6 -4
  87. package/types/config.d.ts +75 -0
  88. package/types/config.js +19 -1
  89. package/cli/codegen/ts-ast.d.ts +0 -124
  90. package/cli/codegen/ts-ast.js +0 -280
  91. package/esm/cli/codegen/ts-ast.d.ts +0 -124
  92. package/esm/cli/codegen/ts-ast.js +0 -260
@@ -1,381 +1,248 @@
1
- import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, } from './ts-ast';
1
+ import * as t from '@babel/types';
2
+ import { generateCode, addJSDocComment, typedParam, createTypedCallExpression } 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([], createTypedCallExpression(t.identifier('execute'), [t.identifier(documentConstName), t.identifier('variables')], [
119
+ t.tsTypeReference(t.identifier(resultTypeName)),
120
+ t.tsTypeReference(t.identifier(variablesTypeName)),
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([], createTypedCallExpression(t.identifier('execute'), [t.identifier(documentConstName)], [t.tsTypeReference(t.identifier(resultTypeName))]))));
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(createTypedCallExpression(t.identifier('execute'), [t.identifier(documentConstName), t.identifier('variables'), t.identifier('options')], [
166
+ t.tsTypeReference(t.identifier(resultTypeName)),
167
+ t.tsTypeReference(t.identifier(variablesTypeName)),
168
+ ])));
203
169
  }
204
170
  else {
205
- // No variables
206
- return `return useQuery({
207
- queryKey: ${queryKeyName}(),
208
- queryFn: () => execute<${resultTypeName}>(${documentConstName}),
209
- ...options,
210
- });`;
171
+ fetchBodyStatements.push(t.returnStatement(createTypedCallExpression(t.identifier('execute'), [t.identifier(documentConstName), t.identifier('undefined'), t.identifier('options')], [t.tsTypeReference(t.identifier(resultTypeName))])));
211
172
  }
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;
173
+ const fetchParams = [];
222
174
  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
- \`\`\``;
175
+ fetchParams.push(typedParam('variables', t.tsTypeReference(t.identifier(variablesTypeName)), !hasRequiredArgs));
306
176
  }
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
- \`\`\``;
177
+ fetchParams.push(typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
178
+ const fetchFunc = t.functionDeclaration(t.identifier(fetchFnName), fetchParams, t.blockStatement(fetchBodyStatements));
179
+ fetchFunc.async = true;
180
+ fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsTypeReference(t.identifier(resultTypeName))])));
181
+ const fetchExport = t.exportNamedDeclaration(fetchFunc);
182
+ const argNames = operation.args.map((a) => a.name).join(', ');
183
+ const fetchExampleCall = hasArgs ? `${fetchFnName}({ ${argNames} })` : `${fetchFnName}()`;
184
+ addJSDocComment(fetchExport, [
185
+ `Fetch ${operation.name} without React hooks`,
186
+ '',
187
+ '@example',
188
+ '```ts',
189
+ `const data = await ${fetchExampleCall};`,
190
+ '```',
191
+ ]);
192
+ statements.push(fetchExport);
193
+ if (reactQueryEnabled) {
194
+ const prefetchFnName = `prefetch${ucFirst(operation.name)}Query`;
195
+ const prefetchBodyStatements = [];
196
+ const prefetchQueryOptions = [];
197
+ if (hasArgs) {
198
+ prefetchQueryOptions.push(t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(queryKeyName), [t.identifier('variables')])));
199
+ prefetchQueryOptions.push(t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(documentConstName), t.identifier('variables'), t.identifier('options')], [
200
+ t.tsTypeReference(t.identifier(resultTypeName)),
201
+ t.tsTypeReference(t.identifier(variablesTypeName)),
202
+ ]))));
203
+ }
204
+ else {
205
+ prefetchQueryOptions.push(t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(queryKeyName), [])));
206
+ prefetchQueryOptions.push(t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], createTypedCallExpression(t.identifier('execute'), [t.identifier(documentConstName), t.identifier('undefined'), t.identifier('options')], [t.tsTypeReference(t.identifier(resultTypeName))]))));
207
+ }
208
+ prefetchBodyStatements.push(t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [t.objectExpression(prefetchQueryOptions)]))));
209
+ const prefetchParams = [
210
+ typedParam('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
211
+ ];
212
+ if (hasArgs) {
213
+ prefetchParams.push(typedParam('variables', t.tsTypeReference(t.identifier(variablesTypeName)), !hasRequiredArgs));
214
+ }
215
+ prefetchParams.push(typedParam('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
216
+ const prefetchFunc = t.functionDeclaration(t.identifier(prefetchFnName), prefetchParams, t.blockStatement(prefetchBodyStatements));
217
+ prefetchFunc.async = true;
218
+ prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
219
+ const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
220
+ const prefetchExampleCall = hasArgs
221
+ ? `${prefetchFnName}(queryClient, { ${argNames} })`
222
+ : `${prefetchFnName}(queryClient)`;
223
+ addJSDocComment(prefetchExport, [
224
+ `Prefetch ${operation.name} for SSR or cache warming`,
225
+ '',
226
+ '@example',
227
+ '```ts',
228
+ `await ${prefetchExampleCall};`,
229
+ '```',
230
+ ]);
231
+ statements.push(prefetchExport);
372
232
  }
233
+ const code = generateCode(statements);
234
+ const headerText = reactQueryEnabled
235
+ ? `Custom query hook for ${operation.name}`
236
+ : `Custom query functions for ${operation.name}`;
237
+ const content = getGeneratedFileHeader(headerText) + '\n\n' + code;
238
+ return {
239
+ fileName,
240
+ content,
241
+ operationName: operation.name,
242
+ };
373
243
  }
374
- /**
375
- * Generate all custom query hook files
376
- */
377
244
  export function generateAllCustomQueryHooks(options) {
378
- const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames } = options;
245
+ const { operations, typeRegistry, maxDepth = 2, skipQueryField = true, reactQueryEnabled = true, tableTypeNames, useCentralizedKeys = true, } = options;
379
246
  return operations
380
247
  .filter((op) => op.kind === 'query')
381
248
  .map((operation) => generateCustomQueryHook({
@@ -385,5 +252,6 @@ export function generateAllCustomQueryHooks(options) {
385
252
  skipQueryField,
386
253
  reactQueryEnabled,
387
254
  tableTypeNames,
255
+ useCentralizedKeys,
388
256
  }));
389
257
  }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import * as t from 'gql-ast';
8
8
  import { print } from 'graphql';
9
- import { getTableNames, getAllRowsQueryName, getSingleRowQueryName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getFilterTypeName, getOrderByTypeName, getScalarFields, getPrimaryKeyInfo, ucFirst, } from './utils';
9
+ import { getTableNames, getAllRowsQueryName, getSingleRowQueryName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getFilterTypeName, getConditionTypeName, getOrderByTypeName, getScalarFields, getPrimaryKeyInfo, ucFirst, } from './utils';
10
10
  // ============================================================================
11
11
  // Field selection builders
12
12
  // ============================================================================
@@ -39,22 +39,39 @@ export function buildListQueryAST(config) {
39
39
  const { table } = config;
40
40
  const queryName = getAllRowsQueryName(table);
41
41
  const filterType = getFilterTypeName(table);
42
+ const conditionType = getConditionTypeName(table);
42
43
  const orderByType = getOrderByTypeName(table);
43
44
  const scalarFields = getScalarFields(table);
44
- // Variable definitions
45
+ // Variable definitions - all pagination arguments from PostGraphile
45
46
  const variableDefinitions = [
46
47
  t.variableDefinition({
47
48
  variable: t.variable({ name: 'first' }),
48
49
  type: t.namedType({ type: 'Int' }),
49
50
  }),
51
+ t.variableDefinition({
52
+ variable: t.variable({ name: 'last' }),
53
+ type: t.namedType({ type: 'Int' }),
54
+ }),
50
55
  t.variableDefinition({
51
56
  variable: t.variable({ name: 'offset' }),
52
57
  type: t.namedType({ type: 'Int' }),
53
58
  }),
59
+ t.variableDefinition({
60
+ variable: t.variable({ name: 'before' }),
61
+ type: t.namedType({ type: 'Cursor' }),
62
+ }),
63
+ t.variableDefinition({
64
+ variable: t.variable({ name: 'after' }),
65
+ type: t.namedType({ type: 'Cursor' }),
66
+ }),
54
67
  t.variableDefinition({
55
68
  variable: t.variable({ name: 'filter' }),
56
69
  type: t.namedType({ type: filterType }),
57
70
  }),
71
+ t.variableDefinition({
72
+ variable: t.variable({ name: 'condition' }),
73
+ type: t.namedType({ type: conditionType }),
74
+ }),
58
75
  t.variableDefinition({
59
76
  variable: t.variable({ name: 'orderBy' }),
60
77
  type: t.listType({
@@ -65,8 +82,12 @@ export function buildListQueryAST(config) {
65
82
  // Query arguments
66
83
  const args = [
67
84
  t.argument({ name: 'first', value: t.variable({ name: 'first' }) }),
85
+ t.argument({ name: 'last', value: t.variable({ name: 'last' }) }),
68
86
  t.argument({ name: 'offset', value: t.variable({ name: 'offset' }) }),
87
+ t.argument({ name: 'before', value: t.variable({ name: 'before' }) }),
88
+ t.argument({ name: 'after', value: t.variable({ name: 'after' }) }),
69
89
  t.argument({ name: 'filter', value: t.variable({ name: 'filter' }) }),
90
+ t.argument({ name: 'condition', value: t.variable({ name: 'condition' }) }),
70
91
  t.argument({ name: 'orderBy', value: t.variable({ name: 'orderBy' }) }),
71
92
  ];
72
93
  // Field selections