@constructive-io/graphql-codegen 2.21.0 → 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 (93) hide show
  1. package/cli/codegen/barrel.d.ts +4 -1
  2. package/cli/codegen/barrel.js +18 -12
  3. package/cli/codegen/client.js +33 -0
  4. package/cli/codegen/custom-mutations.d.ts +4 -0
  5. package/cli/codegen/custom-mutations.js +39 -13
  6. package/cli/codegen/custom-queries.d.ts +4 -0
  7. package/cli/codegen/custom-queries.js +36 -11
  8. package/cli/codegen/gql-ast.js +9 -5
  9. package/cli/codegen/index.js +35 -7
  10. package/cli/codegen/mutations.d.ts +2 -0
  11. package/cli/codegen/mutations.js +87 -23
  12. package/cli/codegen/orm/barrel.js +4 -2
  13. package/cli/codegen/orm/index.js +17 -0
  14. package/cli/codegen/orm/input-types-generator.js +83 -29
  15. package/cli/codegen/orm/model-generator.js +6 -4
  16. package/cli/codegen/queries.js +36 -27
  17. package/cli/codegen/scalars.d.ts +6 -4
  18. package/cli/codegen/scalars.js +17 -9
  19. package/cli/codegen/schema-types-generator.d.ts +26 -0
  20. package/cli/codegen/schema-types-generator.js +365 -0
  21. package/cli/codegen/ts-ast.d.ts +3 -1
  22. package/cli/codegen/ts-ast.js +2 -2
  23. package/cli/codegen/type-resolver.d.ts +52 -6
  24. package/cli/codegen/type-resolver.js +97 -19
  25. package/cli/codegen/types.d.ts +7 -4
  26. package/cli/codegen/types.js +94 -41
  27. package/cli/codegen/utils.d.ts +20 -2
  28. package/cli/codegen/utils.js +32 -7
  29. package/cli/commands/generate-orm.js +5 -5
  30. package/cli/commands/generate.d.ts +4 -1
  31. package/cli/commands/generate.js +27 -8
  32. package/cli/introspect/transform-schema.d.ts +33 -21
  33. package/cli/introspect/transform-schema.js +31 -21
  34. package/esm/cli/codegen/barrel.d.ts +4 -1
  35. package/esm/cli/codegen/barrel.js +18 -12
  36. package/esm/cli/codegen/client.js +33 -0
  37. package/esm/cli/codegen/custom-mutations.d.ts +4 -0
  38. package/esm/cli/codegen/custom-mutations.js +40 -14
  39. package/esm/cli/codegen/custom-queries.d.ts +4 -0
  40. package/esm/cli/codegen/custom-queries.js +37 -12
  41. package/esm/cli/codegen/gql-ast.js +10 -6
  42. package/esm/cli/codegen/index.js +35 -7
  43. package/esm/cli/codegen/mutations.d.ts +2 -0
  44. package/esm/cli/codegen/mutations.js +88 -24
  45. package/esm/cli/codegen/orm/barrel.js +4 -2
  46. package/esm/cli/codegen/orm/index.js +17 -0
  47. package/esm/cli/codegen/orm/input-types-generator.js +83 -29
  48. package/esm/cli/codegen/orm/model-generator.js +7 -5
  49. package/esm/cli/codegen/queries.js +37 -28
  50. package/esm/cli/codegen/scalars.d.ts +6 -4
  51. package/esm/cli/codegen/scalars.js +16 -8
  52. package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
  53. package/esm/cli/codegen/schema-types-generator.js +362 -0
  54. package/esm/cli/codegen/ts-ast.d.ts +3 -1
  55. package/esm/cli/codegen/ts-ast.js +2 -2
  56. package/esm/cli/codegen/type-resolver.d.ts +52 -6
  57. package/esm/cli/codegen/type-resolver.js +97 -20
  58. package/esm/cli/codegen/types.d.ts +7 -4
  59. package/esm/cli/codegen/types.js +95 -41
  60. package/esm/cli/codegen/utils.d.ts +20 -2
  61. package/esm/cli/codegen/utils.js +31 -7
  62. package/esm/cli/commands/generate-orm.js +5 -5
  63. package/esm/cli/commands/generate.d.ts +4 -1
  64. package/esm/cli/commands/generate.js +27 -8
  65. package/esm/cli/introspect/transform-schema.d.ts +33 -21
  66. package/esm/cli/introspect/transform-schema.js +31 -21
  67. package/esm/types/schema.d.ts +2 -0
  68. package/package.json +8 -6
  69. package/types/schema.d.ts +2 -0
  70. package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  71. package/__tests__/codegen/input-types-generator.test.js +0 -635
  72. package/__tests__/codegen/react-query-optional.test.d.ts +0 -1
  73. package/__tests__/codegen/react-query-optional.test.js +0 -292
  74. package/cli/codegen/filters.d.ts +0 -27
  75. package/cli/codegen/filters.js +0 -357
  76. package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  77. package/cli/codegen/orm/input-types-generator.test.js +0 -75
  78. package/cli/codegen/orm/select-types.test.d.ts +0 -11
  79. package/cli/codegen/orm/select-types.test.js +0 -22
  80. package/cli/introspect/transform-schema.test.d.ts +0 -1
  81. package/cli/introspect/transform-schema.test.js +0 -67
  82. package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
  83. package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
  84. package/esm/__tests__/codegen/react-query-optional.test.d.ts +0 -1
  85. package/esm/__tests__/codegen/react-query-optional.test.js +0 -290
  86. package/esm/cli/codegen/filters.d.ts +0 -27
  87. package/esm/cli/codegen/filters.js +0 -351
  88. package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
  89. package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
  90. package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
  91. package/esm/cli/codegen/orm/select-types.test.js +0 -21
  92. package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
  93. package/esm/cli/introspect/transform-schema.test.js +0 -65
@@ -7,6 +7,24 @@ exports.generateAllMutationHooks = generateAllMutationHooks;
7
7
  const ts_ast_1 = require("./ts-ast");
8
8
  const gql_ast_1 = require("./gql-ast");
9
9
  const utils_1 = require("./utils");
10
+ /**
11
+ * Check if a field is auto-generated and should be excluded from create inputs
12
+ * Uses primary key from constraints and common timestamp patterns
13
+ */
14
+ function isAutoGeneratedField(fieldName, pkFieldNames) {
15
+ const name = fieldName.toLowerCase();
16
+ // Exclude primary key fields (from constraints)
17
+ if (pkFieldNames.has(fieldName))
18
+ return true;
19
+ // Exclude common timestamp patterns (case-insensitive)
20
+ // These are typically auto-set by database triggers or defaults
21
+ const timestampPatterns = [
22
+ 'createdat', 'created_at', 'createddate', 'created_date',
23
+ 'updatedat', 'updated_at', 'updateddate', 'updated_date',
24
+ 'deletedat', 'deleted_at', // soft delete timestamps
25
+ ];
26
+ return timestampPatterns.includes(name);
27
+ }
10
28
  // ============================================================================
11
29
  // Create mutation hook generator
12
30
  // ============================================================================
@@ -15,24 +33,35 @@ const utils_1 = require("./utils");
15
33
  * When reactQueryEnabled is false, returns null since mutations require React Query
16
34
  */
17
35
  function generateCreateMutationHook(table, options = {}) {
18
- const { reactQueryEnabled = true } = options;
36
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
19
37
  // Mutations require React Query - skip generation when disabled
20
38
  if (!reactQueryEnabled) {
21
39
  return null;
22
40
  }
41
+ const enumSet = new Set(enumsFromSchemaTypes);
23
42
  const project = (0, ts_ast_1.createProject)();
24
43
  const { typeName, singularName } = (0, utils_1.getTableNames)(table);
25
44
  const hookName = (0, utils_1.getCreateMutationHookName)(table);
26
45
  const mutationName = (0, utils_1.getCreateMutationName)(table);
27
46
  const scalarFields = (0, utils_1.getScalarFields)(table);
47
+ // Get primary key field names dynamically from table constraints
48
+ const pkFieldNames = new Set((0, utils_1.getPrimaryKeyInfo)(table).map(pk => pk.name));
49
+ // Collect which enums are used by this table's fields
50
+ const usedEnums = new Set();
51
+ for (const field of scalarFields) {
52
+ const cleanType = field.type.gqlType.replace(/!/g, '');
53
+ if (enumSet.has(cleanType)) {
54
+ usedEnums.add(cleanType);
55
+ }
56
+ }
28
57
  // Generate GraphQL document via AST
29
58
  const mutationAST = (0, gql_ast_1.buildCreateMutationAST)({ table });
30
59
  const mutationDocument = (0, gql_ast_1.printGraphQL)(mutationAST);
31
60
  const sourceFile = (0, ts_ast_1.createSourceFile)(project, (0, utils_1.getCreateMutationFileName)(table));
32
61
  // Add file header
33
62
  sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)(`Create mutation hook for ${typeName}`) + '\n\n');
34
- // Add imports
35
- sourceFile.addImportDeclarations([
63
+ // Build import declarations
64
+ const imports = [
36
65
  (0, ts_ast_1.createImport)({
37
66
  moduleSpecifier: '@tanstack/react-query',
38
67
  namedImports: ['useMutation', 'useQueryClient'],
@@ -46,7 +75,16 @@ function generateCreateMutationHook(table, options = {}) {
46
75
  moduleSpecifier: '../types',
47
76
  typeOnlyNamedImports: [typeName],
48
77
  }),
49
- ]);
78
+ ];
79
+ // Add import for enum types from schema-types if any are used
80
+ if (usedEnums.size > 0) {
81
+ imports.push((0, ts_ast_1.createImport)({
82
+ moduleSpecifier: '../schema-types',
83
+ typeOnlyNamedImports: Array.from(usedEnums).sort(),
84
+ }));
85
+ }
86
+ // Add imports
87
+ sourceFile.addImportDeclarations(imports);
50
88
  // Re-export entity type
51
89
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
52
90
  // Add section comment
@@ -60,11 +98,9 @@ function generateCreateMutationHook(table, options = {}) {
60
98
  sourceFile.addStatements('// Types');
61
99
  sourceFile.addStatements('// ============================================================================\n');
62
100
  // Generate CreateInput type - exclude auto-generated fields
101
+ // Note: Not exported to avoid conflicts with schema-types
63
102
  const inputFields = scalarFields
64
- .filter((f) => {
65
- const name = f.name.toLowerCase();
66
- return !['id', 'createdat', 'updatedat', 'created_at', 'updated_at'].includes(name);
67
- })
103
+ .filter((f) => !isAutoGeneratedField(f.name, pkFieldNames))
68
104
  .map((f) => ({
69
105
  name: f.name,
70
106
  type: `${(0, utils_1.fieldTypeToTs)(f.type)} | null`,
@@ -72,6 +108,7 @@ function generateCreateMutationHook(table, options = {}) {
72
108
  }));
73
109
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${typeName}CreateInput`, inputFields, {
74
110
  docs: [`Input type for creating a ${typeName}`],
111
+ isExported: false,
75
112
  }));
76
113
  // Variables interface
77
114
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`, [
@@ -152,7 +189,7 @@ mutate({
152
189
  * When reactQueryEnabled is false, returns null since mutations require React Query
153
190
  */
154
191
  function generateUpdateMutationHook(table, options = {}) {
155
- const { reactQueryEnabled = true } = options;
192
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
156
193
  // Mutations require React Query - skip generation when disabled
157
194
  if (!reactQueryEnabled) {
158
195
  return null;
@@ -161,19 +198,32 @@ function generateUpdateMutationHook(table, options = {}) {
161
198
  if (table.query?.update === null) {
162
199
  return null;
163
200
  }
201
+ const enumSet = new Set(enumsFromSchemaTypes);
164
202
  const project = (0, ts_ast_1.createProject)();
165
203
  const { typeName, singularName } = (0, utils_1.getTableNames)(table);
166
204
  const hookName = (0, utils_1.getUpdateMutationHookName)(table);
167
205
  const mutationName = (0, utils_1.getUpdateMutationName)(table);
168
206
  const scalarFields = (0, utils_1.getScalarFields)(table);
207
+ // Get primary key info dynamically from table constraints
208
+ const pkFields = (0, utils_1.getPrimaryKeyInfo)(table);
209
+ const pkField = pkFields[0]; // Use first PK field
210
+ const pkFieldNames = new Set(pkFields.map(pk => pk.name));
211
+ // Collect which enums are used by this table's fields
212
+ const usedEnums = new Set();
213
+ for (const field of scalarFields) {
214
+ const cleanType = field.type.gqlType.replace(/!/g, '');
215
+ if (enumSet.has(cleanType)) {
216
+ usedEnums.add(cleanType);
217
+ }
218
+ }
169
219
  // Generate GraphQL document via AST
170
220
  const mutationAST = (0, gql_ast_1.buildUpdateMutationAST)({ table });
171
221
  const mutationDocument = (0, gql_ast_1.printGraphQL)(mutationAST);
172
222
  const sourceFile = (0, ts_ast_1.createSourceFile)(project, (0, utils_1.getUpdateMutationFileName)(table));
173
223
  // Add file header
174
224
  sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)(`Update mutation hook for ${typeName}`) + '\n\n');
175
- // Add imports
176
- sourceFile.addImportDeclarations([
225
+ // Build import declarations
226
+ const imports = [
177
227
  (0, ts_ast_1.createImport)({
178
228
  moduleSpecifier: '@tanstack/react-query',
179
229
  namedImports: ['useMutation', 'useQueryClient'],
@@ -187,7 +237,16 @@ function generateUpdateMutationHook(table, options = {}) {
187
237
  moduleSpecifier: '../types',
188
238
  typeOnlyNamedImports: [typeName],
189
239
  }),
190
- ]);
240
+ ];
241
+ // Add import for enum types from schema-types if any are used
242
+ if (usedEnums.size > 0) {
243
+ imports.push((0, ts_ast_1.createImport)({
244
+ moduleSpecifier: '../schema-types',
245
+ typeOnlyNamedImports: Array.from(usedEnums).sort(),
246
+ }));
247
+ }
248
+ // Add imports
249
+ sourceFile.addImportDeclarations(imports);
191
250
  // Re-export entity type
192
251
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
193
252
  // Add section comment
@@ -200,9 +259,10 @@ function generateUpdateMutationHook(table, options = {}) {
200
259
  sourceFile.addStatements('\n// ============================================================================');
201
260
  sourceFile.addStatements('// Types');
202
261
  sourceFile.addStatements('// ============================================================================\n');
203
- // Generate Patch type - all fields optional
262
+ // Generate Patch type - all fields optional, exclude primary key
263
+ // Note: Not exported to avoid conflicts with schema-types
204
264
  const patchFields = scalarFields
205
- .filter((f) => f.name.toLowerCase() !== 'id')
265
+ .filter((f) => !pkFieldNames.has(f.name))
206
266
  .map((f) => ({
207
267
  name: f.name,
208
268
  type: `${(0, utils_1.fieldTypeToTs)(f.type)} | null`,
@@ -210,13 +270,14 @@ function generateUpdateMutationHook(table, options = {}) {
210
270
  }));
211
271
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${typeName}Patch`, patchFields, {
212
272
  docs: [`Patch type for updating a ${typeName} - all fields optional`],
273
+ isExported: false,
213
274
  }));
214
- // Variables interface
275
+ // Variables interface - use dynamic PK field name and type
215
276
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`, [
216
277
  {
217
278
  name: 'input',
218
279
  type: `{
219
- id: string;
280
+ ${pkField.name}: ${pkField.tsType};
220
281
  patch: ${typeName}Patch;
221
282
  }`,
222
283
  },
@@ -255,7 +316,7 @@ function generateUpdateMutationHook(table, options = {}) {
255
316
  ),
256
317
  onSuccess: (_, variables) => {
257
318
  // Invalidate specific item and list queries
258
- queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.id] });
319
+ queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
259
320
  queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
260
321
  },
261
322
  ...options,
@@ -270,7 +331,7 @@ const { mutate, isPending } = ${hookName}();
270
331
 
271
332
  mutate({
272
333
  input: {
273
- id: 'uuid-here',
334
+ ${pkField.name}: ${pkField.tsType === 'string' ? "'value-here'" : '123'},
274
335
  patch: {
275
336
  // ... fields to update
276
337
  },
@@ -306,6 +367,9 @@ function generateDeleteMutationHook(table, options = {}) {
306
367
  const { typeName } = (0, utils_1.getTableNames)(table);
307
368
  const hookName = (0, utils_1.getDeleteMutationHookName)(table);
308
369
  const mutationName = (0, utils_1.getDeleteMutationName)(table);
370
+ // Get primary key info dynamically from table constraints
371
+ const pkFields = (0, utils_1.getPrimaryKeyInfo)(table);
372
+ const pkField = pkFields[0]; // Use first PK field
309
373
  // Generate GraphQL document via AST
310
374
  const mutationAST = (0, gql_ast_1.buildDeleteMutationAST)({ table });
311
375
  const mutationDocument = (0, gql_ast_1.printGraphQL)(mutationAST);
@@ -334,12 +398,12 @@ function generateDeleteMutationHook(table, options = {}) {
334
398
  sourceFile.addStatements('\n// ============================================================================');
335
399
  sourceFile.addStatements('// Types');
336
400
  sourceFile.addStatements('// ============================================================================\n');
337
- // Variables interface
401
+ // Variables interface - use dynamic PK field name and type
338
402
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`, [
339
403
  {
340
404
  name: 'input',
341
405
  type: `{
342
- id: string;
406
+ ${pkField.name}: ${pkField.tsType};
343
407
  }`,
344
408
  },
345
409
  ]));
@@ -349,7 +413,7 @@ function generateDeleteMutationHook(table, options = {}) {
349
413
  name: mutationName,
350
414
  type: `{
351
415
  clientMutationId: string | null;
352
- deletedId: string | null;
416
+ deleted${(0, utils_1.ucFirst)(pkField.name)}: ${pkField.tsType} | null;
353
417
  }`,
354
418
  },
355
419
  ]));
@@ -378,7 +442,7 @@ function generateDeleteMutationHook(table, options = {}) {
378
442
  ),
379
443
  onSuccess: (_, variables) => {
380
444
  // Remove from cache and invalidate list
381
- queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.id] });
445
+ queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
382
446
  queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
383
447
  },
384
448
  ...options,
@@ -393,7 +457,7 @@ const { mutate, isPending } = ${hookName}();
393
457
 
394
458
  mutate({
395
459
  input: {
396
- id: 'uuid-to-delete',
460
+ ${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'},
397
461
  },
398
462
  });
399
463
  \`\`\``,
@@ -16,9 +16,11 @@ function generateModelsBarrel(tables) {
16
16
  for (const table of tables) {
17
17
  const { typeName } = (0, utils_1.getTableNames)(table);
18
18
  const modelName = `${typeName}Model`;
19
- const fileName = (0, utils_1.lcFirst)(typeName);
19
+ // Use same naming logic as model-generator to avoid "index.ts" clash with barrel file
20
+ const baseFileName = (0, utils_1.lcFirst)(typeName);
21
+ const moduleFileName = baseFileName === 'index' ? `${baseFileName}Model` : baseFileName;
20
22
  sourceFile.addExportDeclaration({
21
- moduleSpecifier: `./${fileName}`,
23
+ moduleSpecifier: `./${moduleFileName}`,
22
24
  namedExports: [modelName],
23
25
  });
24
26
  }
@@ -44,6 +44,23 @@ function generateOrm(options) {
44
44
  ];
45
45
  const usedInputTypes = (0, input_types_generator_1.collectInputTypeNames)(allOps);
46
46
  const usedPayloadTypes = (0, input_types_generator_1.collectPayloadTypeNames)(allOps);
47
+ // Also include payload types for table CRUD mutations (they reference Edge types)
48
+ if (typeRegistry) {
49
+ for (const table of tables) {
50
+ const typeName = table.name;
51
+ // Add standard CRUD payload types
52
+ const crudPayloadTypes = [
53
+ `Create${typeName}Payload`,
54
+ `Update${typeName}Payload`,
55
+ `Delete${typeName}Payload`,
56
+ ];
57
+ for (const payloadType of crudPayloadTypes) {
58
+ if (typeRegistry.has(payloadType)) {
59
+ usedPayloadTypes.add(payloadType);
60
+ }
61
+ }
62
+ }
63
+ }
47
64
  const inputTypesFile = (0, input_types_generator_1.generateInputTypesFile)(typeRegistry ?? new Map(), usedInputTypes, tables, usedPayloadTypes);
48
65
  files.push({ path: inputTypesFile.fileName, content: inputTypesFile.content });
49
66
  }
@@ -122,6 +122,47 @@ function addScalarFilterTypes(sourceFile) {
122
122
  }
123
123
  }
124
124
  // ============================================================================
125
+ // Enum Types Collector
126
+ // ============================================================================
127
+ /**
128
+ * Check if a type is likely an enum (not a scalar and not ending with Input/Filter/etc)
129
+ */
130
+ function isLikelyEnumType(typeName, typeRegistry) {
131
+ const typeInfo = typeRegistry.get(typeName);
132
+ return typeInfo?.kind === 'ENUM';
133
+ }
134
+ /**
135
+ * Collect enum types used by table fields
136
+ */
137
+ function collectEnumTypesFromTables(tables, typeRegistry) {
138
+ const enumTypes = new Set();
139
+ for (const table of tables) {
140
+ for (const field of table.fields) {
141
+ const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
142
+ // Check if this type is an enum in the registry
143
+ if (isLikelyEnumType(fieldType, typeRegistry)) {
144
+ enumTypes.add(fieldType);
145
+ }
146
+ }
147
+ }
148
+ return enumTypes;
149
+ }
150
+ /**
151
+ * Add enum types to source file
152
+ */
153
+ function addEnumTypes(sourceFile, typeRegistry, enumTypeNames) {
154
+ if (enumTypeNames.size === 0)
155
+ return;
156
+ (0, ts_ast_1.addSectionComment)(sourceFile, 'Enum Types');
157
+ for (const typeName of Array.from(enumTypeNames).sort()) {
158
+ const typeInfo = typeRegistry.get(typeName);
159
+ if (!typeInfo || typeInfo.kind !== 'ENUM' || !typeInfo.enumValues)
160
+ continue;
161
+ const values = typeInfo.enumValues.map((v) => `'${v}'`).join(' | ');
162
+ sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(typeName, values));
163
+ }
164
+ }
165
+ // ============================================================================
125
166
  // Entity Types Generator (AST-based)
126
167
  // ============================================================================
127
168
  /**
@@ -387,10 +428,12 @@ function buildOrderByUnion(table) {
387
428
  }
388
429
  /**
389
430
  * Add OrderBy types
431
+ * Uses inflection from table metadata for correct pluralization
390
432
  */
391
433
  function addOrderByTypes(sourceFile, tables) {
392
434
  (0, ts_ast_1.addSectionComment)(sourceFile, 'OrderBy Types');
393
435
  for (const table of tables) {
436
+ // Use getOrderByTypeName which respects table.inflection.orderByType
394
437
  const enumName = (0, utils_1.getOrderByTypeName)(table);
395
438
  sourceFile.addTypeAlias((0, ts_ast_1.createTypeAlias)(enumName, buildOrderByUnion(table)));
396
439
  }
@@ -509,32 +552,39 @@ function collectInputTypeNames(operations) {
509
552
  }
510
553
  return inputTypes;
511
554
  }
555
+ /**
556
+ * Build a set of exact table CRUD input type names to skip
557
+ * These are generated by addAllCrudInputTypes, so we don't need to regenerate them
558
+ */
559
+ function buildTableCrudTypeNames(tables) {
560
+ const crudTypes = new Set();
561
+ for (const table of tables) {
562
+ const { typeName } = (0, utils_1.getTableNames)(table);
563
+ crudTypes.add(`Create${typeName}Input`);
564
+ crudTypes.add(`Update${typeName}Input`);
565
+ crudTypes.add(`Delete${typeName}Input`);
566
+ crudTypes.add(`${typeName}Filter`);
567
+ crudTypes.add(`${typeName}Patch`);
568
+ }
569
+ return crudTypes;
570
+ }
512
571
  /**
513
572
  * Add custom input types from TypeRegistry
514
573
  */
515
- function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes) {
574
+ function addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrudTypes) {
516
575
  (0, ts_ast_1.addSectionComment)(sourceFile, 'Custom Input Types (from schema)');
517
576
  const generatedTypes = new Set();
518
577
  const typesToGenerate = new Set(Array.from(usedInputTypes));
519
- // Filter out types we've already generated
520
- const typesToRemove = [];
521
- typesToGenerate.forEach((typeName) => {
522
- if (typeName.endsWith('Filter') ||
523
- typeName.startsWith('Create') ||
524
- typeName.startsWith('Update') ||
525
- typeName.startsWith('Delete')) {
526
- const isTableCrud = /^(Create|Update|Delete)[A-Z][a-zA-Z]+Input$/.test(typeName) ||
527
- /^[A-Z][a-zA-Z]+Filter$/.test(typeName);
528
- if (isTableCrud) {
529
- typesToRemove.push(typeName);
578
+ // Filter out types we've already generated (exact matches for table CRUD types only)
579
+ if (tableCrudTypes) {
580
+ for (const typeName of Array.from(typesToGenerate)) {
581
+ if (tableCrudTypes.has(typeName)) {
582
+ typesToGenerate.delete(typeName);
530
583
  }
531
584
  }
532
- });
533
- typesToRemove.forEach((t) => typesToGenerate.delete(t));
534
- let iterations = 0;
535
- const maxIterations = 200;
536
- while (typesToGenerate.size > 0 && iterations < maxIterations) {
537
- iterations++;
585
+ }
586
+ // Process all types - no artificial limit
587
+ while (typesToGenerate.size > 0) {
538
588
  const typeNameResult = typesToGenerate.values().next();
539
589
  if (typeNameResult.done)
540
590
  break;
@@ -600,10 +650,8 @@ function addPayloadTypes(sourceFile, typeRegistry, usedPayloadTypes, alreadyGene
600
650
  'String', 'Int', 'Float', 'Boolean', 'ID', 'UUID', 'Datetime', 'Date',
601
651
  'Time', 'JSON', 'BigInt', 'BigFloat', 'Cursor', 'Query', 'Mutation',
602
652
  ]);
603
- let iterations = 0;
604
- const maxIterations = 200;
605
- while (typesToGenerate.size > 0 && iterations < maxIterations) {
606
- iterations++;
653
+ // Process all types - no artificial limit
654
+ while (typesToGenerate.size > 0) {
607
655
  const typeNameResult = typesToGenerate.values().next();
608
656
  if (typeNameResult.done)
609
657
  break;
@@ -670,7 +718,12 @@ function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloa
670
718
  sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)('GraphQL types for ORM client') + '\n');
671
719
  // 1. Scalar filter types
672
720
  addScalarFilterTypes(sourceFile);
673
- // 2. Entity and relation types (if tables provided)
721
+ // 2. Enum types used by table fields
722
+ if (tables && tables.length > 0) {
723
+ const enumTypes = collectEnumTypesFromTables(tables, typeRegistry);
724
+ addEnumTypes(sourceFile, typeRegistry, enumTypes);
725
+ }
726
+ // 3. Entity and relation types (if tables provided)
674
727
  if (tables && tables.length > 0) {
675
728
  const tableByName = new Map(tables.map((table) => [table.name, table]));
676
729
  addEntityTypes(sourceFile, tables);
@@ -678,16 +731,17 @@ function generateInputTypesFile(typeRegistry, usedInputTypes, tables, usedPayloa
678
731
  addEntityRelationTypes(sourceFile, tables, tableByName);
679
732
  addEntityWithRelations(sourceFile, tables);
680
733
  addEntitySelectTypes(sourceFile, tables, tableByName);
681
- // 3. Table filter types
734
+ // 4. Table filter types
682
735
  addTableFilterTypes(sourceFile, tables);
683
- // 4. OrderBy types
736
+ // 5. OrderBy types
684
737
  addOrderByTypes(sourceFile, tables);
685
- // 5. CRUD input types
738
+ // 6. CRUD input types
686
739
  addAllCrudInputTypes(sourceFile, tables);
687
740
  }
688
- // 6. Custom input types from TypeRegistry
689
- addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes);
690
- // 7. Payload/return types for custom operations
741
+ // 7. Custom input types from TypeRegistry
742
+ const tableCrudTypes = tables ? buildTableCrudTypeNames(tables) : undefined;
743
+ addCustomInputTypes(sourceFile, typeRegistry, usedInputTypes, tableCrudTypes);
744
+ // 8. Payload/return types for custom operations
691
745
  if (usedPayloadTypes && usedPayloadTypes.size > 0) {
692
746
  const alreadyGeneratedTypes = new Set();
693
747
  if (tables) {
@@ -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`;