@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
@@ -5,11 +5,13 @@ exports.generateAllFiles = generateAllFiles;
5
5
  exports.generate = generate;
6
6
  const client_1 = require("./client");
7
7
  const types_1 = require("./types");
8
+ const schema_types_generator_1 = require("./schema-types-generator");
8
9
  const queries_1 = require("./queries");
9
10
  const mutations_1 = require("./mutations");
10
11
  const custom_queries_1 = require("./custom-queries");
11
12
  const custom_mutations_1 = require("./custom-mutations");
12
13
  const barrel_1 = require("./barrel");
14
+ const utils_1 = require("./utils");
13
15
  // ============================================================================
14
16
  // Main orchestrator
15
17
  // ============================================================================
@@ -28,25 +30,47 @@ function generate(options) {
28
30
  // Extract codegen options
29
31
  const maxDepth = config.codegen.maxFieldDepth;
30
32
  const skipQueryField = config.codegen.skipQueryField;
33
+ const reactQueryEnabled = config.reactQuery.enabled;
31
34
  // 1. Generate client.ts
32
35
  files.push({
33
36
  path: 'client.ts',
34
37
  content: (0, client_1.generateClientFile)(),
35
38
  });
36
- // 2. Generate types.ts
39
+ // Collect table type names for import path resolution
40
+ const tableTypeNames = new Set(tables.map((t) => (0, utils_1.getTableNames)(t).typeName));
41
+ // 2. Generate schema-types.ts for custom operations (if any)
42
+ // NOTE: This must come BEFORE types.ts so that types.ts can import enum types
43
+ let hasSchemaTypes = false;
44
+ let generatedEnumNames = [];
45
+ if (customOperations && customOperations.typeRegistry) {
46
+ const schemaTypesResult = (0, schema_types_generator_1.generateSchemaTypesFile)({
47
+ typeRegistry: customOperations.typeRegistry,
48
+ tableTypeNames,
49
+ });
50
+ // Only include if there's meaningful content
51
+ if (schemaTypesResult.content.split('\n').length > 10) {
52
+ files.push({
53
+ path: 'schema-types.ts',
54
+ content: schemaTypesResult.content,
55
+ });
56
+ hasSchemaTypes = true;
57
+ generatedEnumNames = schemaTypesResult.generatedEnums || [];
58
+ }
59
+ }
60
+ // 3. Generate types.ts (can now import enums from schema-types)
37
61
  files.push({
38
62
  path: 'types.ts',
39
- content: (0, types_1.generateTypesFile)(tables),
63
+ content: (0, types_1.generateTypesFile)(tables, { enumsFromSchemaTypes: generatedEnumNames }),
40
64
  });
41
- // 3. Generate table-based query hooks (queries/*.ts)
42
- const queryHooks = (0, queries_1.generateAllQueryHooks)(tables);
65
+ // 4. Generate table-based query hooks (queries/*.ts)
66
+ const queryHooks = (0, queries_1.generateAllQueryHooks)(tables, { reactQueryEnabled });
43
67
  for (const hook of queryHooks) {
44
68
  files.push({
45
69
  path: `queries/${hook.fileName}`,
46
70
  content: hook.content,
47
71
  });
48
72
  }
49
- // 4. Generate custom query hooks if available
73
+ // 5. Generate custom query hooks if available
50
74
  let customQueryHooks = [];
51
75
  if (customOperations && customOperations.queries.length > 0) {
52
76
  customQueryHooks = (0, custom_queries_1.generateAllCustomQueryHooks)({
@@ -54,6 +78,8 @@ function generate(options) {
54
78
  typeRegistry: customOperations.typeRegistry,
55
79
  maxDepth,
56
80
  skipQueryField,
81
+ reactQueryEnabled,
82
+ tableTypeNames,
57
83
  });
58
84
  for (const hook of customQueryHooks) {
59
85
  files.push({
@@ -70,7 +96,10 @@ function generate(options) {
70
96
  : (0, barrel_1.generateQueriesBarrel)(tables),
71
97
  });
72
98
  // 6. Generate table-based mutation hooks (mutations/*.ts)
73
- const mutationHooks = (0, mutations_1.generateAllMutationHooks)(tables);
99
+ const mutationHooks = (0, mutations_1.generateAllMutationHooks)(tables, {
100
+ reactQueryEnabled,
101
+ enumsFromSchemaTypes: generatedEnumNames,
102
+ });
74
103
  for (const hook of mutationHooks) {
75
104
  files.push({
76
105
  path: `mutations/${hook.fileName}`,
@@ -85,6 +114,8 @@ function generate(options) {
85
114
  typeRegistry: customOperations.typeRegistry,
86
115
  maxDepth,
87
116
  skipQueryField,
117
+ reactQueryEnabled,
118
+ tableTypeNames,
88
119
  });
89
120
  for (const hook of customMutationHooks) {
90
121
  files.push({
@@ -100,10 +131,10 @@ function generate(options) {
100
131
  ? (0, barrel_1.generateCustomMutationsBarrel)(tables, customMutationHooks.map((h) => h.operationName))
101
132
  : (0, barrel_1.generateMutationsBarrel)(tables),
102
133
  });
103
- // 9. Generate main index.ts barrel
134
+ // 9. Generate main index.ts barrel (with schema-types if present)
104
135
  files.push({
105
136
  path: 'index.ts',
106
- content: (0, barrel_1.generateMainBarrel)(tables),
137
+ content: (0, barrel_1.generateMainBarrel)(tables, hasSchemaTypes),
107
138
  });
108
139
  return {
109
140
  files,
@@ -12,19 +12,29 @@ export interface GeneratedMutationFile {
12
12
  fileName: string;
13
13
  content: string;
14
14
  }
15
+ export interface MutationGeneratorOptions {
16
+ /** Whether to generate React Query hooks (default: true for backwards compatibility) */
17
+ reactQueryEnabled?: boolean;
18
+ /** Enum type names that are available from schema-types.ts */
19
+ enumsFromSchemaTypes?: string[];
20
+ }
15
21
  /**
16
22
  * Generate create mutation hook file content using AST
23
+ * When reactQueryEnabled is false, returns null since mutations require React Query
17
24
  */
18
- export declare function generateCreateMutationHook(table: CleanTable): GeneratedMutationFile;
25
+ export declare function generateCreateMutationHook(table: CleanTable, options?: MutationGeneratorOptions): GeneratedMutationFile | null;
19
26
  /**
20
27
  * Generate update mutation hook file content using AST
28
+ * When reactQueryEnabled is false, returns null since mutations require React Query
21
29
  */
22
- export declare function generateUpdateMutationHook(table: CleanTable): GeneratedMutationFile | null;
30
+ export declare function generateUpdateMutationHook(table: CleanTable, options?: MutationGeneratorOptions): GeneratedMutationFile | null;
23
31
  /**
24
32
  * Generate delete mutation hook file content using AST
33
+ * When reactQueryEnabled is false, returns null since mutations require React Query
25
34
  */
26
- export declare function generateDeleteMutationHook(table: CleanTable): GeneratedMutationFile | null;
35
+ export declare function generateDeleteMutationHook(table: CleanTable, options?: MutationGeneratorOptions): GeneratedMutationFile | null;
27
36
  /**
28
37
  * Generate all mutation hook files for all tables
38
+ * When reactQueryEnabled is false, returns empty array since mutations require React Query
29
39
  */
30
- export declare function generateAllMutationHooks(tables: CleanTable[]): GeneratedMutationFile[];
40
+ export declare function generateAllMutationHooks(tables: CleanTable[], options?: MutationGeneratorOptions): GeneratedMutationFile[];
@@ -7,26 +7,61 @@ 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
  // ============================================================================
13
31
  /**
14
32
  * Generate create mutation hook file content using AST
33
+ * When reactQueryEnabled is false, returns null since mutations require React Query
15
34
  */
16
- function generateCreateMutationHook(table) {
35
+ function generateCreateMutationHook(table, options = {}) {
36
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
37
+ // Mutations require React Query - skip generation when disabled
38
+ if (!reactQueryEnabled) {
39
+ return null;
40
+ }
41
+ const enumSet = new Set(enumsFromSchemaTypes);
17
42
  const project = (0, ts_ast_1.createProject)();
18
43
  const { typeName, singularName } = (0, utils_1.getTableNames)(table);
19
44
  const hookName = (0, utils_1.getCreateMutationHookName)(table);
20
45
  const mutationName = (0, utils_1.getCreateMutationName)(table);
21
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
+ }
22
57
  // Generate GraphQL document via AST
23
58
  const mutationAST = (0, gql_ast_1.buildCreateMutationAST)({ table });
24
59
  const mutationDocument = (0, gql_ast_1.printGraphQL)(mutationAST);
25
60
  const sourceFile = (0, ts_ast_1.createSourceFile)(project, (0, utils_1.getCreateMutationFileName)(table));
26
61
  // Add file header
27
62
  sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)(`Create mutation hook for ${typeName}`) + '\n\n');
28
- // Add imports
29
- sourceFile.addImportDeclarations([
63
+ // Build import declarations
64
+ const imports = [
30
65
  (0, ts_ast_1.createImport)({
31
66
  moduleSpecifier: '@tanstack/react-query',
32
67
  namedImports: ['useMutation', 'useQueryClient'],
@@ -40,7 +75,16 @@ function generateCreateMutationHook(table) {
40
75
  moduleSpecifier: '../types',
41
76
  typeOnlyNamedImports: [typeName],
42
77
  }),
43
- ]);
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);
44
88
  // Re-export entity type
45
89
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
46
90
  // Add section comment
@@ -54,11 +98,9 @@ function generateCreateMutationHook(table) {
54
98
  sourceFile.addStatements('// Types');
55
99
  sourceFile.addStatements('// ============================================================================\n');
56
100
  // Generate CreateInput type - exclude auto-generated fields
101
+ // Note: Not exported to avoid conflicts with schema-types
57
102
  const inputFields = scalarFields
58
- .filter((f) => {
59
- const name = f.name.toLowerCase();
60
- return !['id', 'createdat', 'updatedat', 'created_at', 'updated_at'].includes(name);
61
- })
103
+ .filter((f) => !isAutoGeneratedField(f.name, pkFieldNames))
62
104
  .map((f) => ({
63
105
  name: f.name,
64
106
  type: `${(0, utils_1.fieldTypeToTs)(f.type)} | null`,
@@ -66,6 +108,7 @@ function generateCreateMutationHook(table) {
66
108
  }));
67
109
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${typeName}CreateInput`, inputFields, {
68
110
  docs: [`Input type for creating a ${typeName}`],
111
+ isExported: false,
69
112
  }));
70
113
  // Variables interface
71
114
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`, [
@@ -143,25 +186,44 @@ mutate({
143
186
  // ============================================================================
144
187
  /**
145
188
  * Generate update mutation hook file content using AST
189
+ * When reactQueryEnabled is false, returns null since mutations require React Query
146
190
  */
147
- function generateUpdateMutationHook(table) {
191
+ function generateUpdateMutationHook(table, options = {}) {
192
+ const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
193
+ // Mutations require React Query - skip generation when disabled
194
+ if (!reactQueryEnabled) {
195
+ return null;
196
+ }
148
197
  // Check if update mutation exists
149
198
  if (table.query?.update === null) {
150
199
  return null;
151
200
  }
201
+ const enumSet = new Set(enumsFromSchemaTypes);
152
202
  const project = (0, ts_ast_1.createProject)();
153
203
  const { typeName, singularName } = (0, utils_1.getTableNames)(table);
154
204
  const hookName = (0, utils_1.getUpdateMutationHookName)(table);
155
205
  const mutationName = (0, utils_1.getUpdateMutationName)(table);
156
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
+ }
157
219
  // Generate GraphQL document via AST
158
220
  const mutationAST = (0, gql_ast_1.buildUpdateMutationAST)({ table });
159
221
  const mutationDocument = (0, gql_ast_1.printGraphQL)(mutationAST);
160
222
  const sourceFile = (0, ts_ast_1.createSourceFile)(project, (0, utils_1.getUpdateMutationFileName)(table));
161
223
  // Add file header
162
224
  sourceFile.insertText(0, (0, ts_ast_1.createFileHeader)(`Update mutation hook for ${typeName}`) + '\n\n');
163
- // Add imports
164
- sourceFile.addImportDeclarations([
225
+ // Build import declarations
226
+ const imports = [
165
227
  (0, ts_ast_1.createImport)({
166
228
  moduleSpecifier: '@tanstack/react-query',
167
229
  namedImports: ['useMutation', 'useQueryClient'],
@@ -175,7 +237,16 @@ function generateUpdateMutationHook(table) {
175
237
  moduleSpecifier: '../types',
176
238
  typeOnlyNamedImports: [typeName],
177
239
  }),
178
- ]);
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);
179
250
  // Re-export entity type
180
251
  sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
181
252
  // Add section comment
@@ -188,9 +259,10 @@ function generateUpdateMutationHook(table) {
188
259
  sourceFile.addStatements('\n// ============================================================================');
189
260
  sourceFile.addStatements('// Types');
190
261
  sourceFile.addStatements('// ============================================================================\n');
191
- // 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
192
264
  const patchFields = scalarFields
193
- .filter((f) => f.name.toLowerCase() !== 'id')
265
+ .filter((f) => !pkFieldNames.has(f.name))
194
266
  .map((f) => ({
195
267
  name: f.name,
196
268
  type: `${(0, utils_1.fieldTypeToTs)(f.type)} | null`,
@@ -198,13 +270,14 @@ function generateUpdateMutationHook(table) {
198
270
  }));
199
271
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${typeName}Patch`, patchFields, {
200
272
  docs: [`Patch type for updating a ${typeName} - all fields optional`],
273
+ isExported: false,
201
274
  }));
202
- // Variables interface
275
+ // Variables interface - use dynamic PK field name and type
203
276
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`, [
204
277
  {
205
278
  name: 'input',
206
279
  type: `{
207
- id: string;
280
+ ${pkField.name}: ${pkField.tsType};
208
281
  patch: ${typeName}Patch;
209
282
  }`,
210
283
  },
@@ -243,7 +316,7 @@ function generateUpdateMutationHook(table) {
243
316
  ),
244
317
  onSuccess: (_, variables) => {
245
318
  // Invalidate specific item and list queries
246
- queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.id] });
319
+ queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
247
320
  queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
248
321
  },
249
322
  ...options,
@@ -258,7 +331,7 @@ const { mutate, isPending } = ${hookName}();
258
331
 
259
332
  mutate({
260
333
  input: {
261
- id: 'uuid-here',
334
+ ${pkField.name}: ${pkField.tsType === 'string' ? "'value-here'" : '123'},
262
335
  patch: {
263
336
  // ... fields to update
264
337
  },
@@ -278,8 +351,14 @@ mutate({
278
351
  // ============================================================================
279
352
  /**
280
353
  * Generate delete mutation hook file content using AST
354
+ * When reactQueryEnabled is false, returns null since mutations require React Query
281
355
  */
282
- function generateDeleteMutationHook(table) {
356
+ function generateDeleteMutationHook(table, options = {}) {
357
+ const { reactQueryEnabled = true } = options;
358
+ // Mutations require React Query - skip generation when disabled
359
+ if (!reactQueryEnabled) {
360
+ return null;
361
+ }
283
362
  // Check if delete mutation exists
284
363
  if (table.query?.delete === null) {
285
364
  return null;
@@ -288,6 +367,9 @@ function generateDeleteMutationHook(table) {
288
367
  const { typeName } = (0, utils_1.getTableNames)(table);
289
368
  const hookName = (0, utils_1.getDeleteMutationHookName)(table);
290
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
291
373
  // Generate GraphQL document via AST
292
374
  const mutationAST = (0, gql_ast_1.buildDeleteMutationAST)({ table });
293
375
  const mutationDocument = (0, gql_ast_1.printGraphQL)(mutationAST);
@@ -316,12 +398,12 @@ function generateDeleteMutationHook(table) {
316
398
  sourceFile.addStatements('\n// ============================================================================');
317
399
  sourceFile.addStatements('// Types');
318
400
  sourceFile.addStatements('// ============================================================================\n');
319
- // Variables interface
401
+ // Variables interface - use dynamic PK field name and type
320
402
  sourceFile.addInterface((0, ts_ast_1.createInterface)(`${(0, utils_1.ucFirst)(mutationName)}MutationVariables`, [
321
403
  {
322
404
  name: 'input',
323
405
  type: `{
324
- id: string;
406
+ ${pkField.name}: ${pkField.tsType};
325
407
  }`,
326
408
  },
327
409
  ]));
@@ -331,7 +413,7 @@ function generateDeleteMutationHook(table) {
331
413
  name: mutationName,
332
414
  type: `{
333
415
  clientMutationId: string | null;
334
- deletedId: string | null;
416
+ deleted${(0, utils_1.ucFirst)(pkField.name)}: ${pkField.tsType} | null;
335
417
  }`,
336
418
  },
337
419
  ]));
@@ -360,7 +442,7 @@ function generateDeleteMutationHook(table) {
360
442
  ),
361
443
  onSuccess: (_, variables) => {
362
444
  // Remove from cache and invalidate list
363
- queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.id] });
445
+ queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
364
446
  queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
365
447
  },
366
448
  ...options,
@@ -375,7 +457,7 @@ const { mutate, isPending } = ${hookName}();
375
457
 
376
458
  mutate({
377
459
  input: {
378
- id: 'uuid-to-delete',
460
+ ${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'},
379
461
  },
380
462
  });
381
463
  \`\`\``,
@@ -392,16 +474,20 @@ mutate({
392
474
  // ============================================================================
393
475
  /**
394
476
  * Generate all mutation hook files for all tables
477
+ * When reactQueryEnabled is false, returns empty array since mutations require React Query
395
478
  */
396
- function generateAllMutationHooks(tables) {
479
+ function generateAllMutationHooks(tables, options = {}) {
397
480
  const files = [];
398
481
  for (const table of tables) {
399
- files.push(generateCreateMutationHook(table));
400
- const updateHook = generateUpdateMutationHook(table);
482
+ const createHook = generateCreateMutationHook(table, options);
483
+ if (createHook) {
484
+ files.push(createHook);
485
+ }
486
+ const updateHook = generateUpdateMutationHook(table, options);
401
487
  if (updateHook) {
402
488
  files.push(updateHook);
403
489
  }
404
- const deleteHook = generateDeleteMutationHook(table);
490
+ const deleteHook = generateDeleteMutationHook(table, options);
405
491
  if (deleteHook) {
406
492
  files.push(deleteHook);
407
493
  }
@@ -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) {