@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
package/cli/codegen/index.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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[];
|
package/cli/codegen/mutations.js
CHANGED
|
@@ -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
|
-
//
|
|
29
|
-
|
|
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
|
-
//
|
|
164
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
400
|
-
|
|
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
|
-
|
|
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: `./${
|
|
23
|
+
moduleSpecifier: `./${moduleFileName}`,
|
|
22
24
|
namedExports: [modelName],
|
|
23
25
|
});
|
|
24
26
|
}
|
package/cli/codegen/orm/index.js
CHANGED
|
@@ -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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
534
|
-
|
|
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
|
-
|
|
604
|
-
|
|
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.
|
|
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
|
-
//
|
|
734
|
+
// 4. Table filter types
|
|
682
735
|
addTableFilterTypes(sourceFile, tables);
|
|
683
|
-
//
|
|
736
|
+
// 5. OrderBy types
|
|
684
737
|
addOrderByTypes(sourceFile, tables);
|
|
685
|
-
//
|
|
738
|
+
// 6. CRUD input types
|
|
686
739
|
addAllCrudInputTypes(sourceFile, tables);
|
|
687
740
|
}
|
|
688
|
-
//
|
|
689
|
-
|
|
690
|
-
|
|
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) {
|