@constructive-io/graphql-codegen 2.24.1 → 2.26.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 +403 -279
- package/cli/codegen/orm/client-generator.js +475 -136
- package/cli/codegen/orm/custom-ops-generator.js +8 -3
- package/cli/codegen/orm/model-generator.js +18 -5
- package/cli/codegen/orm/select-types.d.ts +33 -0
- package/cli/commands/generate-orm.d.ts +14 -0
- package/cli/commands/generate-orm.js +160 -44
- package/cli/commands/generate.d.ts +22 -0
- package/cli/commands/generate.js +195 -55
- package/cli/commands/init.js +29 -9
- package/cli/index.js +133 -28
- package/cli/watch/orchestrator.d.ts +4 -0
- package/cli/watch/orchestrator.js +4 -0
- package/esm/cli/codegen/orm/client-generator.js +475 -136
- package/esm/cli/codegen/orm/custom-ops-generator.js +8 -3
- package/esm/cli/codegen/orm/model-generator.js +18 -5
- package/esm/cli/codegen/orm/select-types.d.ts +33 -0
- package/esm/cli/commands/generate-orm.d.ts +14 -0
- package/esm/cli/commands/generate-orm.js +161 -45
- package/esm/cli/commands/generate.d.ts +22 -0
- package/esm/cli/commands/generate.js +195 -56
- package/esm/cli/commands/init.js +29 -9
- package/esm/cli/index.js +134 -29
- package/esm/cli/watch/orchestrator.d.ts +4 -0
- package/esm/cli/watch/orchestrator.js +5 -1
- package/esm/types/config.d.ts +39 -2
- package/esm/types/config.js +88 -4
- package/esm/types/index.d.ts +2 -2
- package/esm/types/index.js +1 -1
- package/package.json +10 -7
- package/types/config.d.ts +39 -2
- package/types/config.js +91 -4
- package/types/index.d.ts +2 -2
- package/types/index.js +2 -1
- package/cli/codegen/orm/query-builder.d.ts +0 -161
- package/cli/codegen/orm/query-builder.js +0 -366
- package/esm/cli/codegen/orm/query-builder.d.ts +0 -161
- package/esm/cli/codegen/orm/query-builder.js +0 -353
|
@@ -159,9 +159,14 @@ function buildOperationMethod(op, operationType) {
|
|
|
159
159
|
const optionsParam = t.identifier('options');
|
|
160
160
|
optionsParam.optional = true;
|
|
161
161
|
if (selectTypeName) {
|
|
162
|
+
// Use DeepExact<S, SelectType> to enforce strict field validation
|
|
163
|
+
// This catches invalid fields even when mixed with valid ones
|
|
162
164
|
optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeLiteral([
|
|
163
165
|
(() => {
|
|
164
|
-
const prop = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier('
|
|
166
|
+
const prop = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
|
|
167
|
+
t.tsTypeReference(t.identifier('S')),
|
|
168
|
+
t.tsTypeReference(t.identifier(selectTypeName)),
|
|
169
|
+
]))));
|
|
165
170
|
prop.optional = true;
|
|
166
171
|
return prop;
|
|
167
172
|
})(),
|
|
@@ -229,7 +234,7 @@ function generateCustomQueryOpsFile(operations) {
|
|
|
229
234
|
// Add imports
|
|
230
235
|
statements.push(createImportDeclaration('../client', ['OrmClient']));
|
|
231
236
|
statements.push(createImportDeclaration('../query-builder', ['QueryBuilder', 'buildCustomDocument']));
|
|
232
|
-
statements.push(createImportDeclaration('../select-types', ['InferSelectResult'], true));
|
|
237
|
+
statements.push(createImportDeclaration('../select-types', ['InferSelectResult', 'DeepExact'], true));
|
|
233
238
|
if (allTypeImports.length > 0) {
|
|
234
239
|
statements.push(createImportDeclaration('../input-types', allTypeImports, true));
|
|
235
240
|
}
|
|
@@ -267,7 +272,7 @@ function generateCustomMutationOpsFile(operations) {
|
|
|
267
272
|
// Add imports
|
|
268
273
|
statements.push(createImportDeclaration('../client', ['OrmClient']));
|
|
269
274
|
statements.push(createImportDeclaration('../query-builder', ['QueryBuilder', 'buildCustomDocument']));
|
|
270
|
-
statements.push(createImportDeclaration('../select-types', ['InferSelectResult'], true));
|
|
275
|
+
statements.push(createImportDeclaration('../select-types', ['InferSelectResult', 'DeepExact'], true));
|
|
271
276
|
if (allTypeImports.length > 0) {
|
|
272
277
|
statements.push(createImportDeclaration('../input-types', allTypeImports, true));
|
|
273
278
|
}
|
|
@@ -100,7 +100,7 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
100
100
|
]));
|
|
101
101
|
statements.push(createImportDeclaration('../select-types', [
|
|
102
102
|
'ConnectionResult', 'FindManyArgs', 'FindFirstArgs', 'CreateArgs',
|
|
103
|
-
'UpdateArgs', 'DeleteArgs', 'InferSelectResult',
|
|
103
|
+
'UpdateArgs', 'DeleteArgs', 'InferSelectResult', 'DeepExact',
|
|
104
104
|
], true));
|
|
105
105
|
statements.push(createImportDeclaration('../input-types', [
|
|
106
106
|
typeName, relationTypeName, selectTypeName, whereTypeName, orderByTypeName,
|
|
@@ -114,10 +114,14 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
114
114
|
paramProp.accessibility = 'private';
|
|
115
115
|
classBody.push(t.classMethod('constructor', t.identifier('constructor'), [paramProp], t.blockStatement([])));
|
|
116
116
|
// findMany method
|
|
117
|
+
// Use DeepExact<S, SelectType> to enforce strict field validation
|
|
117
118
|
const findManyParam = t.identifier('args');
|
|
118
119
|
findManyParam.optional = true;
|
|
119
120
|
findManyParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('FindManyArgs'), t.tsTypeParameterInstantiation([
|
|
120
|
-
t.tsTypeReference(t.identifier('
|
|
121
|
+
t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
|
|
122
|
+
t.tsTypeReference(t.identifier('S')),
|
|
123
|
+
t.tsTypeReference(t.identifier(selectTypeName)),
|
|
124
|
+
])),
|
|
121
125
|
t.tsTypeReference(t.identifier(whereTypeName)),
|
|
122
126
|
t.tsTypeReference(t.identifier(orderByTypeName)),
|
|
123
127
|
])));
|
|
@@ -152,7 +156,10 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
152
156
|
const findFirstParam = t.identifier('args');
|
|
153
157
|
findFirstParam.optional = true;
|
|
154
158
|
findFirstParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation([
|
|
155
|
-
t.tsTypeReference(t.identifier('
|
|
159
|
+
t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
|
|
160
|
+
t.tsTypeReference(t.identifier('S')),
|
|
161
|
+
t.tsTypeReference(t.identifier(selectTypeName)),
|
|
162
|
+
])),
|
|
156
163
|
t.tsTypeReference(t.identifier(whereTypeName)),
|
|
157
164
|
])));
|
|
158
165
|
const findFirstReturnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
|
|
@@ -178,7 +185,10 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
178
185
|
// create method
|
|
179
186
|
const createParam = t.identifier('args');
|
|
180
187
|
createParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('CreateArgs'), t.tsTypeParameterInstantiation([
|
|
181
|
-
t.tsTypeReference(t.identifier('
|
|
188
|
+
t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
|
|
189
|
+
t.tsTypeReference(t.identifier('S')),
|
|
190
|
+
t.tsTypeReference(t.identifier(selectTypeName)),
|
|
191
|
+
])),
|
|
182
192
|
t.tsIndexedAccessType(t.tsTypeReference(t.identifier(createInputTypeName)), t.tsLiteralType(t.stringLiteral(singularName))),
|
|
183
193
|
])));
|
|
184
194
|
const createReturnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
|
|
@@ -204,7 +214,10 @@ function generateModelFile(table, _useSharedTypes) {
|
|
|
204
214
|
if (updateMutationName) {
|
|
205
215
|
const updateParam = t.identifier('args');
|
|
206
216
|
updateParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('UpdateArgs'), t.tsTypeParameterInstantiation([
|
|
207
|
-
t.tsTypeReference(t.identifier('
|
|
217
|
+
t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
|
|
218
|
+
t.tsTypeReference(t.identifier('S')),
|
|
219
|
+
t.tsTypeReference(t.identifier(selectTypeName)),
|
|
220
|
+
])),
|
|
208
221
|
t.tsTypeLiteral([t.tsPropertySignature(t.identifier('id'), t.tsTypeAnnotation(t.tsStringKeyword()))]),
|
|
209
222
|
t.tsTypeReference(t.identifier(patchTypeName)),
|
|
210
223
|
])));
|
|
@@ -44,6 +44,39 @@ export interface NestedSelectConfig {
|
|
|
44
44
|
filter?: Record<string, unknown>;
|
|
45
45
|
orderBy?: string[];
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Recursively validates select objects, rejecting unknown keys.
|
|
49
|
+
*
|
|
50
|
+
* This type ensures that users can only select fields that actually exist
|
|
51
|
+
* in the GraphQL schema. It returns `never` if any excess keys are found
|
|
52
|
+
* at any nesting level, causing a TypeScript compile error.
|
|
53
|
+
*
|
|
54
|
+
* Why this is needed:
|
|
55
|
+
* TypeScript's excess property checking has a quirk where it only catches
|
|
56
|
+
* invalid fields when they are the ONLY fields. When mixed with valid fields
|
|
57
|
+
* (e.g., `{ id: true, invalidField: true }`), the structural typing allows
|
|
58
|
+
* the excess property through. This type explicitly checks for and rejects
|
|
59
|
+
* such cases.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // This will cause a type error because 'invalid' doesn't exist:
|
|
63
|
+
* type Result = DeepExact<{ id: true, invalid: true }, { id?: boolean }>;
|
|
64
|
+
* // Result = never (causes assignment error)
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // This works because all fields are valid:
|
|
68
|
+
* type Result = DeepExact<{ id: true }, { id?: boolean; name?: boolean }>;
|
|
69
|
+
* // Result = { id: true }
|
|
70
|
+
*/
|
|
71
|
+
export type DeepExact<T, Shape> = T extends Shape ? Exclude<keyof T, keyof Shape> extends never ? {
|
|
72
|
+
[K in keyof T]: K extends keyof Shape ? T[K] extends {
|
|
73
|
+
select: infer NS;
|
|
74
|
+
} ? Shape[K] extends {
|
|
75
|
+
select?: infer ShapeNS;
|
|
76
|
+
} ? {
|
|
77
|
+
select: DeepExact<NS, NonNullable<ShapeNS>>;
|
|
78
|
+
} : T[K] : T[K] : never;
|
|
79
|
+
} : never : never;
|
|
47
80
|
/**
|
|
48
81
|
* Infers the result type from a select configuration
|
|
49
82
|
*
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
export interface GenerateOrmOptions {
|
|
10
10
|
/** Path to config file */
|
|
11
11
|
config?: string;
|
|
12
|
+
/** Named target in a multi-target config */
|
|
13
|
+
target?: string;
|
|
12
14
|
/** GraphQL endpoint URL (overrides config) */
|
|
13
15
|
endpoint?: string;
|
|
14
16
|
/** Path to GraphQL schema file (.graphql) */
|
|
@@ -24,9 +26,21 @@ export interface GenerateOrmOptions {
|
|
|
24
26
|
/** Skip custom operations (only generate table CRUD) */
|
|
25
27
|
skipCustomOperations?: boolean;
|
|
26
28
|
}
|
|
29
|
+
export interface GenerateOrmTargetResult {
|
|
30
|
+
name: string;
|
|
31
|
+
output: string;
|
|
32
|
+
success: boolean;
|
|
33
|
+
message: string;
|
|
34
|
+
tables?: string[];
|
|
35
|
+
customQueries?: string[];
|
|
36
|
+
customMutations?: string[];
|
|
37
|
+
filesWritten?: string[];
|
|
38
|
+
errors?: string[];
|
|
39
|
+
}
|
|
27
40
|
export interface GenerateOrmResult {
|
|
28
41
|
success: boolean;
|
|
29
42
|
message: string;
|
|
43
|
+
targets?: GenerateOrmTargetResult[];
|
|
30
44
|
tables?: string[];
|
|
31
45
|
customQueries?: string[];
|
|
32
46
|
customMutations?: string[];
|
|
@@ -19,9 +19,9 @@ const orm_1 = require("../codegen/orm");
|
|
|
19
19
|
* Execute the generate-orm command
|
|
20
20
|
*/
|
|
21
21
|
async function generateOrmCommand(options = {}) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
if (options.verbose) {
|
|
23
|
+
console.log('Loading configuration...');
|
|
24
|
+
}
|
|
25
25
|
const configResult = await loadConfig(options);
|
|
26
26
|
if (!configResult.success) {
|
|
27
27
|
return {
|
|
@@ -29,26 +29,73 @@ async function generateOrmCommand(options = {}) {
|
|
|
29
29
|
message: configResult.error,
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
const targets = configResult.targets ?? [];
|
|
33
|
+
if (targets.length === 0) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
message: 'No targets resolved from configuration.',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const isMultiTarget = configResult.isMulti ?? targets.length > 1;
|
|
40
|
+
const results = [];
|
|
41
|
+
for (const target of targets) {
|
|
42
|
+
const result = await generateOrmForTarget(target, options, isMultiTarget);
|
|
43
|
+
results.push(result);
|
|
44
|
+
}
|
|
45
|
+
if (!isMultiTarget) {
|
|
46
|
+
const [result] = results;
|
|
47
|
+
return {
|
|
48
|
+
success: result.success,
|
|
49
|
+
message: result.message,
|
|
50
|
+
targets: results,
|
|
51
|
+
tables: result.tables,
|
|
52
|
+
customQueries: result.customQueries,
|
|
53
|
+
customMutations: result.customMutations,
|
|
54
|
+
filesWritten: result.filesWritten,
|
|
55
|
+
errors: result.errors,
|
|
56
|
+
};
|
|
38
57
|
}
|
|
39
|
-
|
|
40
|
-
|
|
58
|
+
const successCount = results.filter((result) => result.success).length;
|
|
59
|
+
const failedCount = results.length - successCount;
|
|
60
|
+
const summaryMessage = failedCount === 0
|
|
61
|
+
? `Generated ORM clients for ${results.length} targets.`
|
|
62
|
+
: `Generated ORM clients for ${successCount} of ${results.length} targets.`;
|
|
63
|
+
return {
|
|
64
|
+
success: failedCount === 0,
|
|
65
|
+
message: summaryMessage,
|
|
66
|
+
targets: results,
|
|
67
|
+
errors: failedCount > 0
|
|
68
|
+
? results.flatMap((result) => result.errors ?? [])
|
|
69
|
+
: undefined,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function generateOrmForTarget(target, options, isMultiTarget) {
|
|
73
|
+
const config = target.config;
|
|
74
|
+
const outputDir = options.output || config.orm?.output || './generated/orm';
|
|
75
|
+
const prefix = isMultiTarget ? `[${target.name}] ` : '';
|
|
76
|
+
const log = options.verbose
|
|
77
|
+
? (message) => console.log(`${prefix}${message}`)
|
|
78
|
+
: () => { };
|
|
79
|
+
const formatMessage = (message) => isMultiTarget ? `Target "${target.name}": ${message}` : message;
|
|
80
|
+
if (isMultiTarget) {
|
|
81
|
+
console.log(`\nTarget "${target.name}"`);
|
|
82
|
+
const sourceLabel = config.schema
|
|
83
|
+
? `schema: ${config.schema}`
|
|
84
|
+
: `endpoint: ${config.endpoint}`;
|
|
85
|
+
console.log(` Source: ${sourceLabel}`);
|
|
86
|
+
console.log(` Output: ${outputDir}`);
|
|
41
87
|
}
|
|
42
|
-
|
|
43
|
-
// 2. Create schema source
|
|
88
|
+
// 1. Validate source
|
|
44
89
|
const sourceValidation = (0, source_1.validateSourceOptions)({
|
|
45
90
|
endpoint: config.endpoint || undefined,
|
|
46
91
|
schema: config.schema || undefined,
|
|
47
92
|
});
|
|
48
93
|
if (!sourceValidation.valid) {
|
|
49
94
|
return {
|
|
95
|
+
name: target.name,
|
|
96
|
+
output: outputDir,
|
|
50
97
|
success: false,
|
|
51
|
-
message: sourceValidation.error,
|
|
98
|
+
message: formatMessage(sourceValidation.error),
|
|
52
99
|
};
|
|
53
100
|
}
|
|
54
101
|
const source = (0, source_1.createSchemaSource)({
|
|
@@ -57,7 +104,7 @@ async function generateOrmCommand(options = {}) {
|
|
|
57
104
|
authorization: options.authorization || config.headers['Authorization'],
|
|
58
105
|
headers: config.headers,
|
|
59
106
|
});
|
|
60
|
-
//
|
|
107
|
+
// 2. Run the codegen pipeline
|
|
61
108
|
let pipelineResult;
|
|
62
109
|
try {
|
|
63
110
|
pipelineResult = await (0, shared_1.runCodegenPipeline)({
|
|
@@ -69,21 +116,25 @@ async function generateOrmCommand(options = {}) {
|
|
|
69
116
|
}
|
|
70
117
|
catch (err) {
|
|
71
118
|
return {
|
|
119
|
+
name: target.name,
|
|
120
|
+
output: outputDir,
|
|
72
121
|
success: false,
|
|
73
|
-
message: `Failed to fetch schema: ${err instanceof Error ? err.message : 'Unknown error'}
|
|
122
|
+
message: formatMessage(`Failed to fetch schema: ${err instanceof Error ? err.message : 'Unknown error'}`),
|
|
74
123
|
};
|
|
75
124
|
}
|
|
76
125
|
const { tables, customOperations, stats } = pipelineResult;
|
|
77
|
-
//
|
|
126
|
+
// 3. Validate tables found
|
|
78
127
|
const tablesValidation = (0, shared_1.validateTablesFound)(tables);
|
|
79
128
|
if (!tablesValidation.valid) {
|
|
80
129
|
return {
|
|
130
|
+
name: target.name,
|
|
131
|
+
output: outputDir,
|
|
81
132
|
success: false,
|
|
82
|
-
message: tablesValidation.error,
|
|
133
|
+
message: formatMessage(tablesValidation.error),
|
|
83
134
|
};
|
|
84
135
|
}
|
|
85
|
-
//
|
|
86
|
-
console.log(
|
|
136
|
+
// 4. Generate ORM code
|
|
137
|
+
console.log(`${prefix}Generating code...`);
|
|
87
138
|
const { files: generatedFiles, stats: genStats } = (0, orm_1.generateOrm)({
|
|
88
139
|
tables,
|
|
89
140
|
customOperations: {
|
|
@@ -93,7 +144,7 @@ async function generateOrmCommand(options = {}) {
|
|
|
93
144
|
},
|
|
94
145
|
config,
|
|
95
146
|
});
|
|
96
|
-
console.log(
|
|
147
|
+
console.log(`${prefix}Generated ${genStats.totalFiles} files`);
|
|
97
148
|
log(` ${genStats.tables} table models`);
|
|
98
149
|
log(` ${genStats.customQueries} custom query operations`);
|
|
99
150
|
log(` ${genStats.customMutations} custom mutation operations`);
|
|
@@ -101,15 +152,17 @@ async function generateOrmCommand(options = {}) {
|
|
|
101
152
|
const customMutations = customOperations.mutations.map((m) => m.name);
|
|
102
153
|
if (options.dryRun) {
|
|
103
154
|
return {
|
|
155
|
+
name: target.name,
|
|
156
|
+
output: outputDir,
|
|
104
157
|
success: true,
|
|
105
|
-
message: `Dry run complete. Would generate ${generatedFiles.length} files for ${tables.length} tables and ${stats.customQueries + stats.customMutations} custom operations
|
|
158
|
+
message: formatMessage(`Dry run complete. Would generate ${generatedFiles.length} files for ${tables.length} tables and ${stats.customQueries + stats.customMutations} custom operations.`),
|
|
106
159
|
tables: tables.map((t) => t.name),
|
|
107
160
|
customQueries,
|
|
108
161
|
customMutations,
|
|
109
162
|
filesWritten: generatedFiles.map((f) => f.path),
|
|
110
163
|
};
|
|
111
164
|
}
|
|
112
|
-
//
|
|
165
|
+
// 5. Write files
|
|
113
166
|
log('Writing files...');
|
|
114
167
|
const writeResult = await (0, generate_1.writeGeneratedFiles)(generatedFiles, outputDir, [
|
|
115
168
|
'models',
|
|
@@ -118,23 +171,45 @@ async function generateOrmCommand(options = {}) {
|
|
|
118
171
|
]);
|
|
119
172
|
if (!writeResult.success) {
|
|
120
173
|
return {
|
|
174
|
+
name: target.name,
|
|
175
|
+
output: outputDir,
|
|
121
176
|
success: false,
|
|
122
|
-
message: `Failed to write files: ${writeResult.errors?.join(', ')}
|
|
177
|
+
message: formatMessage(`Failed to write files: ${writeResult.errors?.join(', ')}`),
|
|
123
178
|
errors: writeResult.errors,
|
|
124
179
|
};
|
|
125
180
|
}
|
|
126
181
|
const totalOps = customQueries.length + customMutations.length;
|
|
127
182
|
const customOpsMsg = totalOps > 0 ? ` and ${totalOps} custom operations` : '';
|
|
128
183
|
return {
|
|
184
|
+
name: target.name,
|
|
185
|
+
output: outputDir,
|
|
129
186
|
success: true,
|
|
130
|
-
message: `Generated ORM client for ${tables.length} tables${customOpsMsg}. Files written to ${outputDir}
|
|
187
|
+
message: formatMessage(`Generated ORM client for ${tables.length} tables${customOpsMsg}. Files written to ${outputDir}`),
|
|
131
188
|
tables: tables.map((t) => t.name),
|
|
132
189
|
customQueries,
|
|
133
190
|
customMutations,
|
|
134
191
|
filesWritten: writeResult.filesWritten,
|
|
135
192
|
};
|
|
136
193
|
}
|
|
194
|
+
function buildTargetOverrides(options) {
|
|
195
|
+
const overrides = {};
|
|
196
|
+
if (options.endpoint) {
|
|
197
|
+
overrides.endpoint = options.endpoint;
|
|
198
|
+
overrides.schema = undefined;
|
|
199
|
+
}
|
|
200
|
+
if (options.schema) {
|
|
201
|
+
overrides.schema = options.schema;
|
|
202
|
+
overrides.endpoint = undefined;
|
|
203
|
+
}
|
|
204
|
+
return overrides;
|
|
205
|
+
}
|
|
137
206
|
async function loadConfig(options) {
|
|
207
|
+
if (options.endpoint && options.schema) {
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
error: 'Cannot use both --endpoint and --schema. Choose one source.',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
138
213
|
// Find config file
|
|
139
214
|
let configPath = options.config;
|
|
140
215
|
if (!configPath) {
|
|
@@ -148,29 +223,70 @@ async function loadConfig(options) {
|
|
|
148
223
|
}
|
|
149
224
|
baseConfig = loadResult.config;
|
|
150
225
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
226
|
+
const overrides = buildTargetOverrides(options);
|
|
227
|
+
if ((0, config_1.isMultiConfig)(baseConfig)) {
|
|
228
|
+
if (Object.keys(baseConfig.targets).length === 0) {
|
|
229
|
+
return {
|
|
230
|
+
success: false,
|
|
231
|
+
error: 'Config file defines no targets.',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (!options.target &&
|
|
235
|
+
(options.endpoint || options.schema || options.output)) {
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
error: 'Multiple targets configured. Use --target with --endpoint, --schema, or --output.',
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (options.target && !baseConfig.targets[options.target]) {
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
error: `Target "${options.target}" not found in config file.`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const selectedTargets = options.target
|
|
248
|
+
? { [options.target]: baseConfig.targets[options.target] }
|
|
249
|
+
: baseConfig.targets;
|
|
250
|
+
const defaults = baseConfig.defaults ?? {};
|
|
251
|
+
const resolvedTargets = [];
|
|
252
|
+
for (const [name, target] of Object.entries(selectedTargets)) {
|
|
253
|
+
let mergedTarget = (0, config_1.mergeConfig)(defaults, target);
|
|
254
|
+
if (options.target && name === options.target) {
|
|
255
|
+
mergedTarget = (0, config_1.mergeConfig)(mergedTarget, overrides);
|
|
256
|
+
}
|
|
257
|
+
if (!mergedTarget.endpoint && !mergedTarget.schema) {
|
|
258
|
+
return {
|
|
259
|
+
success: false,
|
|
260
|
+
error: `Target "${name}" is missing an endpoint or schema.`,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
resolvedTargets.push({
|
|
264
|
+
name,
|
|
265
|
+
config: (0, config_1.resolveConfig)(mergedTarget),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
success: true,
|
|
270
|
+
targets: resolvedTargets,
|
|
271
|
+
isMulti: true,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (options.target) {
|
|
275
|
+
return {
|
|
276
|
+
success: false,
|
|
277
|
+
error: 'Config file does not define targets. Remove --target to continue.',
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const mergedConfig = (0, config_1.mergeConfig)(baseConfig, overrides);
|
|
167
281
|
if (!mergedConfig.endpoint && !mergedConfig.schema) {
|
|
168
282
|
return {
|
|
169
283
|
success: false,
|
|
170
284
|
error: 'No source specified. Use --endpoint or --schema, or create a config file with "graphql-codegen init".',
|
|
171
285
|
};
|
|
172
286
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
287
|
+
return {
|
|
288
|
+
success: true,
|
|
289
|
+
targets: [{ name: 'default', config: (0, config_1.resolveConfig)(mergedConfig) }],
|
|
290
|
+
isMulti: false,
|
|
291
|
+
};
|
|
176
292
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface GenerateOptions {
|
|
2
2
|
/** Path to config file */
|
|
3
3
|
config?: string;
|
|
4
|
+
/** Named target in a multi-target config */
|
|
5
|
+
target?: string;
|
|
4
6
|
/** GraphQL endpoint URL (overrides config) */
|
|
5
7
|
endpoint?: string;
|
|
6
8
|
/** Path to GraphQL schema file (.graphql) */
|
|
@@ -16,9 +18,21 @@ export interface GenerateOptions {
|
|
|
16
18
|
/** Skip custom operations (only generate table CRUD) */
|
|
17
19
|
skipCustomOperations?: boolean;
|
|
18
20
|
}
|
|
21
|
+
export interface GenerateTargetResult {
|
|
22
|
+
name: string;
|
|
23
|
+
output: string;
|
|
24
|
+
success: boolean;
|
|
25
|
+
message: string;
|
|
26
|
+
tables?: string[];
|
|
27
|
+
customQueries?: string[];
|
|
28
|
+
customMutations?: string[];
|
|
29
|
+
filesWritten?: string[];
|
|
30
|
+
errors?: string[];
|
|
31
|
+
}
|
|
19
32
|
export interface GenerateResult {
|
|
20
33
|
success: boolean;
|
|
21
34
|
message: string;
|
|
35
|
+
targets?: GenerateTargetResult[];
|
|
22
36
|
tables?: string[];
|
|
23
37
|
customQueries?: string[];
|
|
24
38
|
customMutations?: string[];
|
|
@@ -42,3 +56,11 @@ export interface WriteOptions {
|
|
|
42
56
|
showProgress?: boolean;
|
|
43
57
|
}
|
|
44
58
|
export declare function writeGeneratedFiles(files: GeneratedFile[], outputDir: string, subdirs: string[], options?: WriteOptions): Promise<WriteResult>;
|
|
59
|
+
/**
|
|
60
|
+
* Format generated files using oxfmt
|
|
61
|
+
* Runs oxfmt on the output directory after all files are written
|
|
62
|
+
*/
|
|
63
|
+
export declare function formatOutput(outputDir: string): {
|
|
64
|
+
success: boolean;
|
|
65
|
+
error?: string;
|
|
66
|
+
};
|