@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.
- package/README.md +15 -3
- package/cli/codegen/barrel.d.ts +4 -1
- package/cli/codegen/barrel.js +18 -12
- package/cli/codegen/client.js +33 -0
- package/cli/codegen/custom-mutations.d.ts +11 -1
- package/cli/codegen/custom-mutations.js +49 -15
- package/cli/codegen/custom-queries.d.ts +8 -0
- package/cli/codegen/custom-queries.js +82 -47
- package/cli/codegen/gql-ast.js +9 -5
- package/cli/codegen/index.js +39 -8
- package/cli/codegen/mutations.d.ts +14 -4
- package/cli/codegen/mutations.js +114 -28
- package/cli/codegen/orm/barrel.js +4 -2
- package/cli/codegen/orm/index.js +17 -0
- package/cli/codegen/orm/input-types-generator.js +83 -29
- package/cli/codegen/orm/model-generator.js +6 -4
- package/cli/codegen/queries.d.ts +7 -3
- package/cli/codegen/queries.js +185 -158
- package/cli/codegen/scalars.d.ts +6 -4
- package/cli/codegen/scalars.js +17 -9
- package/cli/codegen/schema-types-generator.d.ts +26 -0
- package/cli/codegen/schema-types-generator.js +365 -0
- package/cli/codegen/ts-ast.d.ts +3 -1
- package/cli/codegen/ts-ast.js +2 -2
- package/cli/codegen/type-resolver.d.ts +52 -6
- package/cli/codegen/type-resolver.js +97 -19
- package/cli/codegen/types.d.ts +7 -4
- package/cli/codegen/types.js +94 -41
- package/cli/codegen/utils.d.ts +20 -2
- package/cli/codegen/utils.js +32 -7
- package/cli/commands/generate-orm.js +5 -5
- package/cli/commands/generate.d.ts +4 -1
- package/cli/commands/generate.js +27 -8
- package/cli/introspect/transform-schema.d.ts +33 -21
- package/cli/introspect/transform-schema.js +31 -21
- package/esm/cli/codegen/barrel.d.ts +4 -1
- package/esm/cli/codegen/barrel.js +18 -12
- package/esm/cli/codegen/client.js +33 -0
- package/esm/cli/codegen/custom-mutations.d.ts +11 -1
- package/esm/cli/codegen/custom-mutations.js +50 -16
- package/esm/cli/codegen/custom-queries.d.ts +8 -0
- package/esm/cli/codegen/custom-queries.js +83 -48
- package/esm/cli/codegen/gql-ast.js +10 -6
- package/esm/cli/codegen/index.js +39 -8
- package/esm/cli/codegen/mutations.d.ts +14 -4
- package/esm/cli/codegen/mutations.js +115 -29
- package/esm/cli/codegen/orm/barrel.js +4 -2
- package/esm/cli/codegen/orm/index.js +17 -0
- package/esm/cli/codegen/orm/input-types-generator.js +83 -29
- package/esm/cli/codegen/orm/model-generator.js +7 -5
- package/esm/cli/codegen/queries.d.ts +7 -3
- package/esm/cli/codegen/queries.js +186 -159
- package/esm/cli/codegen/scalars.d.ts +6 -4
- package/esm/cli/codegen/scalars.js +16 -8
- package/esm/cli/codegen/schema-types-generator.d.ts +26 -0
- package/esm/cli/codegen/schema-types-generator.js +362 -0
- package/esm/cli/codegen/ts-ast.d.ts +3 -1
- package/esm/cli/codegen/ts-ast.js +2 -2
- package/esm/cli/codegen/type-resolver.d.ts +52 -6
- package/esm/cli/codegen/type-resolver.js +97 -20
- package/esm/cli/codegen/types.d.ts +7 -4
- package/esm/cli/codegen/types.js +95 -41
- package/esm/cli/codegen/utils.d.ts +20 -2
- package/esm/cli/codegen/utils.js +31 -7
- package/esm/cli/commands/generate-orm.js +5 -5
- package/esm/cli/commands/generate.d.ts +4 -1
- package/esm/cli/commands/generate.js +27 -8
- package/esm/cli/introspect/transform-schema.d.ts +33 -21
- package/esm/cli/introspect/transform-schema.js +31 -21
- package/esm/types/config.d.ts +16 -1
- package/esm/types/config.js +6 -0
- package/esm/types/schema.d.ts +2 -0
- package/package.json +8 -6
- package/types/config.d.ts +16 -1
- package/types/config.js +6 -0
- package/types/schema.d.ts +2 -0
- package/__tests__/codegen/input-types-generator.test.d.ts +0 -1
- package/__tests__/codegen/input-types-generator.test.js +0 -635
- package/cli/codegen/filters.d.ts +0 -27
- package/cli/codegen/filters.js +0 -357
- package/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
- package/cli/codegen/orm/input-types-generator.test.js +0 -75
- package/cli/codegen/orm/select-types.test.d.ts +0 -11
- package/cli/codegen/orm/select-types.test.js +0 -22
- package/cli/introspect/transform-schema.test.d.ts +0 -1
- package/cli/introspect/transform-schema.test.js +0 -67
- package/esm/__tests__/codegen/input-types-generator.test.d.ts +0 -1
- package/esm/__tests__/codegen/input-types-generator.test.js +0 -633
- package/esm/cli/codegen/filters.d.ts +0 -27
- package/esm/cli/codegen/filters.js +0 -351
- package/esm/cli/codegen/orm/input-types-generator.test.d.ts +0 -1
- package/esm/cli/codegen/orm/input-types-generator.test.js +0 -73
- package/esm/cli/codegen/orm/select-types.test.d.ts +0 -11
- package/esm/cli/codegen/orm/select-types.test.js +0 -21
- package/esm/cli/introspect/transform-schema.test.d.ts +0 -1
- package/esm/cli/introspect/transform-schema.test.js +0 -65
|
@@ -1,26 +1,61 @@
|
|
|
1
1
|
import { createProject, createSourceFile, getFormattedOutput, createFileHeader, createImport, createInterface, createConst, } from './ts-ast';
|
|
2
2
|
import { buildCreateMutationAST, buildUpdateMutationAST, buildDeleteMutationAST, printGraphQL, } from './gql-ast';
|
|
3
|
-
import { getTableNames, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, getCreateMutationFileName, getUpdateMutationFileName, getDeleteMutationFileName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getScalarFields, fieldTypeToTs, ucFirst, lcFirst, } from './utils';
|
|
3
|
+
import { getTableNames, getCreateMutationHookName, getUpdateMutationHookName, getDeleteMutationHookName, getCreateMutationFileName, getUpdateMutationFileName, getDeleteMutationFileName, getCreateMutationName, getUpdateMutationName, getDeleteMutationName, getScalarFields, getPrimaryKeyInfo, fieldTypeToTs, ucFirst, lcFirst, } from './utils';
|
|
4
|
+
/**
|
|
5
|
+
* Check if a field is auto-generated and should be excluded from create inputs
|
|
6
|
+
* Uses primary key from constraints and common timestamp patterns
|
|
7
|
+
*/
|
|
8
|
+
function isAutoGeneratedField(fieldName, pkFieldNames) {
|
|
9
|
+
const name = fieldName.toLowerCase();
|
|
10
|
+
// Exclude primary key fields (from constraints)
|
|
11
|
+
if (pkFieldNames.has(fieldName))
|
|
12
|
+
return true;
|
|
13
|
+
// Exclude common timestamp patterns (case-insensitive)
|
|
14
|
+
// These are typically auto-set by database triggers or defaults
|
|
15
|
+
const timestampPatterns = [
|
|
16
|
+
'createdat', 'created_at', 'createddate', 'created_date',
|
|
17
|
+
'updatedat', 'updated_at', 'updateddate', 'updated_date',
|
|
18
|
+
'deletedat', 'deleted_at', // soft delete timestamps
|
|
19
|
+
];
|
|
20
|
+
return timestampPatterns.includes(name);
|
|
21
|
+
}
|
|
4
22
|
// ============================================================================
|
|
5
23
|
// Create mutation hook generator
|
|
6
24
|
// ============================================================================
|
|
7
25
|
/**
|
|
8
26
|
* Generate create mutation hook file content using AST
|
|
27
|
+
* When reactQueryEnabled is false, returns null since mutations require React Query
|
|
9
28
|
*/
|
|
10
|
-
export function generateCreateMutationHook(table) {
|
|
29
|
+
export function generateCreateMutationHook(table, options = {}) {
|
|
30
|
+
const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
|
|
31
|
+
// Mutations require React Query - skip generation when disabled
|
|
32
|
+
if (!reactQueryEnabled) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const enumSet = new Set(enumsFromSchemaTypes);
|
|
11
36
|
const project = createProject();
|
|
12
37
|
const { typeName, singularName } = getTableNames(table);
|
|
13
38
|
const hookName = getCreateMutationHookName(table);
|
|
14
39
|
const mutationName = getCreateMutationName(table);
|
|
15
40
|
const scalarFields = getScalarFields(table);
|
|
41
|
+
// Get primary key field names dynamically from table constraints
|
|
42
|
+
const pkFieldNames = new Set(getPrimaryKeyInfo(table).map(pk => pk.name));
|
|
43
|
+
// Collect which enums are used by this table's fields
|
|
44
|
+
const usedEnums = new Set();
|
|
45
|
+
for (const field of scalarFields) {
|
|
46
|
+
const cleanType = field.type.gqlType.replace(/!/g, '');
|
|
47
|
+
if (enumSet.has(cleanType)) {
|
|
48
|
+
usedEnums.add(cleanType);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
16
51
|
// Generate GraphQL document via AST
|
|
17
52
|
const mutationAST = buildCreateMutationAST({ table });
|
|
18
53
|
const mutationDocument = printGraphQL(mutationAST);
|
|
19
54
|
const sourceFile = createSourceFile(project, getCreateMutationFileName(table));
|
|
20
55
|
// Add file header
|
|
21
56
|
sourceFile.insertText(0, createFileHeader(`Create mutation hook for ${typeName}`) + '\n\n');
|
|
22
|
-
//
|
|
23
|
-
|
|
57
|
+
// Build import declarations
|
|
58
|
+
const imports = [
|
|
24
59
|
createImport({
|
|
25
60
|
moduleSpecifier: '@tanstack/react-query',
|
|
26
61
|
namedImports: ['useMutation', 'useQueryClient'],
|
|
@@ -34,7 +69,16 @@ export function generateCreateMutationHook(table) {
|
|
|
34
69
|
moduleSpecifier: '../types',
|
|
35
70
|
typeOnlyNamedImports: [typeName],
|
|
36
71
|
}),
|
|
37
|
-
]
|
|
72
|
+
];
|
|
73
|
+
// Add import for enum types from schema-types if any are used
|
|
74
|
+
if (usedEnums.size > 0) {
|
|
75
|
+
imports.push(createImport({
|
|
76
|
+
moduleSpecifier: '../schema-types',
|
|
77
|
+
typeOnlyNamedImports: Array.from(usedEnums).sort(),
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
// Add imports
|
|
81
|
+
sourceFile.addImportDeclarations(imports);
|
|
38
82
|
// Re-export entity type
|
|
39
83
|
sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
|
|
40
84
|
// Add section comment
|
|
@@ -48,11 +92,9 @@ export function generateCreateMutationHook(table) {
|
|
|
48
92
|
sourceFile.addStatements('// Types');
|
|
49
93
|
sourceFile.addStatements('// ============================================================================\n');
|
|
50
94
|
// Generate CreateInput type - exclude auto-generated fields
|
|
95
|
+
// Note: Not exported to avoid conflicts with schema-types
|
|
51
96
|
const inputFields = scalarFields
|
|
52
|
-
.filter((f) =>
|
|
53
|
-
const name = f.name.toLowerCase();
|
|
54
|
-
return !['id', 'createdat', 'updatedat', 'created_at', 'updated_at'].includes(name);
|
|
55
|
-
})
|
|
97
|
+
.filter((f) => !isAutoGeneratedField(f.name, pkFieldNames))
|
|
56
98
|
.map((f) => ({
|
|
57
99
|
name: f.name,
|
|
58
100
|
type: `${fieldTypeToTs(f.type)} | null`,
|
|
@@ -60,6 +102,7 @@ export function generateCreateMutationHook(table) {
|
|
|
60
102
|
}));
|
|
61
103
|
sourceFile.addInterface(createInterface(`${typeName}CreateInput`, inputFields, {
|
|
62
104
|
docs: [`Input type for creating a ${typeName}`],
|
|
105
|
+
isExported: false,
|
|
63
106
|
}));
|
|
64
107
|
// Variables interface
|
|
65
108
|
sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
|
|
@@ -137,25 +180,44 @@ mutate({
|
|
|
137
180
|
// ============================================================================
|
|
138
181
|
/**
|
|
139
182
|
* Generate update mutation hook file content using AST
|
|
183
|
+
* When reactQueryEnabled is false, returns null since mutations require React Query
|
|
140
184
|
*/
|
|
141
|
-
export function generateUpdateMutationHook(table) {
|
|
185
|
+
export function generateUpdateMutationHook(table, options = {}) {
|
|
186
|
+
const { reactQueryEnabled = true, enumsFromSchemaTypes = [] } = options;
|
|
187
|
+
// Mutations require React Query - skip generation when disabled
|
|
188
|
+
if (!reactQueryEnabled) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
142
191
|
// Check if update mutation exists
|
|
143
192
|
if (table.query?.update === null) {
|
|
144
193
|
return null;
|
|
145
194
|
}
|
|
195
|
+
const enumSet = new Set(enumsFromSchemaTypes);
|
|
146
196
|
const project = createProject();
|
|
147
197
|
const { typeName, singularName } = getTableNames(table);
|
|
148
198
|
const hookName = getUpdateMutationHookName(table);
|
|
149
199
|
const mutationName = getUpdateMutationName(table);
|
|
150
200
|
const scalarFields = getScalarFields(table);
|
|
201
|
+
// Get primary key info dynamically from table constraints
|
|
202
|
+
const pkFields = getPrimaryKeyInfo(table);
|
|
203
|
+
const pkField = pkFields[0]; // Use first PK field
|
|
204
|
+
const pkFieldNames = new Set(pkFields.map(pk => pk.name));
|
|
205
|
+
// Collect which enums are used by this table's fields
|
|
206
|
+
const usedEnums = new Set();
|
|
207
|
+
for (const field of scalarFields) {
|
|
208
|
+
const cleanType = field.type.gqlType.replace(/!/g, '');
|
|
209
|
+
if (enumSet.has(cleanType)) {
|
|
210
|
+
usedEnums.add(cleanType);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
151
213
|
// Generate GraphQL document via AST
|
|
152
214
|
const mutationAST = buildUpdateMutationAST({ table });
|
|
153
215
|
const mutationDocument = printGraphQL(mutationAST);
|
|
154
216
|
const sourceFile = createSourceFile(project, getUpdateMutationFileName(table));
|
|
155
217
|
// Add file header
|
|
156
218
|
sourceFile.insertText(0, createFileHeader(`Update mutation hook for ${typeName}`) + '\n\n');
|
|
157
|
-
//
|
|
158
|
-
|
|
219
|
+
// Build import declarations
|
|
220
|
+
const imports = [
|
|
159
221
|
createImport({
|
|
160
222
|
moduleSpecifier: '@tanstack/react-query',
|
|
161
223
|
namedImports: ['useMutation', 'useQueryClient'],
|
|
@@ -169,7 +231,16 @@ export function generateUpdateMutationHook(table) {
|
|
|
169
231
|
moduleSpecifier: '../types',
|
|
170
232
|
typeOnlyNamedImports: [typeName],
|
|
171
233
|
}),
|
|
172
|
-
]
|
|
234
|
+
];
|
|
235
|
+
// Add import for enum types from schema-types if any are used
|
|
236
|
+
if (usedEnums.size > 0) {
|
|
237
|
+
imports.push(createImport({
|
|
238
|
+
moduleSpecifier: '../schema-types',
|
|
239
|
+
typeOnlyNamedImports: Array.from(usedEnums).sort(),
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
// Add imports
|
|
243
|
+
sourceFile.addImportDeclarations(imports);
|
|
173
244
|
// Re-export entity type
|
|
174
245
|
sourceFile.addStatements(`\n// Re-export entity type for convenience\nexport type { ${typeName} };\n`);
|
|
175
246
|
// Add section comment
|
|
@@ -182,9 +253,10 @@ export function generateUpdateMutationHook(table) {
|
|
|
182
253
|
sourceFile.addStatements('\n// ============================================================================');
|
|
183
254
|
sourceFile.addStatements('// Types');
|
|
184
255
|
sourceFile.addStatements('// ============================================================================\n');
|
|
185
|
-
// Generate Patch type - all fields optional
|
|
256
|
+
// Generate Patch type - all fields optional, exclude primary key
|
|
257
|
+
// Note: Not exported to avoid conflicts with schema-types
|
|
186
258
|
const patchFields = scalarFields
|
|
187
|
-
.filter((f) => f.name
|
|
259
|
+
.filter((f) => !pkFieldNames.has(f.name))
|
|
188
260
|
.map((f) => ({
|
|
189
261
|
name: f.name,
|
|
190
262
|
type: `${fieldTypeToTs(f.type)} | null`,
|
|
@@ -192,13 +264,14 @@ export function generateUpdateMutationHook(table) {
|
|
|
192
264
|
}));
|
|
193
265
|
sourceFile.addInterface(createInterface(`${typeName}Patch`, patchFields, {
|
|
194
266
|
docs: [`Patch type for updating a ${typeName} - all fields optional`],
|
|
267
|
+
isExported: false,
|
|
195
268
|
}));
|
|
196
|
-
// Variables interface
|
|
269
|
+
// Variables interface - use dynamic PK field name and type
|
|
197
270
|
sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
|
|
198
271
|
{
|
|
199
272
|
name: 'input',
|
|
200
273
|
type: `{
|
|
201
|
-
|
|
274
|
+
${pkField.name}: ${pkField.tsType};
|
|
202
275
|
patch: ${typeName}Patch;
|
|
203
276
|
}`,
|
|
204
277
|
},
|
|
@@ -237,7 +310,7 @@ export function generateUpdateMutationHook(table) {
|
|
|
237
310
|
),
|
|
238
311
|
onSuccess: (_, variables) => {
|
|
239
312
|
// Invalidate specific item and list queries
|
|
240
|
-
queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.
|
|
313
|
+
queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
|
|
241
314
|
queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
|
|
242
315
|
},
|
|
243
316
|
...options,
|
|
@@ -252,7 +325,7 @@ const { mutate, isPending } = ${hookName}();
|
|
|
252
325
|
|
|
253
326
|
mutate({
|
|
254
327
|
input: {
|
|
255
|
-
|
|
328
|
+
${pkField.name}: ${pkField.tsType === 'string' ? "'value-here'" : '123'},
|
|
256
329
|
patch: {
|
|
257
330
|
// ... fields to update
|
|
258
331
|
},
|
|
@@ -272,8 +345,14 @@ mutate({
|
|
|
272
345
|
// ============================================================================
|
|
273
346
|
/**
|
|
274
347
|
* Generate delete mutation hook file content using AST
|
|
348
|
+
* When reactQueryEnabled is false, returns null since mutations require React Query
|
|
275
349
|
*/
|
|
276
|
-
export function generateDeleteMutationHook(table) {
|
|
350
|
+
export function generateDeleteMutationHook(table, options = {}) {
|
|
351
|
+
const { reactQueryEnabled = true } = options;
|
|
352
|
+
// Mutations require React Query - skip generation when disabled
|
|
353
|
+
if (!reactQueryEnabled) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
277
356
|
// Check if delete mutation exists
|
|
278
357
|
if (table.query?.delete === null) {
|
|
279
358
|
return null;
|
|
@@ -282,6 +361,9 @@ export function generateDeleteMutationHook(table) {
|
|
|
282
361
|
const { typeName } = getTableNames(table);
|
|
283
362
|
const hookName = getDeleteMutationHookName(table);
|
|
284
363
|
const mutationName = getDeleteMutationName(table);
|
|
364
|
+
// Get primary key info dynamically from table constraints
|
|
365
|
+
const pkFields = getPrimaryKeyInfo(table);
|
|
366
|
+
const pkField = pkFields[0]; // Use first PK field
|
|
285
367
|
// Generate GraphQL document via AST
|
|
286
368
|
const mutationAST = buildDeleteMutationAST({ table });
|
|
287
369
|
const mutationDocument = printGraphQL(mutationAST);
|
|
@@ -310,12 +392,12 @@ export function generateDeleteMutationHook(table) {
|
|
|
310
392
|
sourceFile.addStatements('\n// ============================================================================');
|
|
311
393
|
sourceFile.addStatements('// Types');
|
|
312
394
|
sourceFile.addStatements('// ============================================================================\n');
|
|
313
|
-
// Variables interface
|
|
395
|
+
// Variables interface - use dynamic PK field name and type
|
|
314
396
|
sourceFile.addInterface(createInterface(`${ucFirst(mutationName)}MutationVariables`, [
|
|
315
397
|
{
|
|
316
398
|
name: 'input',
|
|
317
399
|
type: `{
|
|
318
|
-
|
|
400
|
+
${pkField.name}: ${pkField.tsType};
|
|
319
401
|
}`,
|
|
320
402
|
},
|
|
321
403
|
]));
|
|
@@ -325,7 +407,7 @@ export function generateDeleteMutationHook(table) {
|
|
|
325
407
|
name: mutationName,
|
|
326
408
|
type: `{
|
|
327
409
|
clientMutationId: string | null;
|
|
328
|
-
|
|
410
|
+
deleted${ucFirst(pkField.name)}: ${pkField.tsType} | null;
|
|
329
411
|
}`,
|
|
330
412
|
},
|
|
331
413
|
]));
|
|
@@ -354,7 +436,7 @@ export function generateDeleteMutationHook(table) {
|
|
|
354
436
|
),
|
|
355
437
|
onSuccess: (_, variables) => {
|
|
356
438
|
// Remove from cache and invalidate list
|
|
357
|
-
queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.
|
|
439
|
+
queryClient.removeQueries({ queryKey: ['${typeName.toLowerCase()}', 'detail', variables.input.${pkField.name}] });
|
|
358
440
|
queryClient.invalidateQueries({ queryKey: ['${typeName.toLowerCase()}', 'list'] });
|
|
359
441
|
},
|
|
360
442
|
...options,
|
|
@@ -369,7 +451,7 @@ const { mutate, isPending } = ${hookName}();
|
|
|
369
451
|
|
|
370
452
|
mutate({
|
|
371
453
|
input: {
|
|
372
|
-
|
|
454
|
+
${pkField.name}: ${pkField.tsType === 'string' ? "'value-to-delete'" : '123'},
|
|
373
455
|
},
|
|
374
456
|
});
|
|
375
457
|
\`\`\``,
|
|
@@ -386,16 +468,20 @@ mutate({
|
|
|
386
468
|
// ============================================================================
|
|
387
469
|
/**
|
|
388
470
|
* Generate all mutation hook files for all tables
|
|
471
|
+
* When reactQueryEnabled is false, returns empty array since mutations require React Query
|
|
389
472
|
*/
|
|
390
|
-
export function generateAllMutationHooks(tables) {
|
|
473
|
+
export function generateAllMutationHooks(tables, options = {}) {
|
|
391
474
|
const files = [];
|
|
392
475
|
for (const table of tables) {
|
|
393
|
-
|
|
394
|
-
|
|
476
|
+
const createHook = generateCreateMutationHook(table, options);
|
|
477
|
+
if (createHook) {
|
|
478
|
+
files.push(createHook);
|
|
479
|
+
}
|
|
480
|
+
const updateHook = generateUpdateMutationHook(table, options);
|
|
395
481
|
if (updateHook) {
|
|
396
482
|
files.push(updateHook);
|
|
397
483
|
}
|
|
398
|
-
const deleteHook = generateDeleteMutationHook(table);
|
|
484
|
+
const deleteHook = generateDeleteMutationHook(table, options);
|
|
399
485
|
if (deleteHook) {
|
|
400
486
|
files.push(deleteHook);
|
|
401
487
|
}
|
|
@@ -12,9 +12,11 @@ export function generateModelsBarrel(tables) {
|
|
|
12
12
|
for (const table of tables) {
|
|
13
13
|
const { typeName } = getTableNames(table);
|
|
14
14
|
const modelName = `${typeName}Model`;
|
|
15
|
-
|
|
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: `./${
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
529
|
-
|
|
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
|
-
|
|
599
|
-
|
|
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.
|
|
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
|
-
//
|
|
729
|
+
// 4. Table filter types
|
|
677
730
|
addTableFilterTypes(sourceFile, tables);
|
|
678
|
-
//
|
|
731
|
+
// 5. OrderBy types
|
|
679
732
|
addOrderByTypes(sourceFile, tables);
|
|
680
|
-
//
|
|
733
|
+
// 6. CRUD input types
|
|
681
734
|
addAllCrudInputTypes(sourceFile, tables);
|
|
682
735
|
}
|
|
683
|
-
//
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
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 =
|
|
16
|
-
const orderByTypeName =
|
|
17
|
+
const whereTypeName = getFilterTypeName(table);
|
|
18
|
+
const orderByTypeName = getOrderByTypeName(table);
|
|
17
19
|
const createInputTypeName = `Create${typeName}Input`;
|
|
18
20
|
const updateInputTypeName = `Update${typeName}Input`;
|
|
19
21
|
const deleteInputTypeName = `Delete${typeName}Input`;
|
|
@@ -11,15 +11,19 @@ export interface GeneratedQueryFile {
|
|
|
11
11
|
fileName: string;
|
|
12
12
|
content: string;
|
|
13
13
|
}
|
|
14
|
+
export interface QueryGeneratorOptions {
|
|
15
|
+
/** Whether to generate React Query hooks (default: true for backwards compatibility) */
|
|
16
|
+
reactQueryEnabled?: boolean;
|
|
17
|
+
}
|
|
14
18
|
/**
|
|
15
19
|
* Generate list query hook file content using AST
|
|
16
20
|
*/
|
|
17
|
-
export declare function generateListQueryHook(table: CleanTable): GeneratedQueryFile;
|
|
21
|
+
export declare function generateListQueryHook(table: CleanTable, options?: QueryGeneratorOptions): GeneratedQueryFile;
|
|
18
22
|
/**
|
|
19
23
|
* Generate single item query hook file content using AST
|
|
20
24
|
*/
|
|
21
|
-
export declare function generateSingleQueryHook(table: CleanTable): GeneratedQueryFile;
|
|
25
|
+
export declare function generateSingleQueryHook(table: CleanTable, options?: QueryGeneratorOptions): GeneratedQueryFile;
|
|
22
26
|
/**
|
|
23
27
|
* Generate all query hook files for all tables
|
|
24
28
|
*/
|
|
25
|
-
export declare function generateAllQueryHooks(tables: CleanTable[]): GeneratedQueryFile[];
|
|
29
|
+
export declare function generateAllQueryHooks(tables: CleanTable[], options?: QueryGeneratorOptions): GeneratedQueryFile[];
|