@constructive-io/graphql-codegen 2.20.1 → 2.22.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 (96) hide show
  1. package/README.md +15 -3
  2. package/cli/codegen/barrel.d.ts +4 -1
  3. package/cli/codegen/barrel.js +18 -12
  4. package/cli/codegen/client.js +33 -0
  5. package/cli/codegen/custom-mutations.d.ts +11 -1
  6. package/cli/codegen/custom-mutations.js +49 -15
  7. package/cli/codegen/custom-queries.d.ts +8 -0
  8. package/cli/codegen/custom-queries.js +82 -47
  9. package/cli/codegen/gql-ast.js +9 -5
  10. package/cli/codegen/index.js +39 -8
  11. package/cli/codegen/mutations.d.ts +14 -4
  12. package/cli/codegen/mutations.js +114 -28
  13. package/cli/codegen/orm/barrel.js +4 -2
  14. package/cli/codegen/orm/index.js +17 -0
  15. package/cli/codegen/orm/input-types-generator.js +83 -29
  16. package/cli/codegen/orm/model-generator.js +6 -4
  17. package/cli/codegen/queries.d.ts +7 -3
  18. package/cli/codegen/queries.js +185 -158
  19. package/cli/codegen/scalars.d.ts +6 -4
  20. package/cli/codegen/scalars.js +17 -9
  21. package/cli/codegen/schema-types-generator.d.ts +26 -0
  22. package/cli/codegen/schema-types-generator.js +365 -0
  23. package/cli/codegen/ts-ast.d.ts +3 -1
  24. package/cli/codegen/ts-ast.js +2 -2
  25. package/cli/codegen/type-resolver.d.ts +52 -6
  26. package/cli/codegen/type-resolver.js +97 -19
  27. package/cli/codegen/types.d.ts +7 -4
  28. package/cli/codegen/types.js +94 -41
  29. package/cli/codegen/utils.d.ts +20 -2
  30. package/cli/codegen/utils.js +32 -7
  31. package/cli/commands/generate-orm.js +5 -5
  32. package/cli/commands/generate.d.ts +4 -1
  33. package/cli/commands/generate.js +27 -8
  34. package/cli/introspect/transform-schema.d.ts +33 -21
  35. package/cli/introspect/transform-schema.js +31 -21
  36. package/esm/cli/codegen/barrel.d.ts +4 -1
  37. package/esm/cli/codegen/barrel.js +18 -12
  38. package/esm/cli/codegen/client.js +33 -0
  39. package/esm/cli/codegen/custom-mutations.d.ts +11 -1
  40. package/esm/cli/codegen/custom-mutations.js +50 -16
  41. package/esm/cli/codegen/custom-queries.d.ts +8 -0
  42. package/esm/cli/codegen/custom-queries.js +83 -48
  43. package/esm/cli/codegen/gql-ast.js +10 -6
  44. package/esm/cli/codegen/index.js +39 -8
  45. package/esm/cli/codegen/mutations.d.ts +14 -4
  46. package/esm/cli/codegen/mutations.js +115 -29
  47. package/esm/cli/codegen/orm/barrel.js +4 -2
  48. package/esm/cli/codegen/orm/index.js +17 -0
  49. package/esm/cli/codegen/orm/input-types-generator.js +83 -29
  50. package/esm/cli/codegen/orm/model-generator.js +7 -5
  51. package/esm/cli/codegen/queries.d.ts +7 -3
  52. package/esm/cli/codegen/queries.js +186 -159
  53. package/esm/cli/codegen/scalars.d.ts +6 -4
  54. package/esm/cli/codegen/scalars.js +16 -8
  55. package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
  56. package/esm/cli/codegen/schema-types-generator.js +362 -0
  57. package/esm/cli/codegen/ts-ast.d.ts +3 -1
  58. package/esm/cli/codegen/ts-ast.js +2 -2
  59. package/esm/cli/codegen/type-resolver.d.ts +52 -6
  60. package/esm/cli/codegen/type-resolver.js +97 -20
  61. package/esm/cli/codegen/types.d.ts +7 -4
  62. package/esm/cli/codegen/types.js +95 -41
  63. package/esm/cli/codegen/utils.d.ts +20 -2
  64. package/esm/cli/codegen/utils.js +31 -7
  65. package/esm/cli/commands/generate-orm.js +5 -5
  66. package/esm/cli/commands/generate.d.ts +4 -1
  67. package/esm/cli/commands/generate.js +27 -8
  68. package/esm/cli/introspect/transform-schema.d.ts +33 -21
  69. package/esm/cli/introspect/transform-schema.js +31 -21
  70. package/esm/types/config.d.ts +16 -1
  71. package/esm/types/config.js +6 -0
  72. package/esm/types/schema.d.ts +2 -0
  73. package/package.json +8 -6
  74. package/types/config.d.ts +16 -1
  75. package/types/config.js +6 -0
  76. package/types/schema.d.ts +2 -0
  77. package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  78. package/__tests__/codegen/input-types-generator.test.js +0 -635
  79. package/cli/codegen/filters.d.ts +0 -27
  80. package/cli/codegen/filters.js +0 -357
  81. package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  82. package/cli/codegen/orm/input-types-generator.test.js +0 -75
  83. package/cli/codegen/orm/select-types.test.d.ts +0 -11
  84. package/cli/codegen/orm/select-types.test.js +0 -22
  85. package/cli/introspect/transform-schema.test.d.ts +0 -1
  86. package/cli/introspect/transform-schema.test.js +0 -67
  87. package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  88. package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
  89. package/esm/cli/codegen/filters.d.ts +0 -27
  90. package/esm/cli/codegen/filters.js +0 -351
  91. package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  92. package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
  93. package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
  94. package/esm/cli/codegen/orm/select-types.test.js +0 -21
  95. package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
  96. package/esm/cli/introspect/transform-schema.test.js +0 -65
@@ -11,13 +11,15 @@ function generateModelFile(table, _useSharedTypes) {
11
11
  const project = (0, ts_ast_1.createProject)();
12
12
  const { typeName, singularName, pluralName } = (0, utils_1.getTableNames)(table);
13
13
  const modelName = `${typeName}Model`;
14
- const fileName = `${(0, utils_1.lcFirst)(typeName)}.ts`;
14
+ // Avoid "index.ts" which clashes with barrel file
15
+ const baseFileName = (0, utils_1.lcFirst)(typeName);
16
+ const fileName = baseFileName === 'index' ? `${baseFileName}Model.ts` : `${baseFileName}.ts`;
15
17
  const entityLower = singularName;
16
- // Type names for this entity
18
+ // Type names for this entity - use inflection from table metadata
17
19
  const selectTypeName = `${typeName}Select`;
18
20
  const relationTypeName = `${typeName}WithRelations`;
19
- const whereTypeName = `${typeName}Filter`;
20
- const orderByTypeName = `${typeName}sOrderBy`; // PostGraphile uses plural
21
+ const whereTypeName = (0, utils_1.getFilterTypeName)(table);
22
+ const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
21
23
  const createInputTypeName = `Create${typeName}Input`;
22
24
  const updateInputTypeName = `Update${typeName}Input`;
23
25
  const deleteInputTypeName = `Delete${typeName}Input`;
@@ -11,15 +11,19 @@ export interface GeneratedQueryFile {
11
11
  fileName: string;
12
12
  content: string;
13
13
  }
14
+ export interface QueryGeneratorOptions {
15
+ /** Whether to generate React Query hooks (default: true for backwards compatibility) */
16
+ reactQueryEnabled?: boolean;
17
+ }
14
18
  /**
15
19
  * Generate list query hook file content using AST
16
20
  */
17
- export declare function generateListQueryHook(table: CleanTable): GeneratedQueryFile;
21
+ export declare function generateListQueryHook(table: CleanTable, options?: QueryGeneratorOptions): GeneratedQueryFile;
18
22
  /**
19
23
  * Generate single item query hook file content using AST
20
24
  */
21
- export declare function generateSingleQueryHook(table: CleanTable): GeneratedQueryFile;
25
+ export declare function generateSingleQueryHook(table: CleanTable, options?: QueryGeneratorOptions): GeneratedQueryFile;
22
26
  /**
23
27
  * Generate all query hook files for all tables
24
28
  */
25
- export declare function generateAllQueryHooks(tables: CleanTable[]): GeneratedQueryFile[];
29
+ export declare function generateAllQueryHooks(tables: CleanTable[], options?: QueryGeneratorOptions): GeneratedQueryFile[];
@@ -12,7 +12,8 @@ const utils_1 = require("./utils");
12
12
  /**
13
13
  * Generate list query hook file content using AST
14
14
  */
15
- function generateListQueryHook(table) {
15
+ function generateListQueryHook(table, options = {}) {
16
+ const { reactQueryEnabled = true } = options;
16
17
  const project = (0, ts_ast_1.createProject)();
17
18
  const { typeName, pluralName } = (0, utils_1.getTableNames)(table);
18
19
  const hookName = (0, utils_1.getListQueryHookName)(table);
@@ -25,32 +26,36 @@ function generateListQueryHook(table) {
25
26
  const queryDocument = (0, gql_ast_1.printGraphQL)(queryAST);
26
27
  const sourceFile = (0, ts_ast_1.createSourceFile)(project, (0, utils_1.getListQueryFileName)(table));
27
28
  // Add file header as leading comment
28
- sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)(`List query hook for ${typeName}`) + '\n\n');
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');
29
33
  // Collect all filter types used by this table's fields
30
34
  const filterTypesUsed = new Set();
31
35
  for (const field of scalarFields) {
32
- const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType);
36
+ const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType, field.type.isArray);
33
37
  if (filterType) {
34
38
  filterTypesUsed.add(filterType);
35
39
  }
36
40
  }
37
- // Add imports
38
- sourceFile.addImportDeclarations([
39
- (0, ts_ast_1.createImport)({
41
+ // Add imports - conditionally include React Query imports
42
+ const imports = [];
43
+ if (reactQueryEnabled) {
44
+ imports.push((0, ts_ast_1.createImport)({
40
45
  moduleSpecifier: '@tanstack/react-query',
41
46
  namedImports: ['useQuery'],
42
47
  typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
43
- }),
44
- (0, ts_ast_1.createImport)({
45
- moduleSpecifier: '../client',
46
- namedImports: ['execute'],
47
- typeOnlyNamedImports: ['ExecuteOptions'],
48
- }),
49
- (0, ts_ast_1.createImport)({
50
- moduleSpecifier: '../types',
51
- typeOnlyNamedImports: [typeName, ...Array.from(filterTypesUsed)],
52
- }),
53
- ]);
48
+ }));
49
+ }
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);
54
59
  // Re-export entity type
55
60
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
56
61
  // Add section comment
@@ -66,12 +71,14 @@ function generateListQueryHook(table) {
66
71
  // Generate filter interface
67
72
  const fieldFilters = scalarFields
68
73
  .map((field) => {
69
- const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType);
74
+ const filterType = (0, utils_1.getScalarFilterType)(field.type.gqlType, field.type.isArray);
70
75
  return filterType ? { fieldName: field.name, filterType } : null;
71
76
  })
72
77
  .filter((f) => f !== null);
73
- sourceFile.addInterface((0, ts_ast_1.createFilterInterface)(filterTypeName, fieldFilters));
78
+ // Note: Not exported to avoid conflicts with schema-types
79
+ sourceFile.addInterface((0, ts_ast_1.createFilterInterface)(filterTypeName, fieldFilters, { isExported: false }));
74
80
  // Generate OrderBy type
81
+ // Note: Not exported to avoid conflicts with schema-types
75
82
  const orderByValues = [
76
83
  ...scalarFields.flatMap((f) => [
77
84
  `${(0, utils_1.toScreamingSnake)(f.name)}_ASC`,
@@ -81,7 +88,7 @@ function generateListQueryHook(table) {
81
88
  'PRIMARY_KEY_ASC',
82
89
  'PRIMARY_KEY_DESC',
83
90
  ];
84
- sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(orderByTypeName, (0, ts_ast_1.createUnionType)(orderByValues)));
91
+ sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(orderByTypeName, (0, ts_ast_1.createUnionType)(orderByValues), { isExported: false }));
85
92
  // Variables interface
86
93
  const variablesProps = [
87
94
  { name: 'first', type: 'number', optional: true },
@@ -114,27 +121,28 @@ function generateListQueryHook(table) {
114
121
  // Query key factory
115
122
  sourceFile.addVariableStatement((0, ts_ast_1.createConst)(`${queryName}QueryKey`, `(variables?: ${(0, utils_1.ucFirst)(pluralName)}QueryVariables) =>
116
123
  ['${typeName.toLowerCase()}', 'list', variables] as const`));
117
- // Add section comment
118
- sourceFile.addStatements('\n// ============================================================================');
119
- sourceFile.addStatements('// Hook');
120
- sourceFile.addStatements('// ============================================================================\n');
121
- // Hook function
122
- sourceFile.addFunction({
123
- name: hookName,
124
- isExported: true,
125
- parameters: [
126
- {
127
- name: 'variables',
128
- type: `${(0, utils_1.ucFirst)(pluralName)}QueryVariables`,
129
- hasQuestionToken: true,
130
- },
131
- {
132
- name: 'options',
133
- type: `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(pluralName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
134
- hasQuestionToken: true,
135
- },
136
- ],
137
- statements: `return useQuery({
124
+ // Add React Query hook section (only if enabled)
125
+ 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({
138
146
  queryKey: ${queryName}QueryKey(variables),
139
147
  queryFn: () => execute<${(0, utils_1.ucFirst)(pluralName)}QueryResult, ${(0, utils_1.ucFirst)(pluralName)}QueryVariables>(
140
148
  ${queryName}QueryDocument,
@@ -142,9 +150,9 @@ function generateListQueryHook(table) {
142
150
  ),
143
151
  ...options,
144
152
  });`,
145
- docs: [
146
- {
147
- description: `Query hook for fetching ${typeName} list
153
+ docs: [
154
+ {
155
+ description: `Query hook for fetching ${typeName} list
148
156
 
149
157
  @example
150
158
  \`\`\`tsx
@@ -154,9 +162,10 @@ const { data, isLoading } = ${hookName}({
154
162
  orderBy: ['CREATED_AT_DESC'],
155
163
  });
156
164
  \`\`\``,
157
- },
158
- ],
159
- });
165
+ },
166
+ ],
167
+ });
168
+ }
160
169
  // Add section comment for standalone functions
161
170
  sourceFile.addStatements('\n// ============================================================================');
162
171
  sourceFile.addStatements('// Standalone Functions (non-React)');
@@ -202,29 +211,30 @@ const data = await queryClient.fetchQuery({
202
211
  },
203
212
  ],
204
213
  });
205
- // Prefetch function (for SSR/QueryClient)
206
- sourceFile.addFunction({
207
- name: `prefetch${(0, utils_1.ucFirst)(pluralName)}Query`,
208
- isExported: true,
209
- isAsync: true,
210
- parameters: [
211
- {
212
- name: 'queryClient',
213
- type: 'QueryClient',
214
- },
215
- {
216
- name: 'variables',
217
- type: `${(0, utils_1.ucFirst)(pluralName)}QueryVariables`,
218
- hasQuestionToken: true,
219
- },
220
- {
221
- name: 'options',
222
- type: 'ExecuteOptions',
223
- hasQuestionToken: true,
224
- },
225
- ],
226
- returnType: 'Promise<void>',
227
- statements: `await queryClient.prefetchQuery({
214
+ // Prefetch function (for SSR/QueryClient) - only if React Query is enabled
215
+ 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({
228
238
  queryKey: ${queryName}QueryKey(variables),
229
239
  queryFn: () => execute<${(0, utils_1.ucFirst)(pluralName)}QueryResult, ${(0, utils_1.ucFirst)(pluralName)}QueryVariables>(
230
240
  ${queryName}QueryDocument,
@@ -232,17 +242,18 @@ const data = await queryClient.fetchQuery({
232
242
  options
233
243
  ),
234
244
  });`,
235
- docs: [
236
- {
237
- description: `Prefetch ${typeName} list for SSR or cache warming
245
+ docs: [
246
+ {
247
+ description: `Prefetch ${typeName} list for SSR or cache warming
238
248
 
239
249
  @example
240
250
  \`\`\`ts
241
251
  await prefetch${(0, utils_1.ucFirst)(pluralName)}Query(queryClient, { first: 10 });
242
252
  \`\`\``,
243
- },
244
- ],
245
- });
253
+ },
254
+ ],
255
+ });
256
+ }
246
257
  return {
247
258
  fileName: (0, utils_1.getListQueryFileName)(table),
248
259
  content: (0, ts_ast_1.getFormattedOutput)(sourceFile),
@@ -254,34 +265,46 @@ await prefetch${(0, utils_1.ucFirst)(pluralName)}Query(queryClient, { first: 10
254
265
  /**
255
266
  * Generate single item query hook file content using AST
256
267
  */
257
- function generateSingleQueryHook(table) {
268
+ function generateSingleQueryHook(table, options = {}) {
269
+ const { reactQueryEnabled = true } = options;
258
270
  const project = (0, ts_ast_1.createProject)();
259
271
  const { typeName, singularName } = (0, utils_1.getTableNames)(table);
260
272
  const hookName = (0, utils_1.getSingleQueryHookName)(table);
261
273
  const queryName = (0, utils_1.getSingleRowQueryName)(table);
274
+ // Get primary key info dynamically from table constraints
275
+ 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
+ const pkField = pkFields[0];
279
+ const pkName = pkField.name;
280
+ const pkTsType = pkField.tsType;
262
281
  // Generate GraphQL document via AST
263
282
  const queryAST = (0, gql_ast_1.buildSingleQueryAST)({ table });
264
283
  const queryDocument = (0, gql_ast_1.printGraphQL)(queryAST);
265
284
  const sourceFile = (0, ts_ast_1.createSourceFile)(project, (0, utils_1.getSingleQueryFileName)(table));
266
285
  // Add file header
267
- sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)(`Single item query hook for ${typeName}`) + '\n\n');
268
- // Add imports
269
- sourceFile.addImportDeclarations([
270
- (0, ts_ast_1.createImport)({
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 = [];
292
+ if (reactQueryEnabled) {
293
+ imports.push((0, ts_ast_1.createImport)({
271
294
  moduleSpecifier: '@tanstack/react-query',
272
295
  namedImports: ['useQuery'],
273
296
  typeOnlyNamedImports: ['UseQueryOptions', 'QueryClient'],
274
- }),
275
- (0, ts_ast_1.createImport)({
276
- moduleSpecifier: '../client',
277
- namedImports: ['execute'],
278
- typeOnlyNamedImports: ['ExecuteOptions'],
279
- }),
280
- (0, ts_ast_1.createImport)({
281
- moduleSpecifier: '../types',
282
- typeOnlyNamedImports: [typeName],
283
- }),
284
- ]);
297
+ }));
298
+ }
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);
285
308
  // Re-export entity type
286
309
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
287
310
  // Add section comment
@@ -294,9 +317,9 @@ function generateSingleQueryHook(table) {
294
317
  sourceFile.addStatements('\n// ============================================================================');
295
318
  sourceFile.addStatements('// Types');
296
319
  sourceFile.addStatements('// ============================================================================\n');
297
- // Variables interface
320
+ // Variables interface - use dynamic PK field name and type
298
321
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(singularName)}QueryVariables`, [
299
- { name: 'id', type: 'string' },
322
+ { name: pkName, type: pkTsType },
300
323
  ]));
301
324
  // Result interface
302
325
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(singularName)}QueryResult`, [
@@ -306,60 +329,62 @@ function generateSingleQueryHook(table) {
306
329
  sourceFile.addStatements('\n// ============================================================================');
307
330
  sourceFile.addStatements('// Query Key');
308
331
  sourceFile.addStatements('// ============================================================================\n');
309
- // Query key factory
310
- sourceFile.addVariableStatement((0, ts_ast_1.createConst)(`${queryName}QueryKey`, `(id: string) =>
311
- ['${typeName.toLowerCase()}', 'detail', id] as const`));
312
- // Add section comment
313
- sourceFile.addStatements('\n// ============================================================================');
314
- sourceFile.addStatements('// Hook');
315
- sourceFile.addStatements('// ============================================================================\n');
316
- // Hook function
317
- sourceFile.addFunction({
318
- name: hookName,
319
- isExported: true,
320
- parameters: [
321
- { name: 'id', type: 'string' },
322
- {
323
- name: 'options',
324
- type: `Omit<UseQueryOptions<${(0, utils_1.ucFirst)(singularName)}QueryResult, Error>, 'queryKey' | 'queryFn'>`,
325
- hasQuestionToken: true,
326
- },
327
- ],
328
- statements: `return useQuery({
329
- queryKey: ${queryName}QueryKey(id),
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
+ 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}),
330
354
  queryFn: () => execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
331
355
  ${queryName}QueryDocument,
332
- { id }
356
+ { ${pkName} }
333
357
  ),
334
- enabled: !!id && (options?.enabled !== false),
358
+ enabled: !!${pkName} && (options?.enabled !== false),
335
359
  ...options,
336
360
  });`,
337
- docs: [
338
- {
339
- description: `Query hook for fetching a single ${typeName} by ID
361
+ docs: [
362
+ {
363
+ description: `Query hook for fetching a single ${typeName} by primary key
340
364
 
341
365
  @example
342
366
  \`\`\`tsx
343
- const { data, isLoading } = ${hookName}('uuid-here');
367
+ const { data, isLoading } = ${hookName}(${pkTsType === 'string' ? "'value-here'" : '123'});
344
368
 
345
369
  if (data?.${queryName}) {
346
- console.log(data.${queryName}.id);
370
+ console.log(data.${queryName}.${pkName});
347
371
  }
348
372
  \`\`\``,
349
- },
350
- ],
351
- });
373
+ },
374
+ ],
375
+ });
376
+ }
352
377
  // Add section comment for standalone functions
353
378
  sourceFile.addStatements('\n// ============================================================================');
354
379
  sourceFile.addStatements('// Standalone Functions (non-React)');
355
380
  sourceFile.addStatements('// ============================================================================\n');
356
- // Fetch function (standalone, no React)
381
+ // Fetch function (standalone, no React) - use dynamic PK
357
382
  sourceFile.addFunction({
358
383
  name: `fetch${(0, utils_1.ucFirst)(singularName)}Query`,
359
384
  isExported: true,
360
385
  isAsync: true,
361
386
  parameters: [
362
- { name: 'id', type: 'string' },
387
+ { name: pkName, type: pkTsType },
363
388
  {
364
389
  name: 'options',
365
390
  type: 'ExecuteOptions',
@@ -369,54 +394,56 @@ if (data?.${queryName}) {
369
394
  returnType: `Promise<${(0, utils_1.ucFirst)(singularName)}QueryResult>`,
370
395
  statements: `return execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
371
396
  ${queryName}QueryDocument,
372
- { id },
397
+ { ${pkName} },
373
398
  options
374
399
  );`,
375
400
  docs: [
376
401
  {
377
- description: `Fetch a single ${typeName} by ID without React hooks
402
+ description: `Fetch a single ${typeName} by primary key without React hooks
378
403
 
379
404
  @example
380
405
  \`\`\`ts
381
- const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query('uuid-here');
406
+ const data = await fetch${(0, utils_1.ucFirst)(singularName)}Query(${pkTsType === 'string' ? "'value-here'" : '123'});
382
407
  \`\`\``,
383
408
  },
384
409
  ],
385
410
  });
386
- // Prefetch function (for SSR/QueryClient)
387
- sourceFile.addFunction({
388
- name: `prefetch${(0, utils_1.ucFirst)(singularName)}Query`,
389
- isExported: true,
390
- isAsync: true,
391
- parameters: [
392
- { name: 'queryClient', type: 'QueryClient' },
393
- { name: 'id', type: 'string' },
394
- {
395
- name: 'options',
396
- type: 'ExecuteOptions',
397
- hasQuestionToken: true,
398
- },
399
- ],
400
- returnType: 'Promise<void>',
401
- statements: `await queryClient.prefetchQuery({
402
- queryKey: ${queryName}QueryKey(id),
411
+ // Prefetch function (for SSR/QueryClient) - only if React Query is enabled, use dynamic PK
412
+ 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}),
403
429
  queryFn: () => execute<${(0, utils_1.ucFirst)(singularName)}QueryResult, ${(0, utils_1.ucFirst)(singularName)}QueryVariables>(
404
430
  ${queryName}QueryDocument,
405
- { id },
431
+ { ${pkName} },
406
432
  options
407
433
  ),
408
434
  });`,
409
- docs: [
410
- {
411
- description: `Prefetch a single ${typeName} for SSR or cache warming
435
+ docs: [
436
+ {
437
+ description: `Prefetch a single ${typeName} for SSR or cache warming
412
438
 
413
439
  @example
414
440
  \`\`\`ts
415
- await prefetch${(0, utils_1.ucFirst)(singularName)}Query(queryClient, 'uuid-here');
441
+ await prefetch${(0, utils_1.ucFirst)(singularName)}Query(queryClient, ${pkTsType === 'string' ? "'value-here'" : '123'});
416
442
  \`\`\``,
417
- },
418
- ],
419
- });
443
+ },
444
+ ],
445
+ });
446
+ }
420
447
  return {
421
448
  fileName: (0, utils_1.getSingleQueryFileName)(table),
422
449
  content: (0, ts_ast_1.getFormattedOutput)(sourceFile),
@@ -428,11 +455,11 @@ await prefetch${(0, utils_1.ucFirst)(singularName)}Query(queryClient, 'uuid-here
428
455
  /**
429
456
  * Generate all query hook files for all tables
430
457
  */
431
- function generateAllQueryHooks(tables) {
458
+ function generateAllQueryHooks(tables, options = {}) {
432
459
  const files = [];
433
460
  for (const table of tables) {
434
- files.push(generateListQueryHook(table));
435
- files.push(generateSingleQueryHook(table));
461
+ files.push(generateListQueryHook(table, options));
462
+ files.push(generateSingleQueryHook(table, options));
436
463
  }
437
464
  return files;
438
465
  }
@@ -4,9 +4,11 @@
4
4
  export declare const SCALAR_TS_MAP: Record<string, string>;
5
5
  export declare const SCALAR_FILTER_MAP: Record<string, string>;
6
6
  export declare const SCALAR_NAMES: Set<string>;
7
- export interface ScalarToTsOptions {
7
+ /** All base filter type names - skip these in schema-types.ts to avoid duplicates */
8
+ export declare const BASE_FILTER_TYPE_NAMES: Set<string>;
9
+ export declare function scalarToTsType(scalarName: string, options?: {
8
10
  unknownScalar?: 'unknown' | 'name';
9
11
  overrides?: Record<string, string>;
10
- }
11
- export declare function scalarToTsType(scalarName: string, options?: ScalarToTsOptions): string;
12
- export declare function scalarToFilterType(scalarName: string): string | null;
12
+ }): string;
13
+ /** Get the filter type for a scalar (handles both scalar and array types) */
14
+ export declare function scalarToFilterType(scalarName: string, isArray?: boolean): string | null;
@@ -3,7 +3,7 @@
3
3
  * Shared scalar mappings for code generation
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SCALAR_NAMES = exports.SCALAR_FILTER_MAP = exports.SCALAR_TS_MAP = void 0;
6
+ exports.BASE_FILTER_TYPE_NAMES = exports.SCALAR_NAMES = exports.SCALAR_FILTER_MAP = exports.SCALAR_TS_MAP = void 0;
7
7
  exports.scalarToTsType = scalarToTsType;
8
8
  exports.scalarToFilterType = scalarToFilterType;
9
9
  exports.SCALAR_TS_MAP = {
@@ -37,6 +37,8 @@ exports.SCALAR_TS_MAP = {
37
37
  MacAddr: 'string',
38
38
  TsVector: 'string',
39
39
  TsQuery: 'string',
40
+ // File upload
41
+ Upload: 'File',
40
42
  };
41
43
  exports.SCALAR_FILTER_MAP = {
42
44
  String: 'StringFilter',
@@ -57,15 +59,21 @@ exports.SCALAR_FILTER_MAP = {
57
59
  Interval: 'StringFilter',
58
60
  };
59
61
  exports.SCALAR_NAMES = new Set(Object.keys(exports.SCALAR_TS_MAP));
62
+ /** Scalars that have list filter variants (e.g., StringListFilter) */
63
+ const LIST_FILTER_SCALARS = new Set(['String', 'Int', 'UUID']);
64
+ /** All base filter type names - skip these in schema-types.ts to avoid duplicates */
65
+ exports.BASE_FILTER_TYPE_NAMES = new Set([
66
+ ...new Set(Object.values(exports.SCALAR_FILTER_MAP)),
67
+ ...Array.from(LIST_FILTER_SCALARS).map((s) => `${s}ListFilter`),
68
+ ]);
60
69
  function scalarToTsType(scalarName, options = {}) {
61
- const override = options.overrides?.[scalarName];
62
- if (override)
63
- return override;
64
- const mapped = exports.SCALAR_TS_MAP[scalarName];
65
- if (mapped)
66
- return mapped;
67
- return options.unknownScalar === 'unknown' ? 'unknown' : scalarName;
70
+ return options.overrides?.[scalarName] ?? exports.SCALAR_TS_MAP[scalarName] ?? (options.unknownScalar === 'unknown' ? 'unknown' : scalarName);
68
71
  }
69
- function scalarToFilterType(scalarName) {
72
+ /** Get the filter type for a scalar (handles both scalar and array types) */
73
+ function scalarToFilterType(scalarName, isArray = false) {
74
+ const baseName = scalarName === 'ID' ? 'UUID' : scalarName;
75
+ if (isArray) {
76
+ return LIST_FILTER_SCALARS.has(baseName) ? `${baseName}ListFilter` : null;
77
+ }
70
78
  return exports.SCALAR_FILTER_MAP[scalarName] ?? null;
71
79
  }
@@ -0,0 +1,26 @@
1
+ import type { TypeRegistry } from '../../types/schema';
2
+ export interface GeneratedSchemaTypesFile {
3
+ fileName: string;
4
+ content: string;
5
+ /** List of enum type names that were generated */
6
+ generatedEnums: string[];
7
+ /** List of table entity types that are referenced */
8
+ referencedTableTypes: string[];
9
+ }
10
+ export interface GenerateSchemaTypesOptions {
11
+ /** The TypeRegistry containing all GraphQL types */
12
+ typeRegistry: TypeRegistry;
13
+ /** Type names that already exist in types.ts (table entity types) */
14
+ tableTypeNames: Set<string>;
15
+ }
16
+ export interface PayloadTypesResult {
17
+ generatedTypes: Set<string>;
18
+ referencedTableTypes: Set<string>;
19
+ }
20
+ /**
21
+ * Generate comprehensive schema-types.ts file using ts-morph AST
22
+ *
23
+ * This generates all Input/Payload/Enum types from the TypeRegistry
24
+ * that are needed by custom mutation/query hooks.
25
+ */
26
+ export declare function generateSchemaTypesFile(options: GenerateSchemaTypesOptions): GeneratedSchemaTypesFile;