@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
@@ -1,26 +1,61 @@
1
1
  import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, } from './ts-ast';
2
2
  import { buildCreateMutationAST, buildUpdateMutationAST, buildDeleteMutationAST, printGraphQL, } from './gql-ast';
3
- import { getTableNames, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, getCreateMutationFileName, getUpdateMutationFileName, getDeleteMutationFileName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getScalarFields, fieldTypeToTs, ucFirst, lcFirst, } from './utils';
3
+ import { getTableNames, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, getCreateMutationFileName, getUpdateMutationFileName, getDeleteMutationFileName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getScalarFields, getPrimaryKeyInfo, fieldTypeToTs, ucFirst, lcFirst, } from './utils';
4
+ /**
5
+ * Check if a field is auto-generated and should be excluded from create inputs
6
+ * Uses primary key from constraints and common timestamp patterns
7
+ */
8
+ function isAutoGeneratedField(fieldName, pkFieldNames) {
9
+ const name = fieldName.toLowerCase();
10
+ // Exclude primary key fields (from constraints)
11
+ if (pkFieldNames.has(fieldName))
12
+ return true;
13
+ // Exclude common timestamp patterns (case-insensitive)
14
+ // These are typically auto-set by database triggers or defaults
15
+ const timestampPatterns = [
16
+ 'createdat', 'created_at', 'createddate', 'created_date',
17
+ 'updatedat', 'updated_at', 'updateddate', 'updated_date',
18
+ 'deletedat', 'deleted_at', // soft delete timestamps
19
+ ];
20
+ return timestampPatterns.includes(name);
21
+ }
4
22
  // ============================================================================
5
23
  // Create mutation hook generator
6
24
  // ============================================================================
7
25
  /**
8
26
  * Generate create mutation hook file content using AST
27
+ * When reactQueryEnabled is false, returns null since mutations require React Query
9
28
  */
10
- export function generateCreateMutationHook(table) {
29
+ export function generateCreateMutationHook(table, options = {}) {
30
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
31
+ // Mutations require React Query - skip generation when disabled
32
+ if (!reactQueryEnabled) {
33
+ return null;
34
+ }
35
+ const enumSet = new Set(enumsFromSchemaTypes);
11
36
  const project = createProject();
12
37
  const { typeName, singularName } = getTableNames(table);
13
38
  const hookName = getCreateMutationHookName(table);
14
39
  const mutationName = getCreateMutationName(table);
15
40
  const scalarFields = getScalarFields(table);
41
+ // Get primary key field names dynamically from table constraints
42
+ const pkFieldNames = new Set(getPrimaryKeyInfo(table).map(pk => pk.name));
43
+ // Collect which enums are used by this table's fields
44
+ const usedEnums = new Set();
45
+ for (const field of scalarFields) {
46
+ const cleanType = field.type.gqlType.replace(/!/g, '');
47
+ if (enumSet.has(cleanType)) {
48
+ usedEnums.add(cleanType);
49
+ }
50
+ }
16
51
  // Generate GraphQL document via AST
17
52
  const mutationAST = buildCreateMutationAST({ table });
18
53
  const mutationDocument = printGraphQL(mutationAST);
19
54
  const sourceFile = createSourceFile(project, getCreateMutationFileName(table));
20
55
  // Add file header
21
56
  sourceFile.insertText(0, createFileHeader(`Create mutation hook for ${typeName}`) + '\n\n');
22
- // Add imports
23
- sourceFile.addImportDeclarations([
57
+ // Build import declarations
58
+ const imports = [
24
59
  createImport({
25
60
  moduleSpecifier: '@tanstack/react-query',
26
61
  namedImports: ['useMutation', 'useQueryClient'],
@@ -34,7 +69,16 @@ export function generateCreateMutationHook(table) {
34
69
  moduleSpecifier: '../types',
35
70
  typeOnlyNamedImports: [typeName],
36
71
  }),
37
- ]);
72
+ ];
73
+ // Add import for enum types from schema-types if any are used
74
+ if (usedEnums.size > 0) {
75
+ imports.push(createImport({
76
+ moduleSpecifier: '../schema-types',
77
+ typeOnlyNamedImports: Array.from(usedEnums).sort(),
78
+ }));
79
+ }
80
+ // Add imports
81
+ sourceFile.addImportDeclarations(imports);
38
82
  // Re-export entity type
39
83
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
40
84
  // Add section comment
@@ -48,11 +92,9 @@ export function generateCreateMutationHook(table) {
48
92
  sourceFile.addStatements('// Types');
49
93
  sourceFile.addStatements('// ============================================================================\n');
50
94
  // Generate CreateInput type - exclude auto-generated fields
95
+ // Note: Not exported to avoid conflicts with schema-types
51
96
  const inputFields = scalarFields
52
- .filter((f) => {
53
- const name = f.name.toLowerCase();
54
- return !['id', 'createdat', 'updatedat', 'created_at', 'updated_at'].includes(name);
55
- })
97
+ .filter((f) => !isAutoGeneratedField(f.name, pkFieldNames))
56
98
  .map((f) => ({
57
99
  name: f.name,
58
100
  type: `${fieldTypeToTs(f.type)} | null`,
@@ -60,6 +102,7 @@ export function generateCreateMutationHook(table) {
60
102
  }));
61
103
  sourceFile.addInterface(createInterface(`${typeName}CreateInput`, inputFields, {
62
104
  docs: [`Input type for creating a ${typeName}`],
105
+ isExported: false,
63
106
  }));
64
107
  // Variables interface
65
108
  sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
@@ -137,25 +180,44 @@ mutate({
137
180
  // ============================================================================
138
181
  /**
139
182
  * Generate update mutation hook file content using AST
183
+ * When reactQueryEnabled is false, returns null since mutations require React Query
140
184
  */
141
- export function generateUpdateMutationHook(table) {
185
+ export function generateUpdateMutationHook(table, options = {}) {
186
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
187
+ // Mutations require React Query - skip generation when disabled
188
+ if (!reactQueryEnabled) {
189
+ return null;
190
+ }
142
191
  // Check if update mutation exists
143
192
  if (table.query?.update === null) {
144
193
  return null;
145
194
  }
195
+ const enumSet = new Set(enumsFromSchemaTypes);
146
196
  const project = createProject();
147
197
  const { typeName, singularName } = getTableNames(table);
148
198
  const hookName = getUpdateMutationHookName(table);
149
199
  const mutationName = getUpdateMutationName(table);
150
200
  const scalarFields = getScalarFields(table);
201
+ // Get primary key info dynamically from table constraints
202
+ const pkFields = getPrimaryKeyInfo(table);
203
+ const pkField = pkFields[0]; // Use first PK field
204
+ const pkFieldNames = new Set(pkFields.map(pk => pk.name));
205
+ // Collect which enums are used by this table's fields
206
+ const usedEnums = new Set();
207
+ for (const field of scalarFields) {
208
+ const cleanType = field.type.gqlType.replace(/!/g, '');
209
+ if (enumSet.has(cleanType)) {
210
+ usedEnums.add(cleanType);
211
+ }
212
+ }
151
213
  // Generate GraphQL document via AST
152
214
  const mutationAST = buildUpdateMutationAST({ table });
153
215
  const mutationDocument = printGraphQL(mutationAST);
154
216
  const sourceFile = createSourceFile(project, getUpdateMutationFileName(table));
155
217
  // Add file header
156
218
  sourceFile.insertText(0, createFileHeader(`Update mutation hook for ${typeName}`) + '\n\n');
157
- // Add imports
158
- sourceFile.addImportDeclarations([
219
+ // Build import declarations
220
+ const imports = [
159
221
  createImport({
160
222
  moduleSpecifier: '@tanstack/react-query',
161
223
  namedImports: ['useMutation', 'useQueryClient'],
@@ -169,7 +231,16 @@ export function generateUpdateMutationHook(table) {
169
231
  moduleSpecifier: '../types',
170
232
  typeOnlyNamedImports: [typeName],
171
233
  }),
172
- ]);
234
+ ];
235
+ // Add import for enum types from schema-types if any are used
236
+ if (usedEnums.size > 0) {
237
+ imports.push(createImport({
238
+ moduleSpecifier: '../schema-types',
239
+ typeOnlyNamedImports: Array.from(usedEnums).sort(),
240
+ }));
241
+ }
242
+ // Add imports
243
+ sourceFile.addImportDeclarations(imports);
173
244
  // Re-export entity type
174
245
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
175
246
  // Add section comment
@@ -182,9 +253,10 @@ export function generateUpdateMutationHook(table) {
182
253
  sourceFile.addStatements('\n// ============================================================================');
183
254
  sourceFile.addStatements('// Types');
184
255
  sourceFile.addStatements('// ============================================================================\n');
185
- // Generate Patch type - all fields optional
256
+ // Generate Patch type - all fields optional, exclude primary key
257
+ // Note: Not exported to avoid conflicts with schema-types
186
258
  const patchFields = scalarFields
187
- .filter((f) => f.name.toLowerCase() !== 'id')
259
+ .filter((f) => !pkFieldNames.has(f.name))
188
260
  .map((f) => ({
189
261
  name: f.name,
190
262
  type: `${fieldTypeToTs(f.type)} | null`,
@@ -192,13 +264,14 @@ export function generateUpdateMutationHook(table) {
192
264
  }));
193
265
  sourceFile.addInterface(createInterface(`${typeName}Patch`, patchFields, {
194
266
  docs: [`Patch type for updating a ${typeName} - all fields optional`],
267
+ isExported: false,
195
268
  }));
196
- // Variables interface
269
+ // Variables interface - use dynamic PK field name and type
197
270
  sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
198
271
  {
199
272
  name: 'input',
200
273
  type: `{
201
- id: string;
274
+ ${pkField.name}: ${pkField.tsType};
202
275
  patch: ${typeName}Patch;
203
276
  }`,
204
277
  },
@@ -237,7 +310,7 @@ export function generateUpdateMutationHook(table) {
237
310
  ),
238
311
  onSuccess: (_, variables) => {
239
312
  // Invalidate specific item and list queries
240
- queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.id] });
313
+ queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
241
314
  queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
242
315
  },
243
316
  ...options,
@@ -252,7 +325,7 @@ const { mutate, isPending } = ${hookName}();
252
325
 
253
326
  mutate({
254
327
  input: {
255
- id: 'uuid-here',
328
+ ${pkField.name}: ${pkField.tsType === 'string' ? "'value-here'" : '123'},
256
329
  patch: {
257
330
  // ... fields to update
258
331
  },
@@ -272,8 +345,14 @@ mutate({
272
345
  // ============================================================================
273
346
  /**
274
347
  * Generate delete mutation hook file content using AST
348
+ * When reactQueryEnabled is false, returns null since mutations require React Query
275
349
  */
276
- export function generateDeleteMutationHook(table) {
350
+ export function generateDeleteMutationHook(table, options = {}) {
351
+ const { reactQueryEnabled = true } = options;
352
+ // Mutations require React Query - skip generation when disabled
353
+ if (!reactQueryEnabled) {
354
+ return null;
355
+ }
277
356
  // Check if delete mutation exists
278
357
  if (table.query?.delete === null) {
279
358
  return null;
@@ -282,6 +361,9 @@ export function generateDeleteMutationHook(table) {
282
361
  const { typeName } = getTableNames(table);
283
362
  const hookName = getDeleteMutationHookName(table);
284
363
  const mutationName = getDeleteMutationName(table);
364
+ // Get primary key info dynamically from table constraints
365
+ const pkFields = getPrimaryKeyInfo(table);
366
+ const pkField = pkFields[0]; // Use first PK field
285
367
  // Generate GraphQL document via AST
286
368
  const mutationAST = buildDeleteMutationAST({ table });
287
369
  const mutationDocument = printGraphQL(mutationAST);
@@ -310,12 +392,12 @@ export function generateDeleteMutationHook(table) {
310
392
  sourceFile.addStatements('\n// ============================================================================');
311
393
  sourceFile.addStatements('// Types');
312
394
  sourceFile.addStatements('// ============================================================================\n');
313
- // Variables interface
395
+ // Variables interface - use dynamic PK field name and type
314
396
  sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
315
397
  {
316
398
  name: 'input',
317
399
  type: `{
318
- id: string;
400
+ ${pkField.name}: ${pkField.tsType};
319
401
  }`,
320
402
  },
321
403
  ]));
@@ -325,7 +407,7 @@ export function generateDeleteMutationHook(table) {
325
407
  name: mutationName,
326
408
  type: `{
327
409
  clientMutationId: string | null;
328
- deletedId: string | null;
410
+ deleted${ucFirst(pkField.name)}: ${pkField.tsType} | null;
329
411
  }`,
330
412
  },
331
413
  ]));
@@ -354,7 +436,7 @@ export function generateDeleteMutationHook(table) {
354
436
  ),
355
437
  onSuccess: (_, variables) => {
356
438
  // Remove from cache and invalidate list
357
- queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.id] });
439
+ queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
358
440
  queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
359
441
  },
360
442
  ...options,
@@ -369,7 +451,7 @@ const { mutate, isPending } = ${hookName}();
369
451
 
370
452
  mutate({
371
453
  input: {
372
- id: 'uuid-to-delete',
454
+ ${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'},
373
455
  },
374
456
  });
375
457
  \`\`\``,
@@ -386,16 +468,20 @@ mutate({
386
468
  // ============================================================================
387
469
  /**
388
470
  * Generate all mutation hook files for all tables
471
+ * When reactQueryEnabled is false, returns empty array since mutations require React Query
389
472
  */
390
- export function generateAllMutationHooks(tables) {
473
+ export function generateAllMutationHooks(tables, options = {}) {
391
474
  const files = [];
392
475
  for (const table of tables) {
393
- files.push(generateCreateMutationHook(table));
394
- const updateHook = generateUpdateMutationHook(table);
476
+ const createHook = generateCreateMutationHook(table, options);
477
+ if (createHook) {
478
+ files.push(createHook);
479
+ }
480
+ const updateHook = generateUpdateMutationHook(table, options);
395
481
  if (updateHook) {
396
482
  files.push(updateHook);
397
483
  }
398
- const deleteHook = generateDeleteMutationHook(table);
484
+ const deleteHook = generateDeleteMutationHook(table, options);
399
485
  if (deleteHook) {
400
486
  files.push(deleteHook);
401
487
  }
@@ -12,9 +12,11 @@ export function generateModelsBarrel(tables) {
12
12
  for (const table of tables) {
13
13
  const { typeName } = getTableNames(table);
14
14
  const modelName = `${typeName}Model`;
15
- const fileName = lcFirst(typeName);
15
+ // Use same naming logic as model-generator to avoid "index.ts" clash with barrel file
16
+ const baseFileName = lcFirst(typeName);
17
+ const moduleFileName = baseFileName === 'index' ? `${baseFileName}Model` : baseFileName;
16
18
  sourceFile.addExportDeclaration({
17
- moduleSpecifier: `./${fileName}`,
19
+ moduleSpecifier: `./${moduleFileName}`,
18
20
  namedExports: [modelName],
19
21
  });
20
22
  }
@@ -40,6 +40,23 @@ export function generateOrm(options) {
40
40
  ];
41
41
  const usedInputTypes = collectInputTypeNames(allOps);
42
42
  const usedPayloadTypes = collectPayloadTypeNames(allOps);
43
+ // Also include payload types for table CRUD mutations (they reference Edge types)
44
+ if (typeRegistry) {
45
+ for (const table of tables) {
46
+ const typeName = table.name;
47
+ // Add standard CRUD payload types
48
+ const crudPayloadTypes = [
49
+ `Create${typeName}Payload`,
50
+ `Update${typeName}Payload`,
51
+ `Delete${typeName}Payload`,
52
+ ];
53
+ for (const payloadType of crudPayloadTypes) {
54
+ if (typeRegistry.has(payloadType)) {
55
+ usedPayloadTypes.add(payloadType);
56
+ }
57
+ }
58
+ }
59
+ }
43
60
  const inputTypesFile = generateInputTypesFile(typeRegistry ?? new Map(), usedInputTypes, tables, usedPayloadTypes);
44
61
  files.push({ path: inputTypesFile.fileName, content: inputTypesFile.content });
45
62
  }
@@ -117,6 +117,47 @@ function addScalarFilterTypes(sourceFile) {
117
117
  }
118
118
  }
119
119
  // ============================================================================
120
+ // Enum Types Collector
121
+ // ============================================================================
122
+ /**
123
+ * Check if a type is likely an enum (not a scalar and not ending with Input/Filter/etc)
124
+ */
125
+ function isLikelyEnumType(typeName, typeRegistry) {
126
+ const typeInfo = typeRegistry.get(typeName);
127
+ return typeInfo?.kind === 'ENUM';
128
+ }
129
+ /**
130
+ * Collect enum types used by table fields
131
+ */
132
+ function collectEnumTypesFromTables(tables, typeRegistry) {
133
+ const enumTypes = new Set();
134
+ for (const table of tables) {
135
+ for (const field of table.fields) {
136
+ const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
137
+ // Check if this type is an enum in the registry
138
+ if (isLikelyEnumType(fieldType, typeRegistry)) {
139
+ enumTypes.add(fieldType);
140
+ }
141
+ }
142
+ }
143
+ return enumTypes;
144
+ }
145
+ /**
146
+ * Add enum types to source file
147
+ */
148
+ function addEnumTypes(sourceFile, typeRegistry, enumTypeNames) {
149
+ if (enumTypeNames.size === 0)
150
+ return;
151
+ addSectionComment(sourceFile, 'Enum Types');
152
+ for (const typeName of Array.from(enumTypeNames).sort()) {
153
+ const typeInfo = typeRegistry.get(typeName);
154
+ if (!typeInfo || typeInfo.kind !== 'ENUM' || !typeInfo.enumValues)
155
+ continue;
156
+ const values = typeInfo.enumValues.map((v) => `'${v}'`).join(' | ');
157
+ sourceFile.addTypeAlias(createTypeAlias(typeName, values));
158
+ }
159
+ }
160
+ // ============================================================================
120
161
  // Entity Types Generator (AST-based)
121
162
  // ============================================================================
122
163
  /**
@@ -382,10 +423,12 @@ function buildOrderByUnion(table) {
382
423
  }
383
424
  /**
384
425
  * Add OrderBy types
426
+ * Uses inflection from table metadata for correct pluralization
385
427
  */
386
428
  function addOrderByTypes(sourceFile, tables) {
387
429
  addSectionComment(sourceFile, 'OrderBy Types');
388
430
  for (const table of tables) {
431
+ // Use getOrderByTypeName which respects table.inflection.orderByType
389
432
  const enumName = getOrderByTypeName(table);
390
433
  sourceFile.addTypeAlias(createTypeAlias(enumName, buildOrderByUnion(table)));
391
434
  }
@@ -504,32 +547,39 @@ export function collectInputTypeNames(operations) {
504
547
  }
505
548
  return inputTypes;
506
549
  }
550
+ /**
551
+ * Build a set of exact table CRUD input type names to skip
552
+ * These are generated by addAllCrudInputTypes, so we don't need to regenerate them
553
+ */
554
+ function buildTableCrudTypeNames(tables) {
555
+ const crudTypes = new Set();
556
+ for (const table of tables) {
557
+ const { typeName } = getTableNames(table);
558
+ crudTypes.add(`Create${typeName}Input`);
559
+ crudTypes.add(`Update${typeName}Input`);
560
+ crudTypes.add(`Delete${typeName}Input`);
561
+ crudTypes.add(`${typeName}Filter`);
562
+ crudTypes.add(`${typeName}Patch`);
563
+ }
564
+ return crudTypes;
565
+ }
507
566
  /**
508
567
  * Add custom input types from TypeRegistry
509
568
  */
510
- function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes) {
569
+ function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrudTypes) {
511
570
  addSectionComment(sourceFile, 'Custom Input Types (from schema)');
512
571
  const generatedTypes = new Set();
513
572
  const typesToGenerate = new Set(Array.from(usedInputTypes));
514
- // Filter out types we've already generated
515
- const typesToRemove = [];
516
- typesToGenerate.forEach((typeName) => {
517
- if (typeName.endsWith('Filter') ||
518
- typeName.startsWith('Create') ||
519
- typeName.startsWith('Update') ||
520
- typeName.startsWith('Delete')) {
521
- const isTableCrud = /^(Create|Update|Delete)[A-Z][a-zA-Z]+Input$/.test(typeName) ||
522
- /^[A-Z][a-zA-Z]+Filter$/.test(typeName);
523
- if (isTableCrud) {
524
- typesToRemove.push(typeName);
573
+ // Filter out types we've already generated (exact matches for table CRUD types only)
574
+ if (tableCrudTypes) {
575
+ for (const typeName of Array.from(typesToGenerate)) {
576
+ if (tableCrudTypes.has(typeName)) {
577
+ typesToGenerate.delete(typeName);
525
578
  }
526
579
  }
527
- });
528
- typesToRemove.forEach((t) => typesToGenerate.delete(t));
529
- let iterations = 0;
530
- const maxIterations = 200;
531
- while (typesToGenerate.size > 0 && iterations < maxIterations) {
532
- iterations++;
580
+ }
581
+ // Process all types - no artificial limit
582
+ while (typesToGenerate.size > 0) {
533
583
  const typeNameResult = typesToGenerate.values().next();
534
584
  if (typeNameResult.done)
535
585
  break;
@@ -595,10 +645,8 @@ function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGene
595
645
  'String', 'Int', 'Float', 'Boolean', 'ID', 'UUID', 'Datetime', 'Date',
596
646
  'Time', 'JSON', 'BigInt', 'BigFloat', 'Cursor', 'Query', 'Mutation',
597
647
  ]);
598
- let iterations = 0;
599
- const maxIterations = 200;
600
- while (typesToGenerate.size > 0 && iterations < maxIterations) {
601
- iterations++;
648
+ // Process all types - no artificial limit
649
+ while (typesToGenerate.size > 0) {
602
650
  const typeNameResult = typesToGenerate.values().next();
603
651
  if (typeNameResult.done)
604
652
  break;
@@ -665,7 +713,12 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
665
713
  sourceFile.insertText(0, createFileHeader('GraphQL types for ORM client') + '\n');
666
714
  // 1. Scalar filter types
667
715
  addScalarFilterTypes(sourceFile);
668
- // 2. Entity and relation types (if tables provided)
716
+ // 2. Enum types used by table fields
717
+ if (tables && tables.length > 0) {
718
+ const enumTypes = collectEnumTypesFromTables(tables, typeRegistry);
719
+ addEnumTypes(sourceFile, typeRegistry, enumTypes);
720
+ }
721
+ // 3. Entity and relation types (if tables provided)
669
722
  if (tables && tables.length > 0) {
670
723
  const tableByName = new Map(tables.map((table) => [table.name, table]));
671
724
  addEntityTypes(sourceFile, tables);
@@ -673,16 +726,17 @@ export function generateInputTypesFile(typeRegistry, usedInputTypes, tables, use
673
726
  addEntityRelationTypes(sourceFile, tables, tableByName);
674
727
  addEntityWithRelations(sourceFile, tables);
675
728
  addEntitySelectTypes(sourceFile, tables, tableByName);
676
- // 3. Table filter types
729
+ // 4. Table filter types
677
730
  addTableFilterTypes(sourceFile, tables);
678
- // 4. OrderBy types
731
+ // 5. OrderBy types
679
732
  addOrderByTypes(sourceFile, tables);
680
- // 5. CRUD input types
733
+ // 6. CRUD input types
681
734
  addAllCrudInputTypes(sourceFile, tables);
682
735
  }
683
- // 6. Custom input types from TypeRegistry
684
- addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes);
685
- // 7. Payload/return types for custom operations
736
+ // 7. Custom input types from TypeRegistry
737
+ const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
738
+ addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrudTypes);
739
+ // 8. Payload/return types for custom operations
686
740
  if (usedPayloadTypes && usedPayloadTypes.size > 0) {
687
741
  const alreadyGeneratedTypes = new Set();
688
742
  if (tables) {
@@ -1,5 +1,5 @@
1
1
  import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, } from '../ts-ast';
2
- import { getTableNames, lcFirst } from '../utils';
2
+ import { getTableNames, getOrderByTypeName, getFilterTypeName, lcFirst } from '../utils';
3
3
  /**
4
4
  * Generate a model class file for a table
5
5
  */
@@ -7,13 +7,15 @@ export function generateModelFile(table, _useSharedTypes) {
7
7
  const project = createProject();
8
8
  const { typeName, singularName, pluralName } = getTableNames(table);
9
9
  const modelName = `${typeName}Model`;
10
- const fileName = `${lcFirst(typeName)}.ts`;
10
+ // Avoid "index.ts" which clashes with barrel file
11
+ const baseFileName = lcFirst(typeName);
12
+ const fileName = baseFileName === 'index' ? `${baseFileName}Model.ts` : `${baseFileName}.ts`;
11
13
  const entityLower = singularName;
12
- // Type names for this entity
14
+ // Type names for this entity - use inflection from table metadata
13
15
  const selectTypeName = `${typeName}Select`;
14
16
  const relationTypeName = `${typeName}WithRelations`;
15
- const whereTypeName = `${typeName}Filter`;
16
- const orderByTypeName = `${typeName}sOrderBy`; // PostGraphile uses plural
17
+ const whereTypeName = getFilterTypeName(table);
18
+ const orderByTypeName = getOrderByTypeName(table);
17
19
  const createInputTypeName = `Create${typeName}Input`;
18
20
  const updateInputTypeName = `Update${typeName}Input`;
19
21
  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[];