@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,36 +1,85 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.generateListQueryHook = generateListQueryHook;
4
37
  exports.generateSingleQueryHook = generateSingleQueryHook;
5
38
  exports.generateAllQueryHooks = generateAllQueryHooks;
6
- const ts_ast_1 = require("./ts-ast");
39
+ const t = __importStar(require("@babel/types"));
40
+ const babel_ast_1 = require("./babel-ast");
7
41
  const gql_ast_1 = require("./gql-ast");
8
42
  const utils_1 = require("./utils");
9
- // ============================================================================
10
- // List query hook generator
11
- // ============================================================================
12
- /**
13
- * Generate list query hook file content using AST
14
- */
43
+ function createUnionType(values) {
44
+ return t.tsUnionType(values.map((v) => t.tsLiteralType(t.stringLiteral(v))));
45
+ }
46
+ function createFilterInterfaceDeclaration(name, fieldFilters, isExported = true) {
47
+ const properties = [];
48
+ for (const filter of fieldFilters) {
49
+ const prop = t.tsPropertySignature(t.identifier(filter.fieldName), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filter.filterType))));
50
+ prop.optional = true;
51
+ properties.push(prop);
52
+ }
53
+ const andProp = t.tsPropertySignature(t.identifier('and'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(name)))));
54
+ andProp.optional = true;
55
+ properties.push(andProp);
56
+ const orProp = t.tsPropertySignature(t.identifier('or'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(name)))));
57
+ orProp.optional = true;
58
+ properties.push(orProp);
59
+ const notProp = t.tsPropertySignature(t.identifier('not'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(name))));
60
+ notProp.optional = true;
61
+ properties.push(notProp);
62
+ const body = t.tsInterfaceBody(properties);
63
+ const interfaceDecl = t.tsInterfaceDeclaration(t.identifier(name), null, null, body);
64
+ if (isExported) {
65
+ return t.exportNamedDeclaration(interfaceDecl);
66
+ }
67
+ return interfaceDecl;
68
+ }
15
69
  function generateListQueryHook(table, options = {}) {
16
- const { reactQueryEnabled = true } = options;
17
- const project = (0, ts_ast_1.createProject)();
70
+ const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
18
71
  const { typeName, pluralName } = (0, utils_1.getTableNames)(table);
19
72
  const hookName = (0, utils_1.getListQueryHookName)(table);
20
73
  const queryName = (0, utils_1.getAllRowsQueryName)(table);
21
74
  const filterTypeName = (0, utils_1.getFilterTypeName)(table);
75
+ const conditionTypeName = (0, utils_1.getConditionTypeName)(table);
22
76
  const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
23
77
  const scalarFields = (0, utils_1.getScalarFields)(table);
24
- // Generate GraphQL document via AST
78
+ const keysName = `${(0, utils_1.lcFirst)(typeName)}Keys`;
79
+ const scopeTypeName = `${typeName}Scope`;
25
80
  const queryAST = (0, gql_ast_1.buildListQueryAST)({ table });
26
81
  const queryDocument = (0, gql_ast_1.printGraphQL)(queryAST);
27
- const sourceFile = (0, ts_ast_1.createSourceFile)(project, (0, utils_1.getListQueryFileName)(table));
28
- // Add file header as leading comment
29
- const headerText = reactQueryEnabled
30
- ? `List query hook for ${typeName}`
31
- : `List query functions for ${typeName}`;
32
- sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)(headerText) + '\n\n');
33
- // Collect all filter types used by this table's fields
82
+ const statements = [];
34
83
  const filterTypesUsed = new Set();
35
84
  for (const field of scalarFields) {
36
85
  const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType, field.type.isArray);
@@ -38,47 +87,109 @@ function generateListQueryHook(table, options = {}) {
38
87
  filterTypesUsed.add(filterType);
39
88
  }
40
89
  }
41
- // Add imports - conditionally include React Query imports
42
- const imports = [];
43
90
  if (reactQueryEnabled) {
44
- imports.push((0, ts_ast_1.createImport)({
45
- moduleSpecifier: '@tanstack/react-query',
46
- namedImports: ['useQuery'],
47
- typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
48
- }));
91
+ const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
92
+ statements.push(reactQueryImport);
93
+ const reactQueryTypeImport = t.importDeclaration([
94
+ t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
95
+ t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
96
+ ], t.stringLiteral('@tanstack/react-query'));
97
+ reactQueryTypeImport.importKind = 'type';
98
+ statements.push(reactQueryTypeImport);
99
+ }
100
+ const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
101
+ statements.push(clientImport);
102
+ const clientTypeImport = t.importDeclaration([
103
+ t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions')),
104
+ ], t.stringLiteral('../client'));
105
+ clientTypeImport.importKind = 'type';
106
+ statements.push(clientTypeImport);
107
+ const typesImport = t.importDeclaration([
108
+ t.importSpecifier(t.identifier(typeName), t.identifier(typeName)),
109
+ ...Array.from(filterTypesUsed).map((ft) => t.importSpecifier(t.identifier(ft), t.identifier(ft))),
110
+ ], t.stringLiteral('../types'));
111
+ typesImport.importKind = 'type';
112
+ statements.push(typesImport);
113
+ if (useCentralizedKeys) {
114
+ const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
115
+ statements.push(queryKeyImport);
116
+ if (hasRelationships) {
117
+ const scopeTypeImport = t.importDeclaration([
118
+ t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName)),
119
+ ], t.stringLiteral('../query-keys'));
120
+ scopeTypeImport.importKind = 'type';
121
+ statements.push(scopeTypeImport);
122
+ }
49
123
  }
50
- imports.push((0, ts_ast_1.createImport)({
51
- moduleSpecifier: '../client',
52
- namedImports: ['execute'],
53
- typeOnlyNamedImports: ['ExecuteOptions'],
54
- }), (0, ts_ast_1.createImport)({
55
- moduleSpecifier: '../types',
56
- typeOnlyNamedImports: [typeName, ...Array.from(filterTypesUsed)],
57
- }));
58
- sourceFile.addImportDeclarations(imports);
59
- // Re-export entity type
60
- sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
61
- // Add section comment
62
- sourceFile.addStatements('\n// ============================================================================');
63
- sourceFile.addStatements('// GraphQL Document');
64
- sourceFile.addStatements('// ============================================================================\n');
65
- // Add query document constant
66
- sourceFile.addVariableStatement((0, ts_ast_1.createConst)(`${queryName}QueryDocument`, '`\n' + queryDocument + '`'));
67
- // Add section comment
68
- sourceFile.addStatements('\n// ============================================================================');
69
- sourceFile.addStatements('// Types');
70
- sourceFile.addStatements('// ============================================================================\n');
71
- // Generate filter interface
124
+ const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
125
+ reExportDecl.exportKind = 'type';
126
+ statements.push(reExportDecl);
127
+ const queryDocConst = t.variableDeclaration('const', [
128
+ t.variableDeclarator(t.identifier(`${queryName}QueryDocument`), t.templateLiteral([
129
+ t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true),
130
+ ], [])),
131
+ ]);
132
+ statements.push(t.exportNamedDeclaration(queryDocConst));
72
133
  const fieldFilters = scalarFields
73
134
  .map((field) => {
74
135
  const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType, field.type.isArray);
75
136
  return filterType ? { fieldName: field.name, filterType } : null;
76
137
  })
77
138
  .filter((f) => f !== null);
78
- // Note: Not exported to avoid conflicts with schema-types
79
- sourceFile.addInterface((0, ts_ast_1.createFilterInterface)(filterTypeName, fieldFilters, { isExported: false }));
80
- // Generate OrderBy type
81
- // Note: Not exported to avoid conflicts with schema-types
139
+ statements.push(createFilterInterfaceDeclaration(filterTypeName, fieldFilters, false));
140
+ // Generate Condition interface (simple equality filter with scalar types)
141
+ // Track non-primitive types (enums) that need to be imported
142
+ const enumTypesUsed = new Set();
143
+ const conditionProperties = scalarFields.map((field) => {
144
+ const tsType = (0, utils_1.fieldTypeToTs)(field.type);
145
+ const isPrimitive = tsType === 'string' ||
146
+ tsType === 'number' ||
147
+ tsType === 'boolean' ||
148
+ tsType === 'unknown' ||
149
+ tsType.endsWith('[]');
150
+ let typeAnnotation;
151
+ if (field.type.isArray) {
152
+ const baseType = tsType.replace('[]', '');
153
+ const isBasePrimitive = baseType === 'string' ||
154
+ baseType === 'number' ||
155
+ baseType === 'boolean' ||
156
+ baseType === 'unknown';
157
+ if (!isBasePrimitive) {
158
+ enumTypesUsed.add(baseType);
159
+ }
160
+ typeAnnotation = t.tsArrayType(baseType === 'string'
161
+ ? t.tsStringKeyword()
162
+ : baseType === 'number'
163
+ ? t.tsNumberKeyword()
164
+ : baseType === 'boolean'
165
+ ? t.tsBooleanKeyword()
166
+ : t.tsTypeReference(t.identifier(baseType)));
167
+ }
168
+ else {
169
+ if (!isPrimitive) {
170
+ enumTypesUsed.add(tsType);
171
+ }
172
+ typeAnnotation =
173
+ tsType === 'string'
174
+ ? t.tsStringKeyword()
175
+ : tsType === 'number'
176
+ ? t.tsNumberKeyword()
177
+ : tsType === 'boolean'
178
+ ? t.tsBooleanKeyword()
179
+ : t.tsTypeReference(t.identifier(tsType));
180
+ }
181
+ const prop = t.tsPropertySignature(t.identifier(field.name), t.tsTypeAnnotation(typeAnnotation));
182
+ prop.optional = true;
183
+ return prop;
184
+ });
185
+ // Add import for enum types if any are used
186
+ if (enumTypesUsed.size > 0) {
187
+ const schemaTypesImport = t.importDeclaration(Array.from(enumTypesUsed).map((et) => t.importSpecifier(t.identifier(et), t.identifier(et))), t.stringLiteral('../schema-types'));
188
+ schemaTypesImport.importKind = 'type';
189
+ statements.push(schemaTypesImport);
190
+ }
191
+ const conditionInterface = t.tsInterfaceDeclaration(t.identifier(conditionTypeName), null, null, t.tsInterfaceBody(conditionProperties));
192
+ statements.push(conditionInterface);
82
193
  const orderByValues = [
83
194
  ...scalarFields.flatMap((f) => [
84
195
  `${(0, utils_1.toScreamingSnake)(f.name)}_ASC`,
@@ -88,378 +199,537 @@ function generateListQueryHook(table, options = {}) {
88
199
  'PRIMARY_KEY_ASC',
89
200
  'PRIMARY_KEY_DESC',
90
201
  ];
91
- sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(orderByTypeName, (0, ts_ast_1.createUnionType)(orderByValues), { isExported: false }));
92
- // Variables interface
93
- const variablesProps = [
94
- { name: 'first', type: 'number', optional: true },
95
- { name: 'offset', type: 'number', optional: true },
96
- { name: 'filter', type: filterTypeName, optional: true },
97
- { name: 'orderBy', type: `${orderByTypeName}[]`, optional: true },
98
- ];
99
- sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`, variablesProps));
100
- // Result interface
101
- const resultProps = [
102
- {
103
- name: queryName,
104
- type: `{
105
- totalCount: number;
106
- nodes: ${typeName}[];
107
- pageInfo: {
108
- hasNextPage: boolean;
109
- hasPreviousPage: boolean;
110
- startCursor: string | null;
111
- endCursor: string | null;
112
- };
113
- }`,
114
- },
115
- ];
116
- sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`, resultProps));
117
- // Add section comment
118
- sourceFile.addStatements('\n// ============================================================================');
119
- sourceFile.addStatements('// Query Key');
120
- sourceFile.addStatements('// ============================================================================\n');
121
- // Query key factory
122
- sourceFile.addVariableStatement((0, ts_ast_1.createConst)(`${queryName}QueryKey`, `(variables?: ${(0, utils_1.ucFirst)(pluralName)}QueryVariables) =>
123
- ['${typeName.toLowerCase()}', 'list', variables] as const`));
124
- // Add React Query hook section (only if enabled)
202
+ const orderByTypeAlias = t.tsTypeAliasDeclaration(t.identifier(orderByTypeName), null, createUnionType(orderByValues));
203
+ statements.push(orderByTypeAlias);
204
+ const variablesInterfaceBody = t.tsInterfaceBody([
205
+ (() => {
206
+ const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
207
+ p.optional = true;
208
+ return p;
209
+ })(),
210
+ (() => {
211
+ const p = t.tsPropertySignature(t.identifier('last'), t.tsTypeAnnotation(t.tsNumberKeyword()));
212
+ p.optional = true;
213
+ return p;
214
+ })(),
215
+ (() => {
216
+ const p = t.tsPropertySignature(t.identifier('offset'), t.tsTypeAnnotation(t.tsNumberKeyword()));
217
+ p.optional = true;
218
+ return p;
219
+ })(),
220
+ (() => {
221
+ const p = t.tsPropertySignature(t.identifier('before'), t.tsTypeAnnotation(t.tsStringKeyword()));
222
+ p.optional = true;
223
+ return p;
224
+ })(),
225
+ (() => {
226
+ const p = t.tsPropertySignature(t.identifier('after'), t.tsTypeAnnotation(t.tsStringKeyword()));
227
+ p.optional = true;
228
+ return p;
229
+ })(),
230
+ (() => {
231
+ const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterTypeName))));
232
+ p.optional = true;
233
+ return p;
234
+ })(),
235
+ (() => {
236
+ const p = t.tsPropertySignature(t.identifier('condition'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(conditionTypeName))));
237
+ p.optional = true;
238
+ return p;
239
+ })(),
240
+ (() => {
241
+ const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByTypeName)))));
242
+ p.optional = true;
243
+ return p;
244
+ })(),
245
+ ]);
246
+ const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`), null, null, variablesInterfaceBody);
247
+ statements.push(t.exportNamedDeclaration(variablesInterface));
248
+ const pageInfoType = t.tsTypeLiteral([
249
+ t.tsPropertySignature(t.identifier('hasNextPage'), t.tsTypeAnnotation(t.tsBooleanKeyword())),
250
+ t.tsPropertySignature(t.identifier('hasPreviousPage'), t.tsTypeAnnotation(t.tsBooleanKeyword())),
251
+ t.tsPropertySignature(t.identifier('startCursor'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))),
252
+ t.tsPropertySignature(t.identifier('endCursor'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))),
253
+ ]);
254
+ const resultType = t.tsTypeLiteral([
255
+ t.tsPropertySignature(t.identifier('totalCount'), t.tsTypeAnnotation(t.tsNumberKeyword())),
256
+ t.tsPropertySignature(t.identifier('nodes'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(typeName))))),
257
+ t.tsPropertySignature(t.identifier('pageInfo'), t.tsTypeAnnotation(pageInfoType)),
258
+ ]);
259
+ const resultInterfaceBody = t.tsInterfaceBody([
260
+ t.tsPropertySignature(t.identifier(queryName), t.tsTypeAnnotation(resultType)),
261
+ ]);
262
+ const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`), null, null, resultInterfaceBody);
263
+ statements.push(t.exportNamedDeclaration(resultInterface));
264
+ if (useCentralizedKeys) {
265
+ const queryKeyConst = t.variableDeclaration('const', [
266
+ t.variableDeclarator(t.identifier(`${queryName}QueryKey`), t.memberExpression(t.identifier(keysName), t.identifier('list'))),
267
+ ]);
268
+ const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
269
+ (0, babel_ast_1.addJSDocComment)(queryKeyExport, [
270
+ 'Query key factory - re-exported from query-keys.ts',
271
+ ]);
272
+ statements.push(queryKeyExport);
273
+ }
274
+ else {
275
+ const queryKeyArrow = t.arrowFunctionExpression([
276
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)), true),
277
+ ], t.tsAsExpression(t.arrayExpression([
278
+ t.stringLiteral(typeName.toLowerCase()),
279
+ t.stringLiteral('list'),
280
+ t.identifier('variables'),
281
+ ]), t.tsTypeReference(t.identifier('const'))));
282
+ const queryKeyConst = t.variableDeclaration('const', [
283
+ t.variableDeclarator(t.identifier(`${queryName}QueryKey`), queryKeyArrow),
284
+ ]);
285
+ statements.push(t.exportNamedDeclaration(queryKeyConst));
286
+ }
125
287
  if (reactQueryEnabled) {
126
- sourceFile.addStatements('\n// ============================================================================');
127
- sourceFile.addStatements('// Hook');
128
- sourceFile.addStatements('// ============================================================================\n');
129
- // Hook function
130
- sourceFile.addFunction({
131
- name: hookName,
132
- isExported: true,
133
- parameters: [
134
- {
135
- name: 'variables',
136
- type: `${(0, utils_1.ucFirst)(pluralName)}QueryVariables`,
137
- hasQuestionToken: true,
138
- },
139
- {
140
- name: 'options',
141
- type: `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
142
- hasQuestionToken: true,
143
- },
144
- ],
145
- statements: `return useQuery({
146
- queryKey: ${queryName}QueryKey(variables),
147
- queryFn: () => execute<${(0, utils_1.ucFirst)(pluralName)}QueryResult, ${(0, utils_1.ucFirst)(pluralName)}QueryVariables>(
148
- ${queryName}QueryDocument,
149
- variables
150
- ),
151
- ...options,
152
- });`,
153
- docs: [
154
- {
155
- description: `Query hook for fetching ${typeName} list
156
-
157
- @example
158
- \`\`\`tsx
159
- const { data, isLoading } = ${hookName}({
160
- first: 10,
161
- filter: { name: { equalTo: "example" } },
162
- orderBy: ['CREATED_AT_DESC'],
163
- });
164
- \`\`\``,
165
- },
166
- ],
167
- });
288
+ const hookBodyStatements = [];
289
+ if (hasRelationships && useCentralizedKeys) {
290
+ hookBodyStatements.push(t.variableDeclaration('const', [
291
+ t.variableDeclarator(t.objectPattern([
292
+ t.objectProperty(t.identifier('scope'), t.identifier('scope'), false, true),
293
+ t.restElement(t.identifier('queryOptions')),
294
+ ]), t.logicalExpression('??', t.identifier('options'), t.objectExpression([]))),
295
+ ]));
296
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
297
+ t.objectExpression([
298
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')])),
299
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
300
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
301
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)),
302
+ ]))),
303
+ t.spreadElement(t.identifier('queryOptions')),
304
+ ]),
305
+ ])));
306
+ }
307
+ else if (useCentralizedKeys) {
308
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
309
+ t.objectExpression([
310
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')])),
311
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
312
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
313
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)),
314
+ ]))),
315
+ t.spreadElement(t.identifier('options')),
316
+ ]),
317
+ ])));
318
+ }
319
+ else {
320
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
321
+ t.objectExpression([
322
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
323
+ t.identifier('variables'),
324
+ ])),
325
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
326
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
327
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)),
328
+ ]))),
329
+ t.spreadElement(t.identifier('options')),
330
+ ]),
331
+ ])));
332
+ }
333
+ const hookParams = [
334
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)), true),
335
+ ];
336
+ let optionsTypeStr;
337
+ if (hasRelationships && useCentralizedKeys) {
338
+ optionsTypeStr = `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'> & { scope?: ${scopeTypeName} }`;
339
+ }
340
+ else {
341
+ optionsTypeStr = `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`;
342
+ }
343
+ const optionsParam = t.identifier('options');
344
+ optionsParam.optional = true;
345
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
346
+ hookParams.push(optionsParam);
347
+ const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
348
+ const hookExport = t.exportNamedDeclaration(hookFunc);
349
+ const docLines = [
350
+ `Query hook for fetching ${typeName} list`,
351
+ '',
352
+ '@example',
353
+ '```tsx',
354
+ `const { data, isLoading } = ${hookName}({`,
355
+ ' first: 10,',
356
+ ' filter: { name: { equalTo: "example" } },',
357
+ " orderBy: ['CREATED_AT_DESC'],",
358
+ '});',
359
+ '```',
360
+ ];
361
+ if (hasRelationships && useCentralizedKeys) {
362
+ docLines.push('');
363
+ docLines.push('@example With scope for hierarchical cache invalidation');
364
+ docLines.push('```tsx');
365
+ docLines.push(`const { data } = ${hookName}(`);
366
+ docLines.push(' { first: 10 },');
367
+ docLines.push(" { scope: { parentId: 'parent-id' } }");
368
+ docLines.push(');');
369
+ docLines.push('```');
370
+ }
371
+ (0, babel_ast_1.addJSDocComment)(hookExport, docLines);
372
+ statements.push(hookExport);
168
373
  }
169
- // Add section comment for standalone functions
170
- sourceFile.addStatements('\n// ============================================================================');
171
- sourceFile.addStatements('// Standalone Functions (non-React)');
172
- sourceFile.addStatements('// ============================================================================\n');
173
- // Fetch function (standalone, no React)
174
- sourceFile.addFunction({
175
- name: `fetch${(0, utils_1.ucFirst)(pluralName)}Query`,
176
- isExported: true,
177
- isAsync: true,
178
- parameters: [
179
- {
180
- name: 'variables',
181
- type: `${(0, utils_1.ucFirst)(pluralName)}QueryVariables`,
182
- hasQuestionToken: true,
183
- },
184
- {
185
- name: 'options',
186
- type: 'ExecuteOptions',
187
- hasQuestionToken: true,
188
- },
189
- ],
190
- returnType: `Promise<${(0, utils_1.ucFirst)(pluralName)}QueryResult>`,
191
- statements: `return execute<${(0, utils_1.ucFirst)(pluralName)}QueryResult, ${(0, utils_1.ucFirst)(pluralName)}QueryVariables>(
192
- ${queryName}QueryDocument,
193
- variables,
194
- options
195
- );`,
196
- docs: [
197
- {
198
- description: `Fetch ${typeName} list without React hooks
199
-
200
- @example
201
- \`\`\`ts
202
- // Direct fetch
203
- const data = await fetch${(0, utils_1.ucFirst)(pluralName)}Query({ first: 10 });
204
-
205
- // With QueryClient
206
- const data = await queryClient.fetchQuery({
207
- queryKey: ${queryName}QueryKey(variables),
208
- queryFn: () => fetch${(0, utils_1.ucFirst)(pluralName)}Query(variables),
209
- });
210
- \`\`\``,
211
- },
212
- ],
213
- });
214
- // Prefetch function (for SSR/QueryClient) - only if React Query is enabled
374
+ const fetchFuncBody = t.blockStatement([
375
+ t.returnStatement((0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
376
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
377
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)),
378
+ ])),
379
+ ]);
380
+ const fetchFunc = t.functionDeclaration(t.identifier(`fetch${(0, utils_1.ucFirst)(pluralName)}Query`), [
381
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)), true),
382
+ (0, babel_ast_1.typedParam)('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true),
383
+ ], fetchFuncBody);
384
+ fetchFunc.async = true;
385
+ fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
386
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
387
+ ])));
388
+ const fetchExport = t.exportNamedDeclaration(fetchFunc);
389
+ (0, babel_ast_1.addJSDocComment)(fetchExport, [
390
+ `Fetch ${typeName} list without React hooks`,
391
+ '',
392
+ '@example',
393
+ '```ts',
394
+ '// Direct fetch',
395
+ `const data = await fetch${(0, utils_1.ucFirst)(pluralName)}Query({ first: 10 });`,
396
+ '',
397
+ '// With QueryClient',
398
+ 'const data = await queryClient.fetchQuery({',
399
+ ` queryKey: ${queryName}QueryKey(variables),`,
400
+ ` queryFn: () => fetch${(0, utils_1.ucFirst)(pluralName)}Query(variables),`,
401
+ '});',
402
+ '```',
403
+ ]);
404
+ statements.push(fetchExport);
215
405
  if (reactQueryEnabled) {
216
- sourceFile.addFunction({
217
- name: `prefetch${(0, utils_1.ucFirst)(pluralName)}Query`,
218
- isExported: true,
219
- isAsync: true,
220
- parameters: [
221
- {
222
- name: 'queryClient',
223
- type: 'QueryClient',
224
- },
225
- {
226
- name: 'variables',
227
- type: `${(0, utils_1.ucFirst)(pluralName)}QueryVariables`,
228
- hasQuestionToken: true,
229
- },
230
- {
231
- name: 'options',
232
- type: 'ExecuteOptions',
233
- hasQuestionToken: true,
234
- },
235
- ],
236
- returnType: 'Promise<void>',
237
- statements: `await queryClient.prefetchQuery({
238
- queryKey: ${queryName}QueryKey(variables),
239
- queryFn: () => execute<${(0, utils_1.ucFirst)(pluralName)}QueryResult, ${(0, utils_1.ucFirst)(pluralName)}QueryVariables>(
240
- ${queryName}QueryDocument,
241
- variables,
242
- options
243
- ),
244
- });`,
245
- docs: [
246
- {
247
- description: `Prefetch ${typeName} list for SSR or cache warming
248
-
249
- @example
250
- \`\`\`ts
251
- await prefetch${(0, utils_1.ucFirst)(pluralName)}Query(queryClient, { first: 10 });
252
- \`\`\``,
253
- },
254
- ],
255
- });
406
+ const prefetchParams = [
407
+ (0, babel_ast_1.typedParam)('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
408
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)), true),
409
+ ];
410
+ if (hasRelationships && useCentralizedKeys) {
411
+ prefetchParams.push((0, babel_ast_1.typedParam)('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true));
412
+ }
413
+ prefetchParams.push((0, babel_ast_1.typedParam)('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
414
+ let prefetchQueryKeyExpr;
415
+ if (hasRelationships && useCentralizedKeys) {
416
+ prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')]);
417
+ }
418
+ else if (useCentralizedKeys) {
419
+ prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')]);
420
+ }
421
+ else {
422
+ prefetchQueryKeyExpr = t.callExpression(t.identifier(`${queryName}QueryKey`), [t.identifier('variables')]);
423
+ }
424
+ const prefetchFuncBody = t.blockStatement([
425
+ t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
426
+ t.objectExpression([
427
+ t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
428
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
429
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
430
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)),
431
+ ]))),
432
+ ]),
433
+ ]))),
434
+ ]);
435
+ const prefetchFunc = t.functionDeclaration(t.identifier(`prefetch${(0, utils_1.ucFirst)(pluralName)}Query`), prefetchParams, prefetchFuncBody);
436
+ prefetchFunc.async = true;
437
+ prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
438
+ const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
439
+ (0, babel_ast_1.addJSDocComment)(prefetchExport, [
440
+ `Prefetch ${typeName} list for SSR or cache warming`,
441
+ '',
442
+ '@example',
443
+ '```ts',
444
+ `await prefetch${(0, utils_1.ucFirst)(pluralName)}Query(queryClient, { first: 10 });`,
445
+ '```',
446
+ ]);
447
+ statements.push(prefetchExport);
256
448
  }
449
+ const code = (0, babel_ast_1.generateCode)(statements);
450
+ const headerText = reactQueryEnabled
451
+ ? `List query hook for ${typeName}`
452
+ : `List query functions for ${typeName}`;
453
+ const content = (0, utils_1.getGeneratedFileHeader)(headerText) + '\n\n' + code;
257
454
  return {
258
455
  fileName: (0, utils_1.getListQueryFileName)(table),
259
- content: (0, ts_ast_1.getFormattedOutput)(sourceFile),
456
+ content,
260
457
  };
261
458
  }
262
- // ============================================================================
263
- // Single item query hook generator
264
- // ============================================================================
265
- /**
266
- * Generate single item query hook file content using AST
267
- */
268
459
  function generateSingleQueryHook(table, options = {}) {
269
- const { reactQueryEnabled = true } = options;
270
- const project = (0, ts_ast_1.createProject)();
460
+ // Skip tables with composite keys - they are handled as custom queries
461
+ if (!(0, utils_1.hasValidPrimaryKey)(table)) {
462
+ return null;
463
+ }
464
+ const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
271
465
  const { typeName, singularName } = (0, utils_1.getTableNames)(table);
272
466
  const hookName = (0, utils_1.getSingleQueryHookName)(table);
273
467
  const queryName = (0, utils_1.getSingleRowQueryName)(table);
274
- // Get primary key info dynamically from table constraints
468
+ const keysName = `${(0, utils_1.lcFirst)(typeName)}Keys`;
469
+ const scopeTypeName = `${typeName}Scope`;
275
470
  const pkFields = (0, utils_1.getPrimaryKeyInfo)(table);
276
- // For simplicity, use first PK field (most common case)
277
- // Composite PKs would need more complex handling
278
471
  const pkField = pkFields[0];
279
472
  const pkName = pkField.name;
280
473
  const pkTsType = pkField.tsType;
281
- // Generate GraphQL document via AST
282
474
  const queryAST = (0, gql_ast_1.buildSingleQueryAST)({ table });
283
475
  const queryDocument = (0, gql_ast_1.printGraphQL)(queryAST);
284
- const sourceFile = (0, ts_ast_1.createSourceFile)(project, (0, utils_1.getSingleQueryFileName)(table));
285
- // Add file header
286
- const headerText = reactQueryEnabled
287
- ? `Single item query hook for ${typeName}`
288
- : `Single item query functions for ${typeName}`;
289
- sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)(headerText) + '\n\n');
290
- // Add imports - conditionally include React Query imports
291
- const imports = [];
476
+ const statements = [];
292
477
  if (reactQueryEnabled) {
293
- imports.push((0, ts_ast_1.createImport)({
294
- moduleSpecifier: '@tanstack/react-query',
295
- namedImports: ['useQuery'],
296
- typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
297
- }));
478
+ const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
479
+ statements.push(reactQueryImport);
480
+ const reactQueryTypeImport = t.importDeclaration([
481
+ t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
482
+ t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
483
+ ], t.stringLiteral('@tanstack/react-query'));
484
+ reactQueryTypeImport.importKind = 'type';
485
+ statements.push(reactQueryTypeImport);
486
+ }
487
+ const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
488
+ statements.push(clientImport);
489
+ const clientTypeImport = t.importDeclaration([
490
+ t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions')),
491
+ ], t.stringLiteral('../client'));
492
+ clientTypeImport.importKind = 'type';
493
+ statements.push(clientTypeImport);
494
+ const typesImport = t.importDeclaration([t.importSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
495
+ typesImport.importKind = 'type';
496
+ statements.push(typesImport);
497
+ if (useCentralizedKeys) {
498
+ const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
499
+ statements.push(queryKeyImport);
500
+ if (hasRelationships) {
501
+ const scopeTypeImport = t.importDeclaration([
502
+ t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName)),
503
+ ], t.stringLiteral('../query-keys'));
504
+ scopeTypeImport.importKind = 'type';
505
+ statements.push(scopeTypeImport);
506
+ }
507
+ }
508
+ const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
509
+ reExportDecl.exportKind = 'type';
510
+ statements.push(reExportDecl);
511
+ const queryDocConst = t.variableDeclaration('const', [
512
+ t.variableDeclarator(t.identifier(`${queryName}QueryDocument`), t.templateLiteral([
513
+ t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true),
514
+ ], [])),
515
+ ]);
516
+ statements.push(t.exportNamedDeclaration(queryDocConst));
517
+ const pkTypeAnnotation = pkTsType === 'string'
518
+ ? t.tsStringKeyword()
519
+ : pkTsType === 'number'
520
+ ? t.tsNumberKeyword()
521
+ : t.tsTypeReference(t.identifier(pkTsType));
522
+ const variablesInterfaceBody = t.tsInterfaceBody([
523
+ t.tsPropertySignature(t.identifier(pkName), t.tsTypeAnnotation(pkTypeAnnotation)),
524
+ ]);
525
+ const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`), null, null, variablesInterfaceBody);
526
+ statements.push(t.exportNamedDeclaration(variablesInterface));
527
+ const resultInterfaceBody = t.tsInterfaceBody([
528
+ t.tsPropertySignature(t.identifier(queryName), t.tsTypeAnnotation(t.tsUnionType([
529
+ t.tsTypeReference(t.identifier(typeName)),
530
+ t.tsNullKeyword(),
531
+ ]))),
532
+ ]);
533
+ const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`), null, null, resultInterfaceBody);
534
+ statements.push(t.exportNamedDeclaration(resultInterface));
535
+ if (useCentralizedKeys) {
536
+ const queryKeyConst = t.variableDeclaration('const', [
537
+ t.variableDeclarator(t.identifier(`${queryName}QueryKey`), t.memberExpression(t.identifier(keysName), t.identifier('detail'))),
538
+ ]);
539
+ const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
540
+ (0, babel_ast_1.addJSDocComment)(queryKeyExport, [
541
+ 'Query key factory - re-exported from query-keys.ts',
542
+ ]);
543
+ statements.push(queryKeyExport);
544
+ }
545
+ else {
546
+ const queryKeyArrow = t.arrowFunctionExpression([(0, babel_ast_1.typedParam)(pkName, pkTypeAnnotation)], t.tsAsExpression(t.arrayExpression([
547
+ t.stringLiteral(typeName.toLowerCase()),
548
+ t.stringLiteral('detail'),
549
+ t.identifier(pkName),
550
+ ]), t.tsTypeReference(t.identifier('const'))));
551
+ const queryKeyConst = t.variableDeclaration('const', [
552
+ t.variableDeclarator(t.identifier(`${queryName}QueryKey`), queryKeyArrow),
553
+ ]);
554
+ statements.push(t.exportNamedDeclaration(queryKeyConst));
298
555
  }
299
- imports.push((0, ts_ast_1.createImport)({
300
- moduleSpecifier: '../client',
301
- namedImports: ['execute'],
302
- typeOnlyNamedImports: ['ExecuteOptions'],
303
- }), (0, ts_ast_1.createImport)({
304
- moduleSpecifier: '../types',
305
- typeOnlyNamedImports: [typeName],
306
- }));
307
- sourceFile.addImportDeclarations(imports);
308
- // Re-export entity type
309
- sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
310
- // Add section comment
311
- sourceFile.addStatements('\n// ============================================================================');
312
- sourceFile.addStatements('// GraphQL Document');
313
- sourceFile.addStatements('// ============================================================================\n');
314
- // Add query document constant
315
- sourceFile.addVariableStatement((0, ts_ast_1.createConst)(`${queryName}QueryDocument`, '`\n' + queryDocument + '`'));
316
- // Add section comment
317
- sourceFile.addStatements('\n// ============================================================================');
318
- sourceFile.addStatements('// Types');
319
- sourceFile.addStatements('// ============================================================================\n');
320
- // Variables interface - use dynamic PK field name and type
321
- sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`, [
322
- { name: pkName, type: pkTsType },
323
- ]));
324
- // Result interface
325
- sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(singularName)}QueryResult`, [
326
- { name: queryName, type: `${typeName} | null` },
327
- ]));
328
- // Add section comment
329
- sourceFile.addStatements('\n// ============================================================================');
330
- sourceFile.addStatements('// Query Key');
331
- sourceFile.addStatements('// ============================================================================\n');
332
- // Query key factory - use dynamic PK field name and type
333
- sourceFile.addVariableStatement((0, ts_ast_1.createConst)(`${queryName}QueryKey`, `(${pkName}: ${pkTsType}) =>
334
- ['${typeName.toLowerCase()}', 'detail', ${pkName}] as const`));
335
- // Add React Query hook section (only if enabled)
336
556
  if (reactQueryEnabled) {
337
- sourceFile.addStatements('\n// ============================================================================');
338
- sourceFile.addStatements('// Hook');
339
- sourceFile.addStatements('// ============================================================================\n');
340
- // Hook function - use dynamic PK field name and type
341
- sourceFile.addFunction({
342
- name: hookName,
343
- isExported: true,
344
- parameters: [
345
- { name: pkName, type: pkTsType },
346
- {
347
- name: 'options',
348
- type: `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
349
- hasQuestionToken: true,
350
- },
351
- ],
352
- statements: `return useQuery({
353
- queryKey: ${queryName}QueryKey(${pkName}),
354
- queryFn: () => execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
355
- ${queryName}QueryDocument,
356
- { ${pkName} }
357
- ),
358
- enabled: !!${pkName} && (options?.enabled !== false),
359
- ...options,
360
- });`,
361
- docs: [
362
- {
363
- description: `Query hook for fetching a single ${typeName} by primary key
364
-
365
- @example
366
- \`\`\`tsx
367
- const { data, isLoading } = ${hookName}(${pkTsType === 'string' ? "'value-here'" : '123'});
368
-
369
- if (data?.${queryName}) {
370
- console.log(data.${queryName}.${pkName});
371
- }
372
- \`\`\``,
373
- },
374
- ],
375
- });
557
+ const hookBodyStatements = [];
558
+ if (hasRelationships && useCentralizedKeys) {
559
+ hookBodyStatements.push(t.variableDeclaration('const', [
560
+ t.variableDeclarator(t.objectPattern([
561
+ t.objectProperty(t.identifier('scope'), t.identifier('scope'), false, true),
562
+ t.restElement(t.identifier('queryOptions')),
563
+ ]), t.logicalExpression('??', t.identifier('options'), t.objectExpression([]))),
564
+ ]));
565
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
566
+ t.objectExpression([
567
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
568
+ t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
569
+ t.identifier('scope'),
570
+ ])),
571
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
572
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
573
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`)),
574
+ ]))),
575
+ t.spreadElement(t.identifier('queryOptions')),
576
+ ]),
577
+ ])));
578
+ }
579
+ else if (useCentralizedKeys) {
580
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
581
+ t.objectExpression([
582
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
583
+ t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
584
+ ])),
585
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
586
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
587
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`)),
588
+ ]))),
589
+ t.spreadElement(t.identifier('options')),
590
+ ]),
591
+ ])));
592
+ }
593
+ else {
594
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
595
+ t.objectExpression([
596
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
597
+ t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
598
+ ])),
599
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables')], [
600
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
601
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`)),
602
+ ]))),
603
+ t.spreadElement(t.identifier('options')),
604
+ ]),
605
+ ])));
606
+ }
607
+ const hookParams = [
608
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`))),
609
+ ];
610
+ let optionsTypeStr;
611
+ if (hasRelationships && useCentralizedKeys) {
612
+ optionsTypeStr = `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'> & { scope?: ${scopeTypeName} }`;
613
+ }
614
+ else {
615
+ optionsTypeStr = `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`;
616
+ }
617
+ const optionsParam = t.identifier('options');
618
+ optionsParam.optional = true;
619
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
620
+ hookParams.push(optionsParam);
621
+ const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
622
+ const hookExport = t.exportNamedDeclaration(hookFunc);
623
+ const docLines = [
624
+ `Query hook for fetching a single ${typeName}`,
625
+ '',
626
+ '@example',
627
+ '```tsx',
628
+ `const { data, isLoading } = ${hookName}({ ${pkName}: 'some-id' });`,
629
+ '```',
630
+ ];
631
+ if (hasRelationships && useCentralizedKeys) {
632
+ docLines.push('');
633
+ docLines.push('@example With scope for hierarchical cache invalidation');
634
+ docLines.push('```tsx');
635
+ docLines.push(`const { data } = ${hookName}(`);
636
+ docLines.push(` { ${pkName}: 'some-id' },`);
637
+ docLines.push(" { scope: { parentId: 'parent-id' } }");
638
+ docLines.push(');');
639
+ docLines.push('```');
640
+ }
641
+ (0, babel_ast_1.addJSDocComment)(hookExport, docLines);
642
+ statements.push(hookExport);
376
643
  }
377
- // Add section comment for standalone functions
378
- sourceFile.addStatements('\n// ============================================================================');
379
- sourceFile.addStatements('// Standalone Functions (non-React)');
380
- sourceFile.addStatements('// ============================================================================\n');
381
- // Fetch function (standalone, no React) - use dynamic PK
382
- sourceFile.addFunction({
383
- name: `fetch${(0, utils_1.ucFirst)(singularName)}Query`,
384
- isExported: true,
385
- isAsync: true,
386
- parameters: [
387
- { name: pkName, type: pkTsType },
388
- {
389
- name: 'options',
390
- type: 'ExecuteOptions',
391
- hasQuestionToken: true,
392
- },
393
- ],
394
- returnType: `Promise<${(0, utils_1.ucFirst)(singularName)}QueryResult>`,
395
- statements: `return execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
396
- ${queryName}QueryDocument,
397
- { ${pkName} },
398
- options
399
- );`,
400
- docs: [
401
- {
402
- description: `Fetch a single ${typeName} by primary key without React hooks
403
-
404
- @example
405
- \`\`\`ts
406
- const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query(${pkTsType === 'string' ? "'value-here'" : '123'});
407
- \`\`\``,
408
- },
409
- ],
410
- });
411
- // Prefetch function (for SSR/QueryClient) - only if React Query is enabled, use dynamic PK
644
+ const fetchFuncBody = t.blockStatement([
645
+ t.returnStatement((0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
646
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
647
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`)),
648
+ ])),
649
+ ]);
650
+ const fetchFunc = t.functionDeclaration(t.identifier(`fetch${(0, utils_1.ucFirst)(singularName)}Query`), [
651
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`))),
652
+ (0, babel_ast_1.typedParam)('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true),
653
+ ], fetchFuncBody);
654
+ fetchFunc.async = true;
655
+ fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
656
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
657
+ ])));
658
+ const fetchExport = t.exportNamedDeclaration(fetchFunc);
659
+ (0, babel_ast_1.addJSDocComment)(fetchExport, [
660
+ `Fetch a single ${typeName} without React hooks`,
661
+ '',
662
+ '@example',
663
+ '```ts',
664
+ `const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query({ ${pkName}: 'some-id' });`,
665
+ '```',
666
+ ]);
667
+ statements.push(fetchExport);
412
668
  if (reactQueryEnabled) {
413
- sourceFile.addFunction({
414
- name: `prefetch${(0, utils_1.ucFirst)(singularName)}Query`,
415
- isExported: true,
416
- isAsync: true,
417
- parameters: [
418
- { name: 'queryClient', type: 'QueryClient' },
419
- { name: pkName, type: pkTsType },
420
- {
421
- name: 'options',
422
- type: 'ExecuteOptions',
423
- hasQuestionToken: true,
424
- },
425
- ],
426
- returnType: 'Promise<void>',
427
- statements: `await queryClient.prefetchQuery({
428
- queryKey: ${queryName}QueryKey(${pkName}),
429
- queryFn: () => execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
430
- ${queryName}QueryDocument,
431
- { ${pkName} },
432
- options
433
- ),
434
- });`,
435
- docs: [
436
- {
437
- description: `Prefetch a single ${typeName} for SSR or cache warming
438
-
439
- @example
440
- \`\`\`ts
441
- await prefetch${(0, utils_1.ucFirst)(singularName)}Query(queryClient, ${pkTsType === 'string' ? "'value-here'" : '123'});
442
- \`\`\``,
443
- },
444
- ],
445
- });
669
+ const prefetchParams = [
670
+ (0, babel_ast_1.typedParam)('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
671
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`))),
672
+ ];
673
+ if (hasRelationships && useCentralizedKeys) {
674
+ prefetchParams.push((0, babel_ast_1.typedParam)('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true));
675
+ }
676
+ prefetchParams.push((0, babel_ast_1.typedParam)('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
677
+ let prefetchQueryKeyExpr;
678
+ if (hasRelationships && useCentralizedKeys) {
679
+ prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
680
+ t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
681
+ t.identifier('scope'),
682
+ ]);
683
+ }
684
+ else if (useCentralizedKeys) {
685
+ prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.identifier('variables'), t.identifier(pkName))]);
686
+ }
687
+ else {
688
+ prefetchQueryKeyExpr = t.callExpression(t.identifier(`${queryName}QueryKey`), [t.memberExpression(t.identifier('variables'), t.identifier(pkName))]);
689
+ }
690
+ const prefetchFuncBody = t.blockStatement([
691
+ t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
692
+ t.objectExpression([
693
+ t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
694
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], (0, babel_ast_1.createTypedCallExpression)(t.identifier('execute'), [t.identifier(`${queryName}QueryDocument`), t.identifier('variables'), t.identifier('options')], [
695
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
696
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`)),
697
+ ]))),
698
+ ]),
699
+ ]))),
700
+ ]);
701
+ const prefetchFunc = t.functionDeclaration(t.identifier(`prefetch${(0, utils_1.ucFirst)(singularName)}Query`), prefetchParams, prefetchFuncBody);
702
+ prefetchFunc.async = true;
703
+ prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
704
+ const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
705
+ (0, babel_ast_1.addJSDocComment)(prefetchExport, [
706
+ `Prefetch a single ${typeName} for SSR or cache warming`,
707
+ '',
708
+ '@example',
709
+ '```ts',
710
+ `await prefetch${(0, utils_1.ucFirst)(singularName)}Query(queryClient, { ${pkName}: 'some-id' });`,
711
+ '```',
712
+ ]);
713
+ statements.push(prefetchExport);
446
714
  }
715
+ const code = (0, babel_ast_1.generateCode)(statements);
716
+ const headerText = reactQueryEnabled
717
+ ? `Single item query hook for ${typeName}`
718
+ : `Single item query functions for ${typeName}`;
719
+ const content = (0, utils_1.getGeneratedFileHeader)(headerText) + '\n\n' + code;
447
720
  return {
448
721
  fileName: (0, utils_1.getSingleQueryFileName)(table),
449
- content: (0, ts_ast_1.getFormattedOutput)(sourceFile),
722
+ content,
450
723
  };
451
724
  }
452
- // ============================================================================
453
- // Batch generator
454
- // ============================================================================
455
- /**
456
- * Generate all query hook files for all tables
457
- */
458
725
  function generateAllQueryHooks(tables, options = {}) {
459
726
  const files = [];
460
727
  for (const table of tables) {
461
728
  files.push(generateListQueryHook(table, options));
462
- files.push(generateSingleQueryHook(table, options));
729
+ const singleHook = generateSingleQueryHook(table, options);
730
+ if (singleHook) {
731
+ files.push(singleHook);
732
+ }
463
733
  }
464
734
  return files;
465
735
  }