@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
@@ -1,10 +1,12 @@
1
1
  import { generateClientFile } from './client';
2
2
  import { generateTypesFile } from './types';
3
+ import { generateSchemaTypesFile } from './schema-types-generator';
3
4
  import { generateAllQueryHooks } from './queries';
4
5
  import { generateAllMutationHooks } from './mutations';
5
6
  import { generateAllCustomQueryHooks } from './custom-queries';
6
7
  import { generateAllCustomMutationHooks } from './custom-mutations';
7
8
  import { generateQueriesBarrel, generateMutationsBarrel, generateMainBarrel, generateCustomQueriesBarrel, generateCustomMutationsBarrel, } from './barrel';
9
+ import { getTableNames } from './utils';
8
10
  // ============================================================================
9
11
  // Main orchestrator
10
12
  // ============================================================================
@@ -29,12 +31,33 @@ export function generate(options) {
29
31
  path: 'client.ts',
30
32
  content: generateClientFile(),
31
33
  });
32
- // 2. Generate types.ts
34
+ // Collect table type names for import path resolution
35
+ const tableTypeNames = new Set(tables.map((t) => getTableNames(t).typeName));
36
+ // 2. Generate schema-types.ts for custom operations (if any)
37
+ // NOTE: This must come BEFORE types.ts so that types.ts can import enum types
38
+ let hasSchemaTypes = false;
39
+ let generatedEnumNames = [];
40
+ if (customOperations && customOperations.typeRegistry) {
41
+ const schemaTypesResult = generateSchemaTypesFile({
42
+ typeRegistry: customOperations.typeRegistry,
43
+ tableTypeNames,
44
+ });
45
+ // Only include if there's meaningful content
46
+ if (schemaTypesResult.content.split('\n').length > 10) {
47
+ files.push({
48
+ path: 'schema-types.ts',
49
+ content: schemaTypesResult.content,
50
+ });
51
+ hasSchemaTypes = true;
52
+ generatedEnumNames = schemaTypesResult.generatedEnums || [];
53
+ }
54
+ }
55
+ // 3. Generate types.ts (can now import enums from schema-types)
33
56
  files.push({
34
57
  path: 'types.ts',
35
- content: generateTypesFile(tables),
58
+ content: generateTypesFile(tables, { enumsFromSchemaTypes: generatedEnumNames }),
36
59
  });
37
- // 3. Generate table-based query hooks (queries/*.ts)
60
+ // 4. Generate table-based query hooks (queries/*.ts)
38
61
  const queryHooks = generateAllQueryHooks(tables, { reactQueryEnabled });
39
62
  for (const hook of queryHooks) {
40
63
  files.push({
@@ -42,7 +65,7 @@ export function generate(options) {
42
65
  content: hook.content,
43
66
  });
44
67
  }
45
- // 4. Generate custom query hooks if available
68
+ // 5. Generate custom query hooks if available
46
69
  let customQueryHooks = [];
47
70
  if (customOperations && customOperations.queries.length > 0) {
48
71
  customQueryHooks = generateAllCustomQueryHooks({
@@ -51,6 +74,7 @@ export function generate(options) {
51
74
  maxDepth,
52
75
  skipQueryField,
53
76
  reactQueryEnabled,
77
+ tableTypeNames,
54
78
  });
55
79
  for (const hook of customQueryHooks) {
56
80
  files.push({
@@ -67,7 +91,10 @@ export function generate(options) {
67
91
  : generateQueriesBarrel(tables),
68
92
  });
69
93
  // 6. Generate table-based mutation hooks (mutations/*.ts)
70
- const mutationHooks = generateAllMutationHooks(tables, { reactQueryEnabled });
94
+ const mutationHooks = generateAllMutationHooks(tables, {
95
+ reactQueryEnabled,
96
+ enumsFromSchemaTypes: generatedEnumNames,
97
+ });
71
98
  for (const hook of mutationHooks) {
72
99
  files.push({
73
100
  path: `mutations/${hook.fileName}`,
@@ -83,6 +110,7 @@ export function generate(options) {
83
110
  maxDepth,
84
111
  skipQueryField,
85
112
  reactQueryEnabled,
113
+ tableTypeNames,
86
114
  });
87
115
  for (const hook of customMutationHooks) {
88
116
  files.push({
@@ -98,10 +126,10 @@ export function generate(options) {
98
126
  ? generateCustomMutationsBarrel(tables, customMutationHooks.map((h) => h.operationName))
99
127
  : generateMutationsBarrel(tables),
100
128
  });
101
- // 9. Generate main index.ts barrel
129
+ // 9. Generate main index.ts barrel (with schema-types if present)
102
130
  files.push({
103
131
  path: 'index.ts',
104
- content: generateMainBarrel(tables),
132
+ content: generateMainBarrel(tables, hasSchemaTypes),
105
133
  });
106
134
  return {
107
135
  files,
@@ -15,6 +15,8 @@ export interface GeneratedMutationFile {
15
15
  export interface MutationGeneratorOptions {
16
16
  /** Whether to generate React Query hooks (default: true for backwards compatibility) */
17
17
  reactQueryEnabled?: boolean;
18
+ /** Enum type names that are available from schema-types.ts */
19
+ enumsFromSchemaTypes?: string[];
18
20
  }
19
21
  /**
20
22
  * Generate create mutation hook file content using AST
@@ -1,6 +1,24 @@
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
  // ============================================================================
@@ -9,24 +27,35 @@ import { getTableNames, getCreateMutationHookName, getUpdateMutationHookName, ge
9
27
  * When reactQueryEnabled is false, returns null since mutations require React Query
10
28
  */
11
29
  export function generateCreateMutationHook(table, options = {}) {
12
- const { reactQueryEnabled = true } = options;
30
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
13
31
  // Mutations require React Query - skip generation when disabled
14
32
  if (!reactQueryEnabled) {
15
33
  return null;
16
34
  }
35
+ const enumSet = new Set(enumsFromSchemaTypes);
17
36
  const project = createProject();
18
37
  const { typeName, singularName } = getTableNames(table);
19
38
  const hookName = getCreateMutationHookName(table);
20
39
  const mutationName = getCreateMutationName(table);
21
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
+ }
22
51
  // Generate GraphQL document via AST
23
52
  const mutationAST = buildCreateMutationAST({ table });
24
53
  const mutationDocument = printGraphQL(mutationAST);
25
54
  const sourceFile = createSourceFile(project, getCreateMutationFileName(table));
26
55
  // Add file header
27
56
  sourceFile.insertText(0, createFileHeader(`Create mutation hook for ${typeName}`) + '\n\n');
28
- // Add imports
29
- sourceFile.addImportDeclarations([
57
+ // Build import declarations
58
+ const imports = [
30
59
  createImport({
31
60
  moduleSpecifier: '@tanstack/react-query',
32
61
  namedImports: ['useMutation', 'useQueryClient'],
@@ -40,7 +69,16 @@ export function generateCreateMutationHook(table, options = {}) {
40
69
  moduleSpecifier: '../types',
41
70
  typeOnlyNamedImports: [typeName],
42
71
  }),
43
- ]);
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);
44
82
  // Re-export entity type
45
83
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
46
84
  // Add section comment
@@ -54,11 +92,9 @@ export function generateCreateMutationHook(table, options = {}) {
54
92
  sourceFile.addStatements('// Types');
55
93
  sourceFile.addStatements('// ============================================================================\n');
56
94
  // Generate CreateInput type - exclude auto-generated fields
95
+ // Note: Not exported to avoid conflicts with schema-types
57
96
  const inputFields = scalarFields
58
- .filter((f) => {
59
- const name = f.name.toLowerCase();
60
- return !['id', 'createdat', 'updatedat', 'created_at', 'updated_at'].includes(name);
61
- })
97
+ .filter((f) => !isAutoGeneratedField(f.name, pkFieldNames))
62
98
  .map((f) => ({
63
99
  name: f.name,
64
100
  type: `${fieldTypeToTs(f.type)} | null`,
@@ -66,6 +102,7 @@ export function generateCreateMutationHook(table, options = {}) {
66
102
  }));
67
103
  sourceFile.addInterface(createInterface(`${typeName}CreateInput`, inputFields, {
68
104
  docs: [`Input type for creating a ${typeName}`],
105
+ isExported: false,
69
106
  }));
70
107
  // Variables interface
71
108
  sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
@@ -146,7 +183,7 @@ mutate({
146
183
  * When reactQueryEnabled is false, returns null since mutations require React Query
147
184
  */
148
185
  export function generateUpdateMutationHook(table, options = {}) {
149
- const { reactQueryEnabled = true } = options;
186
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
150
187
  // Mutations require React Query - skip generation when disabled
151
188
  if (!reactQueryEnabled) {
152
189
  return null;
@@ -155,19 +192,32 @@ export function generateUpdateMutationHook(table, options = {}) {
155
192
  if (table.query?.update === null) {
156
193
  return null;
157
194
  }
195
+ const enumSet = new Set(enumsFromSchemaTypes);
158
196
  const project = createProject();
159
197
  const { typeName, singularName } = getTableNames(table);
160
198
  const hookName = getUpdateMutationHookName(table);
161
199
  const mutationName = getUpdateMutationName(table);
162
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
+ }
163
213
  // Generate GraphQL document via AST
164
214
  const mutationAST = buildUpdateMutationAST({ table });
165
215
  const mutationDocument = printGraphQL(mutationAST);
166
216
  const sourceFile = createSourceFile(project, getUpdateMutationFileName(table));
167
217
  // Add file header
168
218
  sourceFile.insertText(0, createFileHeader(`Update mutation hook for ${typeName}`) + '\n\n');
169
- // Add imports
170
- sourceFile.addImportDeclarations([
219
+ // Build import declarations
220
+ const imports = [
171
221
  createImport({
172
222
  moduleSpecifier: '@tanstack/react-query',
173
223
  namedImports: ['useMutation', 'useQueryClient'],
@@ -181,7 +231,16 @@ export function generateUpdateMutationHook(table, options = {}) {
181
231
  moduleSpecifier: '../types',
182
232
  typeOnlyNamedImports: [typeName],
183
233
  }),
184
- ]);
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);
185
244
  // Re-export entity type
186
245
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
187
246
  // Add section comment
@@ -194,9 +253,10 @@ export function generateUpdateMutationHook(table, options = {}) {
194
253
  sourceFile.addStatements('\n// ============================================================================');
195
254
  sourceFile.addStatements('// Types');
196
255
  sourceFile.addStatements('// ============================================================================\n');
197
- // 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
198
258
  const patchFields = scalarFields
199
- .filter((f) => f.name.toLowerCase() !== 'id')
259
+ .filter((f) => !pkFieldNames.has(f.name))
200
260
  .map((f) => ({
201
261
  name: f.name,
202
262
  type: `${fieldTypeToTs(f.type)} | null`,
@@ -204,13 +264,14 @@ export function generateUpdateMutationHook(table, options = {}) {
204
264
  }));
205
265
  sourceFile.addInterface(createInterface(`${typeName}Patch`, patchFields, {
206
266
  docs: [`Patch type for updating a ${typeName} - all fields optional`],
267
+ isExported: false,
207
268
  }));
208
- // Variables interface
269
+ // Variables interface - use dynamic PK field name and type
209
270
  sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
210
271
  {
211
272
  name: 'input',
212
273
  type: `{
213
- id: string;
274
+ ${pkField.name}: ${pkField.tsType};
214
275
  patch: ${typeName}Patch;
215
276
  }`,
216
277
  },
@@ -249,7 +310,7 @@ export function generateUpdateMutationHook(table, options = {}) {
249
310
  ),
250
311
  onSuccess: (_, variables) => {
251
312
  // Invalidate specific item and list queries
252
- queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.id] });
313
+ queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
253
314
  queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
254
315
  },
255
316
  ...options,
@@ -264,7 +325,7 @@ const { mutate, isPending } = ${hookName}();
264
325
 
265
326
  mutate({
266
327
  input: {
267
- id: 'uuid-here',
328
+ ${pkField.name}: ${pkField.tsType === 'string' ? "'value-here'" : '123'},
268
329
  patch: {
269
330
  // ... fields to update
270
331
  },
@@ -300,6 +361,9 @@ export function generateDeleteMutationHook(table, options = {}) {
300
361
  const { typeName } = getTableNames(table);
301
362
  const hookName = getDeleteMutationHookName(table);
302
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
303
367
  // Generate GraphQL document via AST
304
368
  const mutationAST = buildDeleteMutationAST({ table });
305
369
  const mutationDocument = printGraphQL(mutationAST);
@@ -328,12 +392,12 @@ export function generateDeleteMutationHook(table, options = {}) {
328
392
  sourceFile.addStatements('\n// ============================================================================');
329
393
  sourceFile.addStatements('// Types');
330
394
  sourceFile.addStatements('// ============================================================================\n');
331
- // Variables interface
395
+ // Variables interface - use dynamic PK field name and type
332
396
  sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
333
397
  {
334
398
  name: 'input',
335
399
  type: `{
336
- id: string;
400
+ ${pkField.name}: ${pkField.tsType};
337
401
  }`,
338
402
  },
339
403
  ]));
@@ -343,7 +407,7 @@ export function generateDeleteMutationHook(table, options = {}) {
343
407
  name: mutationName,
344
408
  type: `{
345
409
  clientMutationId: string | null;
346
- deletedId: string | null;
410
+ deleted${ucFirst(pkField.name)}: ${pkField.tsType} | null;
347
411
  }`,
348
412
  },
349
413
  ]));
@@ -372,7 +436,7 @@ export function generateDeleteMutationHook(table, options = {}) {
372
436
  ),
373
437
  onSuccess: (_, variables) => {
374
438
  // Remove from cache and invalidate list
375
- queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.id] });
439
+ queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
376
440
  queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
377
441
  },
378
442
  ...options,
@@ -387,7 +451,7 @@ const { mutate, isPending } = ${hookName}();
387
451
 
388
452
  mutate({
389
453
  input: {
390
- id: 'uuid-to-delete',
454
+ ${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'},
391
455
  },
392
456
  });
393
457
  \`\`\``,
@@ -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`;