@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
|
@@ -122,9 +122,14 @@ function buildOperationMethod(op, operationType) {
|
|
|
122
122
|
const optionsParam = t.identifier('options');
|
|
123
123
|
optionsParam.optional = true;
|
|
124
124
|
if (selectTypeName) {
|
|
125
|
+
// Use DeepExact<S, SelectType> to enforce strict field validation
|
|
126
|
+
// This catches invalid fields even when mixed with valid ones
|
|
125
127
|
optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeLiteral([
|
|
126
128
|
(() => {
|
|
127
|
-
const prop = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier('
|
|
129
|
+
const prop = t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
|
|
130
|
+
t.tsTypeReference(t.identifier('S')),
|
|
131
|
+
t.tsTypeReference(t.identifier(selectTypeName)),
|
|
132
|
+
]))));
|
|
128
133
|
prop.optional = true;
|
|
129
134
|
return prop;
|
|
130
135
|
})(),
|
|
@@ -192,7 +197,7 @@ export function generateCustomQueryOpsFile(operations) {
|
|
|
192
197
|
// Add imports
|
|
193
198
|
statements.push(createImportDeclaration('../client', ['OrmClient']));
|
|
194
199
|
statements.push(createImportDeclaration('../query-builder', ['QueryBuilder', 'buildCustomDocument']));
|
|
195
|
-
statements.push(createImportDeclaration('../select-types', ['InferSelectResult'], true));
|
|
200
|
+
statements.push(createImportDeclaration('../select-types', ['InferSelectResult', 'DeepExact'], true));
|
|
196
201
|
if (allTypeImports.length > 0) {
|
|
197
202
|
statements.push(createImportDeclaration('../input-types', allTypeImports, true));
|
|
198
203
|
}
|
|
@@ -230,7 +235,7 @@ export function generateCustomMutationOpsFile(operations) {
|
|
|
230
235
|
// Add imports
|
|
231
236
|
statements.push(createImportDeclaration('../client', ['OrmClient']));
|
|
232
237
|
statements.push(createImportDeclaration('../query-builder', ['QueryBuilder', 'buildCustomDocument']));
|
|
233
|
-
statements.push(createImportDeclaration('../select-types', ['InferSelectResult'], true));
|
|
238
|
+
statements.push(createImportDeclaration('../select-types', ['InferSelectResult', 'DeepExact'], true));
|
|
234
239
|
if (allTypeImports.length > 0) {
|
|
235
240
|
statements.push(createImportDeclaration('../input-types', allTypeImports, true));
|
|
236
241
|
}
|
|
@@ -63,7 +63,7 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
63
63
|
]));
|
|
64
64
|
statements.push(createImportDeclaration('../select-types', [
|
|
65
65
|
'ConnectionResult', 'FindManyArgs', 'FindFirstArgs', 'CreateArgs',
|
|
66
|
-
'UpdateArgs', 'DeleteArgs', 'InferSelectResult',
|
|
66
|
+
'UpdateArgs', 'DeleteArgs', 'InferSelectResult', 'DeepExact',
|
|
67
67
|
], true));
|
|
68
68
|
statements.push(createImportDeclaration('../input-types', [
|
|
69
69
|
typeName, relationTypeName, selectTypeName, whereTypeName, orderByTypeName,
|
|
@@ -77,10 +77,14 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
77
77
|
paramProp.accessibility = 'private';
|
|
78
78
|
classBody.push(t.classMethod('constructor', t.identifier('constructor'), [paramProp], t.blockStatement([])));
|
|
79
79
|
// findMany method
|
|
80
|
+
// Use DeepExact<S, SelectType> to enforce strict field validation
|
|
80
81
|
const findManyParam = t.identifier('args');
|
|
81
82
|
findManyParam.optional = true;
|
|
82
83
|
findManyParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('FindManyArgs'), t.tsTypeParameterInstantiation([
|
|
83
|
-
t.tsTypeReference(t.identifier('
|
|
84
|
+
t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
|
|
85
|
+
t.tsTypeReference(t.identifier('S')),
|
|
86
|
+
t.tsTypeReference(t.identifier(selectTypeName)),
|
|
87
|
+
])),
|
|
84
88
|
t.tsTypeReference(t.identifier(whereTypeName)),
|
|
85
89
|
t.tsTypeReference(t.identifier(orderByTypeName)),
|
|
86
90
|
])));
|
|
@@ -115,7 +119,10 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
115
119
|
const findFirstParam = t.identifier('args');
|
|
116
120
|
findFirstParam.optional = true;
|
|
117
121
|
findFirstParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('FindFirstArgs'), t.tsTypeParameterInstantiation([
|
|
118
|
-
t.tsTypeReference(t.identifier('
|
|
122
|
+
t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
|
|
123
|
+
t.tsTypeReference(t.identifier('S')),
|
|
124
|
+
t.tsTypeReference(t.identifier(selectTypeName)),
|
|
125
|
+
])),
|
|
119
126
|
t.tsTypeReference(t.identifier(whereTypeName)),
|
|
120
127
|
])));
|
|
121
128
|
const findFirstReturnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
|
|
@@ -141,7 +148,10 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
141
148
|
// create method
|
|
142
149
|
const createParam = t.identifier('args');
|
|
143
150
|
createParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('CreateArgs'), t.tsTypeParameterInstantiation([
|
|
144
|
-
t.tsTypeReference(t.identifier('
|
|
151
|
+
t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
|
|
152
|
+
t.tsTypeReference(t.identifier('S')),
|
|
153
|
+
t.tsTypeReference(t.identifier(selectTypeName)),
|
|
154
|
+
])),
|
|
145
155
|
t.tsIndexedAccessType(t.tsTypeReference(t.identifier(createInputTypeName)), t.tsLiteralType(t.stringLiteral(singularName))),
|
|
146
156
|
])));
|
|
147
157
|
const createReturnType = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('QueryBuilder'), t.tsTypeParameterInstantiation([
|
|
@@ -167,7 +177,10 @@ export function generateModelFile(table, _useSharedTypes) {
|
|
|
167
177
|
if (updateMutationName) {
|
|
168
178
|
const updateParam = t.identifier('args');
|
|
169
179
|
updateParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('UpdateArgs'), t.tsTypeParameterInstantiation([
|
|
170
|
-
t.tsTypeReference(t.identifier('
|
|
180
|
+
t.tsTypeReference(t.identifier('DeepExact'), t.tsTypeParameterInstantiation([
|
|
181
|
+
t.tsTypeReference(t.identifier('S')),
|
|
182
|
+
t.tsTypeReference(t.identifier(selectTypeName)),
|
|
183
|
+
])),
|
|
171
184
|
t.tsTypeLiteral([t.tsPropertySignature(t.identifier('id'), t.tsTypeAnnotation(t.tsStringKeyword()))]),
|
|
172
185
|
t.tsTypeReference(t.identifier(patchTypeName)),
|
|
173
186
|
])));
|
|
@@ -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[];
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 2. Infers table metadata from introspection (replaces _meta)
|
|
7
7
|
* 3. Generates a Prisma-like ORM client with fluent API
|
|
8
8
|
*/
|
|
9
|
-
import { resolveConfig } from '../../types/config';
|
|
9
|
+
import { isMultiConfig, mergeConfig, resolveConfig } from '../../types/config';
|
|
10
10
|
import { createSchemaSource, validateSourceOptions, } from '../introspect/source';
|
|
11
11
|
import { runCodegenPipeline, validateTablesFound } from './shared';
|
|
12
12
|
import { findConfigFile, loadConfigFile } from './init';
|
|
@@ -16,9 +16,9 @@ import { generateOrm } from '../codegen/orm';
|
|
|
16
16
|
* Execute the generate-orm command
|
|
17
17
|
*/
|
|
18
18
|
export async function generateOrmCommand(options = {}) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if (options.verbose) {
|
|
20
|
+
console.log('Loading configuration...');
|
|
21
|
+
}
|
|
22
22
|
const configResult = await loadConfig(options);
|
|
23
23
|
if (!configResult.success) {
|
|
24
24
|
return {
|
|
@@ -26,26 +26,73 @@ export async function generateOrmCommand(options = {}) {
|
|
|
26
26
|
message: configResult.error,
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
const targets = configResult.targets ?? [];
|
|
30
|
+
if (targets.length === 0) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
message: 'No targets resolved from configuration.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const isMultiTarget = configResult.isMulti ?? targets.length > 1;
|
|
37
|
+
const results = [];
|
|
38
|
+
for (const target of targets) {
|
|
39
|
+
const result = await generateOrmForTarget(target, options, isMultiTarget);
|
|
40
|
+
results.push(result);
|
|
41
|
+
}
|
|
42
|
+
if (!isMultiTarget) {
|
|
43
|
+
const [result] = results;
|
|
44
|
+
return {
|
|
45
|
+
success: result.success,
|
|
46
|
+
message: result.message,
|
|
47
|
+
targets: results,
|
|
48
|
+
tables: result.tables,
|
|
49
|
+
customQueries: result.customQueries,
|
|
50
|
+
customMutations: result.customMutations,
|
|
51
|
+
filesWritten: result.filesWritten,
|
|
52
|
+
errors: result.errors,
|
|
53
|
+
};
|
|
35
54
|
}
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
const successCount = results.filter((result) => result.success).length;
|
|
56
|
+
const failedCount = results.length - successCount;
|
|
57
|
+
const summaryMessage = failedCount === 0
|
|
58
|
+
? `Generated ORM clients for ${results.length} targets.`
|
|
59
|
+
: `Generated ORM clients for ${successCount} of ${results.length} targets.`;
|
|
60
|
+
return {
|
|
61
|
+
success: failedCount === 0,
|
|
62
|
+
message: summaryMessage,
|
|
63
|
+
targets: results,
|
|
64
|
+
errors: failedCount > 0
|
|
65
|
+
? results.flatMap((result) => result.errors ?? [])
|
|
66
|
+
: undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function generateOrmForTarget(target, options, isMultiTarget) {
|
|
70
|
+
const config = target.config;
|
|
71
|
+
const outputDir = options.output || config.orm?.output || './generated/orm';
|
|
72
|
+
const prefix = isMultiTarget ? `[${target.name}] ` : '';
|
|
73
|
+
const log = options.verbose
|
|
74
|
+
? (message) => console.log(`${prefix}${message}`)
|
|
75
|
+
: () => { };
|
|
76
|
+
const formatMessage = (message) => isMultiTarget ? `Target "${target.name}": ${message}` : message;
|
|
77
|
+
if (isMultiTarget) {
|
|
78
|
+
console.log(`\nTarget "${target.name}"`);
|
|
79
|
+
const sourceLabel = config.schema
|
|
80
|
+
? `schema: ${config.schema}`
|
|
81
|
+
: `endpoint: ${config.endpoint}`;
|
|
82
|
+
console.log(` Source: ${sourceLabel}`);
|
|
83
|
+
console.log(` Output: ${outputDir}`);
|
|
38
84
|
}
|
|
39
|
-
|
|
40
|
-
// 2. Create schema source
|
|
85
|
+
// 1. Validate source
|
|
41
86
|
const sourceValidation = validateSourceOptions({
|
|
42
87
|
endpoint: config.endpoint || undefined,
|
|
43
88
|
schema: config.schema || undefined,
|
|
44
89
|
});
|
|
45
90
|
if (!sourceValidation.valid) {
|
|
46
91
|
return {
|
|
92
|
+
name: target.name,
|
|
93
|
+
output: outputDir,
|
|
47
94
|
success: false,
|
|
48
|
-
message: sourceValidation.error,
|
|
95
|
+
message: formatMessage(sourceValidation.error),
|
|
49
96
|
};
|
|
50
97
|
}
|
|
51
98
|
const source = createSchemaSource({
|
|
@@ -54,7 +101,7 @@ export async function generateOrmCommand(options = {}) {
|
|
|
54
101
|
authorization: options.authorization || config.headers['Authorization'],
|
|
55
102
|
headers: config.headers,
|
|
56
103
|
});
|
|
57
|
-
//
|
|
104
|
+
// 2. Run the codegen pipeline
|
|
58
105
|
let pipelineResult;
|
|
59
106
|
try {
|
|
60
107
|
pipelineResult = await runCodegenPipeline({
|
|
@@ -66,21 +113,25 @@ export async function generateOrmCommand(options = {}) {
|
|
|
66
113
|
}
|
|
67
114
|
catch (err) {
|
|
68
115
|
return {
|
|
116
|
+
name: target.name,
|
|
117
|
+
output: outputDir,
|
|
69
118
|
success: false,
|
|
70
|
-
message: `Failed to fetch schema: ${err instanceof Error ? err.message : 'Unknown error'}
|
|
119
|
+
message: formatMessage(`Failed to fetch schema: ${err instanceof Error ? err.message : 'Unknown error'}`),
|
|
71
120
|
};
|
|
72
121
|
}
|
|
73
122
|
const { tables, customOperations, stats } = pipelineResult;
|
|
74
|
-
//
|
|
123
|
+
// 3. Validate tables found
|
|
75
124
|
const tablesValidation = validateTablesFound(tables);
|
|
76
125
|
if (!tablesValidation.valid) {
|
|
77
126
|
return {
|
|
127
|
+
name: target.name,
|
|
128
|
+
output: outputDir,
|
|
78
129
|
success: false,
|
|
79
|
-
message: tablesValidation.error,
|
|
130
|
+
message: formatMessage(tablesValidation.error),
|
|
80
131
|
};
|
|
81
132
|
}
|
|
82
|
-
//
|
|
83
|
-
console.log(
|
|
133
|
+
// 4. Generate ORM code
|
|
134
|
+
console.log(`${prefix}Generating code...`);
|
|
84
135
|
const { files: generatedFiles, stats: genStats } = generateOrm({
|
|
85
136
|
tables,
|
|
86
137
|
customOperations: {
|
|
@@ -90,7 +141,7 @@ export async function generateOrmCommand(options = {}) {
|
|
|
90
141
|
},
|
|
91
142
|
config,
|
|
92
143
|
});
|
|
93
|
-
console.log(
|
|
144
|
+
console.log(`${prefix}Generated ${genStats.totalFiles} files`);
|
|
94
145
|
log(` ${genStats.tables} table models`);
|
|
95
146
|
log(` ${genStats.customQueries} custom query operations`);
|
|
96
147
|
log(` ${genStats.customMutations} custom mutation operations`);
|
|
@@ -98,15 +149,17 @@ export async function generateOrmCommand(options = {}) {
|
|
|
98
149
|
const customMutations = customOperations.mutations.map((m) => m.name);
|
|
99
150
|
if (options.dryRun) {
|
|
100
151
|
return {
|
|
152
|
+
name: target.name,
|
|
153
|
+
output: outputDir,
|
|
101
154
|
success: true,
|
|
102
|
-
message: `Dry run complete. Would generate ${generatedFiles.length} files for ${tables.length} tables and ${stats.customQueries + stats.customMutations} custom operations
|
|
155
|
+
message: formatMessage(`Dry run complete. Would generate ${generatedFiles.length} files for ${tables.length} tables and ${stats.customQueries + stats.customMutations} custom operations.`),
|
|
103
156
|
tables: tables.map((t) => t.name),
|
|
104
157
|
customQueries,
|
|
105
158
|
customMutations,
|
|
106
159
|
filesWritten: generatedFiles.map((f) => f.path),
|
|
107
160
|
};
|
|
108
161
|
}
|
|
109
|
-
//
|
|
162
|
+
// 5. Write files
|
|
110
163
|
log('Writing files...');
|
|
111
164
|
const writeResult = await writeGeneratedFiles(generatedFiles, outputDir, [
|
|
112
165
|
'models',
|
|
@@ -115,23 +168,45 @@ export async function generateOrmCommand(options = {}) {
|
|
|
115
168
|
]);
|
|
116
169
|
if (!writeResult.success) {
|
|
117
170
|
return {
|
|
171
|
+
name: target.name,
|
|
172
|
+
output: outputDir,
|
|
118
173
|
success: false,
|
|
119
|
-
message: `Failed to write files: ${writeResult.errors?.join(', ')}
|
|
174
|
+
message: formatMessage(`Failed to write files: ${writeResult.errors?.join(', ')}`),
|
|
120
175
|
errors: writeResult.errors,
|
|
121
176
|
};
|
|
122
177
|
}
|
|
123
178
|
const totalOps = customQueries.length + customMutations.length;
|
|
124
179
|
const customOpsMsg = totalOps > 0 ? ` and ${totalOps} custom operations` : '';
|
|
125
180
|
return {
|
|
181
|
+
name: target.name,
|
|
182
|
+
output: outputDir,
|
|
126
183
|
success: true,
|
|
127
|
-
message: `Generated ORM client for ${tables.length} tables${customOpsMsg}. Files written to ${outputDir}
|
|
184
|
+
message: formatMessage(`Generated ORM client for ${tables.length} tables${customOpsMsg}. Files written to ${outputDir}`),
|
|
128
185
|
tables: tables.map((t) => t.name),
|
|
129
186
|
customQueries,
|
|
130
187
|
customMutations,
|
|
131
188
|
filesWritten: writeResult.filesWritten,
|
|
132
189
|
};
|
|
133
190
|
}
|
|
191
|
+
function buildTargetOverrides(options) {
|
|
192
|
+
const overrides = {};
|
|
193
|
+
if (options.endpoint) {
|
|
194
|
+
overrides.endpoint = options.endpoint;
|
|
195
|
+
overrides.schema = undefined;
|
|
196
|
+
}
|
|
197
|
+
if (options.schema) {
|
|
198
|
+
overrides.schema = options.schema;
|
|
199
|
+
overrides.endpoint = undefined;
|
|
200
|
+
}
|
|
201
|
+
return overrides;
|
|
202
|
+
}
|
|
134
203
|
async function loadConfig(options) {
|
|
204
|
+
if (options.endpoint && options.schema) {
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: 'Cannot use both --endpoint and --schema. Choose one source.',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
135
210
|
// Find config file
|
|
136
211
|
let configPath = options.config;
|
|
137
212
|
if (!configPath) {
|
|
@@ -145,29 +220,70 @@ async function loadConfig(options) {
|
|
|
145
220
|
}
|
|
146
221
|
baseConfig = loadResult.config;
|
|
147
222
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
223
|
+
const overrides = buildTargetOverrides(options);
|
|
224
|
+
if (isMultiConfig(baseConfig)) {
|
|
225
|
+
if (Object.keys(baseConfig.targets).length === 0) {
|
|
226
|
+
return {
|
|
227
|
+
success: false,
|
|
228
|
+
error: 'Config file defines no targets.',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (!options.target &&
|
|
232
|
+
(options.endpoint || options.schema || options.output)) {
|
|
233
|
+
return {
|
|
234
|
+
success: false,
|
|
235
|
+
error: 'Multiple targets configured. Use --target with --endpoint, --schema, or --output.',
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (options.target && !baseConfig.targets[options.target]) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
error: `Target "${options.target}" not found in config file.`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const selectedTargets = options.target
|
|
245
|
+
? { [options.target]: baseConfig.targets[options.target] }
|
|
246
|
+
: baseConfig.targets;
|
|
247
|
+
const defaults = baseConfig.defaults ?? {};
|
|
248
|
+
const resolvedTargets = [];
|
|
249
|
+
for (const [name, target] of Object.entries(selectedTargets)) {
|
|
250
|
+
let mergedTarget = mergeConfig(defaults, target);
|
|
251
|
+
if (options.target && name === options.target) {
|
|
252
|
+
mergedTarget = mergeConfig(mergedTarget, overrides);
|
|
253
|
+
}
|
|
254
|
+
if (!mergedTarget.endpoint && !mergedTarget.schema) {
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
error: `Target "${name}" is missing an endpoint or schema.`,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
resolvedTargets.push({
|
|
261
|
+
name,
|
|
262
|
+
config: resolveConfig(mergedTarget),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
success: true,
|
|
267
|
+
targets: resolvedTargets,
|
|
268
|
+
isMulti: true,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (options.target) {
|
|
272
|
+
return {
|
|
273
|
+
success: false,
|
|
274
|
+
error: 'Config file does not define targets. Remove --target to continue.',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const mergedConfig = mergeConfig(baseConfig, overrides);
|
|
164
278
|
if (!mergedConfig.endpoint && !mergedConfig.schema) {
|
|
165
279
|
return {
|
|
166
280
|
success: false,
|
|
167
281
|
error: 'No source specified. Use --endpoint or --schema, or create a config file with "graphql-codegen init".',
|
|
168
282
|
};
|
|
169
283
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
284
|
+
return {
|
|
285
|
+
success: true,
|
|
286
|
+
targets: [{ name: 'default', config: resolveConfig(mergedConfig) }],
|
|
287
|
+
isMulti: false,
|
|
288
|
+
};
|
|
173
289
|
}
|
|
@@ -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
|
+
};
|