@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.
- 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 +4 -0
- package/cli/codegen/custom-mutations.js +39 -13
- package/cli/codegen/custom-queries.d.ts +4 -0
- package/cli/codegen/custom-queries.js +36 -11
- package/cli/codegen/gql-ast.js +9 -5
- package/cli/codegen/index.js +35 -7
- package/cli/codegen/mutations.d.ts +2 -0
- package/cli/codegen/mutations.js +87 -23
- 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.js +36 -27
- 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 +4 -0
- package/esm/cli/codegen/custom-mutations.js +40 -14
- package/esm/cli/codegen/custom-queries.d.ts +4 -0
- package/esm/cli/codegen/custom-queries.js +37 -12
- package/esm/cli/codegen/gql-ast.js +10 -6
- package/esm/cli/codegen/index.js +35 -7
- package/esm/cli/codegen/mutations.d.ts +2 -0
- package/esm/cli/codegen/mutations.js +88 -24
- 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.js +37 -28
- 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/schema.d.ts +2 -0
- package/package.json +8 -6
- 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/__tests__/codegen/react-query-optional.test.d.ts +0 -1
- package/__tests__/codegen/react-query-optional.test.js +0 -292
- 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/__tests__/codegen/react-query-optional.test.d.ts +0 -1
- package/esm/__tests__/codegen/react-query-optional.test.js +0 -290
- 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
package/esm/cli/codegen/index.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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, {
|
|
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
|
-
//
|
|
29
|
-
|
|
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
|
-
//
|
|
170
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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`;
|