@constructive-io/graphql-codegen 2.23.2 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +147 -2
  2. package/cli/codegen/babel-ast.d.ts +46 -0
  3. package/cli/codegen/babel-ast.js +145 -0
  4. package/cli/codegen/barrel.d.ts +7 -2
  5. package/cli/codegen/barrel.js +159 -97
  6. package/cli/codegen/client.js +61 -0
  7. package/cli/codegen/custom-mutations.d.ts +2 -12
  8. package/cli/codegen/custom-mutations.js +116 -124
  9. package/cli/codegen/custom-queries.d.ts +2 -10
  10. package/cli/codegen/custom-queries.js +246 -335
  11. package/cli/codegen/index.d.ts +3 -0
  12. package/cli/codegen/index.js +72 -3
  13. package/cli/codegen/invalidation.d.ts +20 -0
  14. package/cli/codegen/invalidation.js +327 -0
  15. package/cli/codegen/mutation-keys.d.ts +24 -0
  16. package/cli/codegen/mutation-keys.js +247 -0
  17. package/cli/codegen/mutations.d.ts +3 -19
  18. package/cli/codegen/mutations.js +372 -383
  19. package/cli/codegen/orm/barrel.d.ts +1 -1
  20. package/cli/codegen/orm/barrel.js +42 -10
  21. package/cli/codegen/orm/client-generator.d.ts +1 -19
  22. package/cli/codegen/orm/client-generator.js +108 -77
  23. package/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  24. package/cli/codegen/orm/custom-ops-generator.js +192 -235
  25. package/cli/codegen/orm/input-types-generator.d.ts +13 -1
  26. package/cli/codegen/orm/input-types-generator.js +403 -147
  27. package/cli/codegen/orm/model-generator.d.ts +1 -19
  28. package/cli/codegen/orm/model-generator.js +229 -234
  29. package/cli/codegen/queries.d.ts +3 -11
  30. package/cli/codegen/queries.js +582 -389
  31. package/cli/codegen/query-keys.d.ts +15 -0
  32. package/cli/codegen/query-keys.js +477 -0
  33. package/cli/codegen/scalars.js +1 -0
  34. package/cli/codegen/schema-types-generator.d.ts +15 -10
  35. package/cli/codegen/schema-types-generator.js +87 -175
  36. package/cli/codegen/type-resolver.d.ts +1 -30
  37. package/cli/codegen/type-resolver.js +0 -53
  38. package/cli/codegen/types.d.ts +1 -1
  39. package/cli/codegen/types.js +76 -21
  40. package/cli/commands/generate.js +1 -0
  41. package/cli/index.js +1 -0
  42. package/esm/cli/codegen/babel-ast.d.ts +46 -0
  43. package/esm/cli/codegen/babel-ast.js +97 -0
  44. package/esm/cli/codegen/barrel.d.ts +7 -2
  45. package/esm/cli/codegen/barrel.js +126 -97
  46. package/esm/cli/codegen/client.js +61 -0
  47. package/esm/cli/codegen/custom-mutations.d.ts +2 -12
  48. package/esm/cli/codegen/custom-mutations.js +83 -124
  49. package/esm/cli/codegen/custom-queries.d.ts +2 -10
  50. package/esm/cli/codegen/custom-queries.js +214 -336
  51. package/esm/cli/codegen/index.d.ts +3 -0
  52. package/esm/cli/codegen/index.js +68 -2
  53. package/esm/cli/codegen/invalidation.d.ts +20 -0
  54. package/esm/cli/codegen/invalidation.js +291 -0
  55. package/esm/cli/codegen/mutation-keys.d.ts +24 -0
  56. package/esm/cli/codegen/mutation-keys.js +211 -0
  57. package/esm/cli/codegen/mutations.d.ts +3 -19
  58. package/esm/cli/codegen/mutations.js +340 -384
  59. package/esm/cli/codegen/orm/barrel.d.ts +1 -1
  60. package/esm/cli/codegen/orm/barrel.js +10 -11
  61. package/esm/cli/codegen/orm/client-generator.d.ts +1 -19
  62. package/esm/cli/codegen/orm/client-generator.js +76 -78
  63. package/esm/cli/codegen/orm/custom-ops-generator.d.ts +1 -12
  64. package/esm/cli/codegen/orm/custom-ops-generator.js +160 -236
  65. package/esm/cli/codegen/orm/input-types-generator.d.ts +13 -1
  66. package/esm/cli/codegen/orm/input-types-generator.js +371 -148
  67. package/esm/cli/codegen/orm/model-generator.d.ts +1 -19
  68. package/esm/cli/codegen/orm/model-generator.js +197 -235
  69. package/esm/cli/codegen/queries.d.ts +3 -11
  70. package/esm/cli/codegen/queries.js +550 -390
  71. package/esm/cli/codegen/query-keys.d.ts +15 -0
  72. package/esm/cli/codegen/query-keys.js +441 -0
  73. package/esm/cli/codegen/scalars.js +1 -0
  74. package/esm/cli/codegen/schema-types-generator.d.ts +15 -10
  75. package/esm/cli/codegen/schema-types-generator.js +54 -175
  76. package/esm/cli/codegen/type-resolver.d.ts +1 -30
  77. package/esm/cli/codegen/type-resolver.js +0 -49
  78. package/esm/cli/codegen/types.d.ts +1 -1
  79. package/esm/cli/codegen/types.js +44 -22
  80. package/esm/cli/commands/generate.js +1 -0
  81. package/esm/cli/index.js +1 -0
  82. package/esm/types/config.d.ts +75 -0
  83. package/esm/types/config.js +19 -1
  84. package/package.json +6 -4
  85. package/types/config.d.ts +75 -0
  86. package/types/config.js +20 -2
  87. package/cli/codegen/ts-ast.d.ts +0 -124
  88. package/cli/codegen/ts-ast.js +0 -280
  89. package/esm/cli/codegen/ts-ast.d.ts +0 -124
  90. package/esm/cli/codegen/ts-ast.js +0 -260
@@ -1,36 +1,84 @@
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);
22
75
  const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
23
76
  const scalarFields = (0, utils_1.getScalarFields)(table);
24
- // Generate GraphQL document via AST
77
+ const keysName = `${(0, utils_1.lcFirst)(typeName)}Keys`;
78
+ const scopeTypeName = `${typeName}Scope`;
25
79
  const queryAST = (0, gql_ast_1.buildListQueryAST)({ table });
26
80
  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
81
+ const statements = [];
34
82
  const filterTypesUsed = new Set();
35
83
  for (const field of scalarFields) {
36
84
  const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType, field.type.isArray);
@@ -38,47 +86,56 @@ function generateListQueryHook(table, options = {}) {
38
86
  filterTypesUsed.add(filterType);
39
87
  }
40
88
  }
41
- // Add imports - conditionally include React Query imports
42
- const imports = [];
43
89
  if (reactQueryEnabled) {
44
- imports.push((0, ts_ast_1.createImport)({
45
- moduleSpecifier: '@tanstack/react-query',
46
- namedImports: ['useQuery'],
47
- typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
48
- }));
90
+ const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
91
+ statements.push(reactQueryImport);
92
+ const reactQueryTypeImport = t.importDeclaration([
93
+ t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
94
+ t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
95
+ ], t.stringLiteral('@tanstack/react-query'));
96
+ reactQueryTypeImport.importKind = 'type';
97
+ statements.push(reactQueryTypeImport);
49
98
  }
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
99
+ const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
100
+ statements.push(clientImport);
101
+ const clientTypeImport = t.importDeclaration([
102
+ t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions')),
103
+ ], t.stringLiteral('../client'));
104
+ clientTypeImport.importKind = 'type';
105
+ statements.push(clientTypeImport);
106
+ const typesImport = t.importDeclaration([
107
+ t.importSpecifier(t.identifier(typeName), t.identifier(typeName)),
108
+ ...Array.from(filterTypesUsed).map((ft) => t.importSpecifier(t.identifier(ft), t.identifier(ft))),
109
+ ], t.stringLiteral('../types'));
110
+ typesImport.importKind = 'type';
111
+ statements.push(typesImport);
112
+ if (useCentralizedKeys) {
113
+ const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
114
+ statements.push(queryKeyImport);
115
+ if (hasRelationships) {
116
+ const scopeTypeImport = t.importDeclaration([
117
+ t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName)),
118
+ ], t.stringLiteral('../query-keys'));
119
+ scopeTypeImport.importKind = 'type';
120
+ statements.push(scopeTypeImport);
121
+ }
122
+ }
123
+ const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
124
+ reExportDecl.exportKind = 'type';
125
+ statements.push(reExportDecl);
126
+ const queryDocConst = t.variableDeclaration('const', [
127
+ t.variableDeclarator(t.identifier(`${queryName}QueryDocument`), t.templateLiteral([
128
+ t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true),
129
+ ], [])),
130
+ ]);
131
+ statements.push(t.exportNamedDeclaration(queryDocConst));
72
132
  const fieldFilters = scalarFields
73
133
  .map((field) => {
74
134
  const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType, field.type.isArray);
75
135
  return filterType ? { fieldName: field.name, filterType } : null;
76
136
  })
77
137
  .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
138
+ statements.push(createFilterInterfaceDeclaration(filterTypeName, fieldFilters, false));
82
139
  const orderByValues = [
83
140
  ...scalarFields.flatMap((f) => [
84
141
  `${(0, utils_1.toScreamingSnake)(f.name)}_ASC`,
@@ -88,373 +145,509 @@ function generateListQueryHook(table, options = {}) {
88
145
  'PRIMARY_KEY_ASC',
89
146
  'PRIMARY_KEY_DESC',
90
147
  ];
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)
148
+ const orderByTypeAlias = t.tsTypeAliasDeclaration(t.identifier(orderByTypeName), null, createUnionType(orderByValues));
149
+ statements.push(orderByTypeAlias);
150
+ const variablesInterfaceBody = t.tsInterfaceBody([
151
+ (() => {
152
+ const p = t.tsPropertySignature(t.identifier('first'), t.tsTypeAnnotation(t.tsNumberKeyword()));
153
+ p.optional = true;
154
+ return p;
155
+ })(),
156
+ (() => {
157
+ const p = t.tsPropertySignature(t.identifier('offset'), t.tsTypeAnnotation(t.tsNumberKeyword()));
158
+ p.optional = true;
159
+ return p;
160
+ })(),
161
+ (() => {
162
+ const p = t.tsPropertySignature(t.identifier('filter'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(filterTypeName))));
163
+ p.optional = true;
164
+ return p;
165
+ })(),
166
+ (() => {
167
+ const p = t.tsPropertySignature(t.identifier('orderBy'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(orderByTypeName)))));
168
+ p.optional = true;
169
+ return p;
170
+ })(),
171
+ ]);
172
+ const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`), null, null, variablesInterfaceBody);
173
+ statements.push(t.exportNamedDeclaration(variablesInterface));
174
+ const pageInfoType = t.tsTypeLiteral([
175
+ t.tsPropertySignature(t.identifier('hasNextPage'), t.tsTypeAnnotation(t.tsBooleanKeyword())),
176
+ t.tsPropertySignature(t.identifier('hasPreviousPage'), t.tsTypeAnnotation(t.tsBooleanKeyword())),
177
+ t.tsPropertySignature(t.identifier('startCursor'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))),
178
+ t.tsPropertySignature(t.identifier('endCursor'), t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))),
179
+ ]);
180
+ const resultType = t.tsTypeLiteral([
181
+ t.tsPropertySignature(t.identifier('totalCount'), t.tsTypeAnnotation(t.tsNumberKeyword())),
182
+ t.tsPropertySignature(t.identifier('nodes'), t.tsTypeAnnotation(t.tsArrayType(t.tsTypeReference(t.identifier(typeName))))),
183
+ t.tsPropertySignature(t.identifier('pageInfo'), t.tsTypeAnnotation(pageInfoType)),
184
+ ]);
185
+ const resultInterfaceBody = t.tsInterfaceBody([
186
+ t.tsPropertySignature(t.identifier(queryName), t.tsTypeAnnotation(resultType)),
187
+ ]);
188
+ const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`), null, null, resultInterfaceBody);
189
+ statements.push(t.exportNamedDeclaration(resultInterface));
190
+ if (useCentralizedKeys) {
191
+ const queryKeyConst = t.variableDeclaration('const', [
192
+ t.variableDeclarator(t.identifier(`${queryName}QueryKey`), t.memberExpression(t.identifier(keysName), t.identifier('list'))),
193
+ ]);
194
+ const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
195
+ (0, babel_ast_1.addJSDocComment)(queryKeyExport, [
196
+ 'Query key factory - re-exported from query-keys.ts',
197
+ ]);
198
+ statements.push(queryKeyExport);
199
+ }
200
+ else {
201
+ const queryKeyArrow = t.arrowFunctionExpression([
202
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)), true),
203
+ ], t.tsAsExpression(t.arrayExpression([
204
+ t.stringLiteral(typeName.toLowerCase()),
205
+ t.stringLiteral('list'),
206
+ t.identifier('variables'),
207
+ ]), t.tsTypeReference(t.identifier('const'))));
208
+ const queryKeyConst = t.variableDeclaration('const', [
209
+ t.variableDeclarator(t.identifier(`${queryName}QueryKey`), queryKeyArrow),
210
+ ]);
211
+ statements.push(t.exportNamedDeclaration(queryKeyConst));
212
+ }
125
213
  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
- });
214
+ const hookBodyStatements = [];
215
+ if (hasRelationships && useCentralizedKeys) {
216
+ hookBodyStatements.push(t.variableDeclaration('const', [
217
+ t.variableDeclarator(t.objectPattern([
218
+ t.objectProperty(t.identifier('scope'), t.identifier('scope'), false, true),
219
+ t.restElement(t.identifier('queryOptions')),
220
+ ]), t.logicalExpression('??', t.identifier('options'), t.objectExpression([]))),
221
+ ]));
222
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
223
+ t.objectExpression([
224
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')])),
225
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
226
+ t.identifier(`${queryName}QueryDocument`),
227
+ t.identifier('variables'),
228
+ ]))),
229
+ t.spreadElement(t.identifier('queryOptions')),
230
+ ]),
231
+ ])));
232
+ }
233
+ else if (useCentralizedKeys) {
234
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
235
+ t.objectExpression([
236
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')])),
237
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
238
+ t.identifier(`${queryName}QueryDocument`),
239
+ t.identifier('variables'),
240
+ ]))),
241
+ t.spreadElement(t.identifier('options')),
242
+ ]),
243
+ ])));
244
+ }
245
+ else {
246
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
247
+ t.objectExpression([
248
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
249
+ t.identifier('variables'),
250
+ ])),
251
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
252
+ t.identifier(`${queryName}QueryDocument`),
253
+ t.identifier('variables'),
254
+ ]))),
255
+ t.spreadElement(t.identifier('options')),
256
+ ]),
257
+ ])));
258
+ }
259
+ const hookParams = [
260
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)), true),
261
+ ];
262
+ let optionsTypeStr;
263
+ if (hasRelationships && useCentralizedKeys) {
264
+ optionsTypeStr = `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'> & { scope?: ${scopeTypeName} }`;
265
+ }
266
+ else {
267
+ optionsTypeStr = `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`;
268
+ }
269
+ const optionsParam = t.identifier('options');
270
+ optionsParam.optional = true;
271
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
272
+ hookParams.push(optionsParam);
273
+ const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
274
+ const hookExport = t.exportNamedDeclaration(hookFunc);
275
+ const docLines = [
276
+ `Query hook for fetching ${typeName} list`,
277
+ '',
278
+ '@example',
279
+ '```tsx',
280
+ `const { data, isLoading } = ${hookName}({`,
281
+ ' first: 10,',
282
+ ' filter: { name: { equalTo: "example" } },',
283
+ " orderBy: ['CREATED_AT_DESC'],",
284
+ '});',
285
+ '```',
286
+ ];
287
+ if (hasRelationships && useCentralizedKeys) {
288
+ docLines.push('');
289
+ docLines.push('@example With scope for hierarchical cache invalidation');
290
+ docLines.push('```tsx');
291
+ docLines.push(`const { data } = ${hookName}(`);
292
+ docLines.push(' { first: 10 },');
293
+ docLines.push(" { scope: { parentId: 'parent-id' } }");
294
+ docLines.push(');');
295
+ docLines.push('```');
296
+ }
297
+ (0, babel_ast_1.addJSDocComment)(hookExport, docLines);
298
+ statements.push(hookExport);
168
299
  }
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
300
+ const fetchFuncBody = t.blockStatement([
301
+ t.returnStatement(t.callExpression(t.identifier('execute'), [
302
+ t.identifier(`${queryName}QueryDocument`),
303
+ t.identifier('variables'),
304
+ t.identifier('options'),
305
+ ])),
306
+ ]);
307
+ const fetchFunc = t.functionDeclaration(t.identifier(`fetch${(0, utils_1.ucFirst)(pluralName)}Query`), [
308
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)), true),
309
+ (0, babel_ast_1.typedParam)('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true),
310
+ ], fetchFuncBody);
311
+ fetchFunc.async = true;
312
+ fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
313
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryResult`)),
314
+ ])));
315
+ const fetchExport = t.exportNamedDeclaration(fetchFunc);
316
+ (0, babel_ast_1.addJSDocComment)(fetchExport, [
317
+ `Fetch ${typeName} list without React hooks`,
318
+ '',
319
+ '@example',
320
+ '```ts',
321
+ '// Direct fetch',
322
+ `const data = await fetch${(0, utils_1.ucFirst)(pluralName)}Query({ first: 10 });`,
323
+ '',
324
+ '// With QueryClient',
325
+ 'const data = await queryClient.fetchQuery({',
326
+ ` queryKey: ${queryName}QueryKey(variables),`,
327
+ ` queryFn: () => fetch${(0, utils_1.ucFirst)(pluralName)}Query(variables),`,
328
+ '});',
329
+ '```',
330
+ ]);
331
+ statements.push(fetchExport);
215
332
  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
- });
333
+ const prefetchParams = [
334
+ (0, babel_ast_1.typedParam)('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
335
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(pluralName)}QueryVariables`)), true),
336
+ ];
337
+ if (hasRelationships && useCentralizedKeys) {
338
+ prefetchParams.push((0, babel_ast_1.typedParam)('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true));
339
+ }
340
+ prefetchParams.push((0, babel_ast_1.typedParam)('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
341
+ let prefetchQueryKeyExpr;
342
+ if (hasRelationships && useCentralizedKeys) {
343
+ prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables'), t.identifier('scope')]);
344
+ }
345
+ else if (useCentralizedKeys) {
346
+ prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('list')), [t.identifier('variables')]);
347
+ }
348
+ else {
349
+ prefetchQueryKeyExpr = t.callExpression(t.identifier(`${queryName}QueryKey`), [t.identifier('variables')]);
350
+ }
351
+ const prefetchFuncBody = t.blockStatement([
352
+ t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
353
+ t.objectExpression([
354
+ t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
355
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
356
+ t.identifier(`${queryName}QueryDocument`),
357
+ t.identifier('variables'),
358
+ t.identifier('options'),
359
+ ]))),
360
+ ]),
361
+ ]))),
362
+ ]);
363
+ const prefetchFunc = t.functionDeclaration(t.identifier(`prefetch${(0, utils_1.ucFirst)(pluralName)}Query`), prefetchParams, prefetchFuncBody);
364
+ prefetchFunc.async = true;
365
+ prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
366
+ const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
367
+ (0, babel_ast_1.addJSDocComment)(prefetchExport, [
368
+ `Prefetch ${typeName} list for SSR or cache warming`,
369
+ '',
370
+ '@example',
371
+ '```ts',
372
+ `await prefetch${(0, utils_1.ucFirst)(pluralName)}Query(queryClient, { first: 10 });`,
373
+ '```',
374
+ ]);
375
+ statements.push(prefetchExport);
256
376
  }
377
+ const code = (0, babel_ast_1.generateCode)(statements);
378
+ const headerText = reactQueryEnabled
379
+ ? `List query hook for ${typeName}`
380
+ : `List query functions for ${typeName}`;
381
+ const content = (0, utils_1.getGeneratedFileHeader)(headerText) + '\n\n' + code;
257
382
  return {
258
383
  fileName: (0, utils_1.getListQueryFileName)(table),
259
- content: (0, ts_ast_1.getFormattedOutput)(sourceFile),
384
+ content,
260
385
  };
261
386
  }
262
- // ============================================================================
263
- // Single item query hook generator
264
- // ============================================================================
265
- /**
266
- * Generate single item query hook file content using AST
267
- */
268
387
  function generateSingleQueryHook(table, options = {}) {
269
- const { reactQueryEnabled = true } = options;
270
- const project = (0, ts_ast_1.createProject)();
388
+ const { reactQueryEnabled = true, useCentralizedKeys = true, hasRelationships = false, } = options;
271
389
  const { typeName, singularName } = (0, utils_1.getTableNames)(table);
272
390
  const hookName = (0, utils_1.getSingleQueryHookName)(table);
273
391
  const queryName = (0, utils_1.getSingleRowQueryName)(table);
274
- // Get primary key info dynamically from table constraints
392
+ const keysName = `${(0, utils_1.lcFirst)(typeName)}Keys`;
393
+ const scopeTypeName = `${typeName}Scope`;
275
394
  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
395
  const pkField = pkFields[0];
279
396
  const pkName = pkField.name;
280
397
  const pkTsType = pkField.tsType;
281
- // Generate GraphQL document via AST
282
398
  const queryAST = (0, gql_ast_1.buildSingleQueryAST)({ table });
283
399
  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 = [];
400
+ const statements = [];
292
401
  if (reactQueryEnabled) {
293
- imports.push((0, ts_ast_1.createImport)({
294
- moduleSpecifier: '@tanstack/react-query',
295
- namedImports: ['useQuery'],
296
- typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
297
- }));
402
+ const reactQueryImport = t.importDeclaration([t.importSpecifier(t.identifier('useQuery'), t.identifier('useQuery'))], t.stringLiteral('@tanstack/react-query'));
403
+ statements.push(reactQueryImport);
404
+ const reactQueryTypeImport = t.importDeclaration([
405
+ t.importSpecifier(t.identifier('UseQueryOptions'), t.identifier('UseQueryOptions')),
406
+ t.importSpecifier(t.identifier('QueryClient'), t.identifier('QueryClient')),
407
+ ], t.stringLiteral('@tanstack/react-query'));
408
+ reactQueryTypeImport.importKind = 'type';
409
+ statements.push(reactQueryTypeImport);
410
+ }
411
+ const clientImport = t.importDeclaration([t.importSpecifier(t.identifier('execute'), t.identifier('execute'))], t.stringLiteral('../client'));
412
+ statements.push(clientImport);
413
+ const clientTypeImport = t.importDeclaration([
414
+ t.importSpecifier(t.identifier('ExecuteOptions'), t.identifier('ExecuteOptions')),
415
+ ], t.stringLiteral('../client'));
416
+ clientTypeImport.importKind = 'type';
417
+ statements.push(clientTypeImport);
418
+ const typesImport = t.importDeclaration([t.importSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
419
+ typesImport.importKind = 'type';
420
+ statements.push(typesImport);
421
+ if (useCentralizedKeys) {
422
+ const queryKeyImport = t.importDeclaration([t.importSpecifier(t.identifier(keysName), t.identifier(keysName))], t.stringLiteral('../query-keys'));
423
+ statements.push(queryKeyImport);
424
+ if (hasRelationships) {
425
+ const scopeTypeImport = t.importDeclaration([
426
+ t.importSpecifier(t.identifier(scopeTypeName), t.identifier(scopeTypeName)),
427
+ ], t.stringLiteral('../query-keys'));
428
+ scopeTypeImport.importKind = 'type';
429
+ statements.push(scopeTypeImport);
430
+ }
431
+ }
432
+ const reExportDecl = t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(typeName), t.identifier(typeName))], t.stringLiteral('../types'));
433
+ reExportDecl.exportKind = 'type';
434
+ statements.push(reExportDecl);
435
+ const queryDocConst = t.variableDeclaration('const', [
436
+ t.variableDeclarator(t.identifier(`${queryName}QueryDocument`), t.templateLiteral([
437
+ t.templateElement({ raw: '\n' + queryDocument, cooked: '\n' + queryDocument }, true),
438
+ ], [])),
439
+ ]);
440
+ statements.push(t.exportNamedDeclaration(queryDocConst));
441
+ const pkTypeAnnotation = pkTsType === 'string'
442
+ ? t.tsStringKeyword()
443
+ : pkTsType === 'number'
444
+ ? t.tsNumberKeyword()
445
+ : t.tsTypeReference(t.identifier(pkTsType));
446
+ const variablesInterfaceBody = t.tsInterfaceBody([
447
+ t.tsPropertySignature(t.identifier(pkName), t.tsTypeAnnotation(pkTypeAnnotation)),
448
+ ]);
449
+ const variablesInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`), null, null, variablesInterfaceBody);
450
+ statements.push(t.exportNamedDeclaration(variablesInterface));
451
+ const resultInterfaceBody = t.tsInterfaceBody([
452
+ t.tsPropertySignature(t.identifier(queryName), t.tsTypeAnnotation(t.tsUnionType([
453
+ t.tsTypeReference(t.identifier(typeName)),
454
+ t.tsNullKeyword(),
455
+ ]))),
456
+ ]);
457
+ const resultInterface = t.tsInterfaceDeclaration(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`), null, null, resultInterfaceBody);
458
+ statements.push(t.exportNamedDeclaration(resultInterface));
459
+ if (useCentralizedKeys) {
460
+ const queryKeyConst = t.variableDeclaration('const', [
461
+ t.variableDeclarator(t.identifier(`${queryName}QueryKey`), t.memberExpression(t.identifier(keysName), t.identifier('detail'))),
462
+ ]);
463
+ const queryKeyExport = t.exportNamedDeclaration(queryKeyConst);
464
+ (0, babel_ast_1.addJSDocComment)(queryKeyExport, [
465
+ 'Query key factory - re-exported from query-keys.ts',
466
+ ]);
467
+ statements.push(queryKeyExport);
468
+ }
469
+ else {
470
+ const queryKeyArrow = t.arrowFunctionExpression([(0, babel_ast_1.typedParam)(pkName, pkTypeAnnotation)], t.tsAsExpression(t.arrayExpression([
471
+ t.stringLiteral(typeName.toLowerCase()),
472
+ t.stringLiteral('detail'),
473
+ t.identifier(pkName),
474
+ ]), t.tsTypeReference(t.identifier('const'))));
475
+ const queryKeyConst = t.variableDeclaration('const', [
476
+ t.variableDeclarator(t.identifier(`${queryName}QueryKey`), queryKeyArrow),
477
+ ]);
478
+ statements.push(t.exportNamedDeclaration(queryKeyConst));
298
479
  }
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
480
  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
- });
481
+ const hookBodyStatements = [];
482
+ if (hasRelationships && useCentralizedKeys) {
483
+ hookBodyStatements.push(t.variableDeclaration('const', [
484
+ t.variableDeclarator(t.objectPattern([
485
+ t.objectProperty(t.identifier('scope'), t.identifier('scope'), false, true),
486
+ t.restElement(t.identifier('queryOptions')),
487
+ ]), t.logicalExpression('??', t.identifier('options'), t.objectExpression([]))),
488
+ ]));
489
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
490
+ t.objectExpression([
491
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
492
+ t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
493
+ t.identifier('scope'),
494
+ ])),
495
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
496
+ t.identifier(`${queryName}QueryDocument`),
497
+ t.identifier('variables'),
498
+ ]))),
499
+ t.spreadElement(t.identifier('queryOptions')),
500
+ ]),
501
+ ])));
502
+ }
503
+ else if (useCentralizedKeys) {
504
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
505
+ t.objectExpression([
506
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
507
+ t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
508
+ ])),
509
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
510
+ t.identifier(`${queryName}QueryDocument`),
511
+ t.identifier('variables'),
512
+ ]))),
513
+ t.spreadElement(t.identifier('options')),
514
+ ]),
515
+ ])));
516
+ }
517
+ else {
518
+ hookBodyStatements.push(t.returnStatement(t.callExpression(t.identifier('useQuery'), [
519
+ t.objectExpression([
520
+ t.objectProperty(t.identifier('queryKey'), t.callExpression(t.identifier(`${queryName}QueryKey`), [
521
+ t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
522
+ ])),
523
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
524
+ t.identifier(`${queryName}QueryDocument`),
525
+ t.identifier('variables'),
526
+ ]))),
527
+ t.spreadElement(t.identifier('options')),
528
+ ]),
529
+ ])));
530
+ }
531
+ const hookParams = [
532
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`))),
533
+ ];
534
+ let optionsTypeStr;
535
+ if (hasRelationships && useCentralizedKeys) {
536
+ optionsTypeStr = `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'> & { scope?: ${scopeTypeName} }`;
537
+ }
538
+ else {
539
+ optionsTypeStr = `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`;
540
+ }
541
+ const optionsParam = t.identifier('options');
542
+ optionsParam.optional = true;
543
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier(optionsTypeStr)));
544
+ hookParams.push(optionsParam);
545
+ const hookFunc = t.functionDeclaration(t.identifier(hookName), hookParams, t.blockStatement(hookBodyStatements));
546
+ const hookExport = t.exportNamedDeclaration(hookFunc);
547
+ const docLines = [
548
+ `Query hook for fetching a single ${typeName}`,
549
+ '',
550
+ '@example',
551
+ '```tsx',
552
+ `const { data, isLoading } = ${hookName}({ ${pkName}: 'some-id' });`,
553
+ '```',
554
+ ];
555
+ if (hasRelationships && useCentralizedKeys) {
556
+ docLines.push('');
557
+ docLines.push('@example With scope for hierarchical cache invalidation');
558
+ docLines.push('```tsx');
559
+ docLines.push(`const { data } = ${hookName}(`);
560
+ docLines.push(` { ${pkName}: 'some-id' },`);
561
+ docLines.push(" { scope: { parentId: 'parent-id' } }");
562
+ docLines.push(');');
563
+ docLines.push('```');
564
+ }
565
+ (0, babel_ast_1.addJSDocComment)(hookExport, docLines);
566
+ statements.push(hookExport);
376
567
  }
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
568
+ const fetchFuncBody = t.blockStatement([
569
+ t.returnStatement(t.callExpression(t.identifier('execute'), [
570
+ t.identifier(`${queryName}QueryDocument`),
571
+ t.identifier('variables'),
572
+ t.identifier('options'),
573
+ ])),
574
+ ]);
575
+ const fetchFunc = t.functionDeclaration(t.identifier(`fetch${(0, utils_1.ucFirst)(singularName)}Query`), [
576
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`))),
577
+ (0, babel_ast_1.typedParam)('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true),
578
+ ], fetchFuncBody);
579
+ fetchFunc.async = true;
580
+ fetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
581
+ t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryResult`)),
582
+ ])));
583
+ const fetchExport = t.exportNamedDeclaration(fetchFunc);
584
+ (0, babel_ast_1.addJSDocComment)(fetchExport, [
585
+ `Fetch a single ${typeName} without React hooks`,
586
+ '',
587
+ '@example',
588
+ '```ts',
589
+ `const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query({ ${pkName}: 'some-id' });`,
590
+ '```',
591
+ ]);
592
+ statements.push(fetchExport);
412
593
  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
- });
594
+ const prefetchParams = [
595
+ (0, babel_ast_1.typedParam)('queryClient', t.tsTypeReference(t.identifier('QueryClient'))),
596
+ (0, babel_ast_1.typedParam)('variables', t.tsTypeReference(t.identifier(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`))),
597
+ ];
598
+ if (hasRelationships && useCentralizedKeys) {
599
+ prefetchParams.push((0, babel_ast_1.typedParam)('scope', t.tsTypeReference(t.identifier(scopeTypeName)), true));
600
+ }
601
+ prefetchParams.push((0, babel_ast_1.typedParam)('options', t.tsTypeReference(t.identifier('ExecuteOptions')), true));
602
+ let prefetchQueryKeyExpr;
603
+ if (hasRelationships && useCentralizedKeys) {
604
+ prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [
605
+ t.memberExpression(t.identifier('variables'), t.identifier(pkName)),
606
+ t.identifier('scope'),
607
+ ]);
608
+ }
609
+ else if (useCentralizedKeys) {
610
+ prefetchQueryKeyExpr = t.callExpression(t.memberExpression(t.identifier(keysName), t.identifier('detail')), [t.memberExpression(t.identifier('variables'), t.identifier(pkName))]);
611
+ }
612
+ else {
613
+ prefetchQueryKeyExpr = t.callExpression(t.identifier(`${queryName}QueryKey`), [t.memberExpression(t.identifier('variables'), t.identifier(pkName))]);
614
+ }
615
+ const prefetchFuncBody = t.blockStatement([
616
+ t.expressionStatement(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('queryClient'), t.identifier('prefetchQuery')), [
617
+ t.objectExpression([
618
+ t.objectProperty(t.identifier('queryKey'), prefetchQueryKeyExpr),
619
+ t.objectProperty(t.identifier('queryFn'), t.arrowFunctionExpression([], t.callExpression(t.identifier('execute'), [
620
+ t.identifier(`${queryName}QueryDocument`),
621
+ t.identifier('variables'),
622
+ t.identifier('options'),
623
+ ]))),
624
+ ]),
625
+ ]))),
626
+ ]);
627
+ const prefetchFunc = t.functionDeclaration(t.identifier(`prefetch${(0, utils_1.ucFirst)(singularName)}Query`), prefetchParams, prefetchFuncBody);
628
+ prefetchFunc.async = true;
629
+ prefetchFunc.returnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()])));
630
+ const prefetchExport = t.exportNamedDeclaration(prefetchFunc);
631
+ (0, babel_ast_1.addJSDocComment)(prefetchExport, [
632
+ `Prefetch a single ${typeName} for SSR or cache warming`,
633
+ '',
634
+ '@example',
635
+ '```ts',
636
+ `await prefetch${(0, utils_1.ucFirst)(singularName)}Query(queryClient, { ${pkName}: 'some-id' });`,
637
+ '```',
638
+ ]);
639
+ statements.push(prefetchExport);
446
640
  }
641
+ const code = (0, babel_ast_1.generateCode)(statements);
642
+ const headerText = reactQueryEnabled
643
+ ? `Single item query hook for ${typeName}`
644
+ : `Single item query functions for ${typeName}`;
645
+ const content = (0, utils_1.getGeneratedFileHeader)(headerText) + '\n\n' + code;
447
646
  return {
448
647
  fileName: (0, utils_1.getSingleQueryFileName)(table),
449
- content: (0, ts_ast_1.getFormattedOutput)(sourceFile),
648
+ content,
450
649
  };
451
650
  }
452
- // ============================================================================
453
- // Batch generator
454
- // ============================================================================
455
- /**
456
- * Generate all query hook files for all tables
457
- */
458
651
  function generateAllQueryHooks(tables, options = {}) {
459
652
  const files = [];
460
653
  for (const table of tables) {