@constructive-io/graphql-codegen 4.1.2 → 4.2.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.
|
@@ -69,8 +69,46 @@ function buildErrorCatch(errorMessage) {
|
|
|
69
69
|
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
|
|
70
70
|
]));
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Unwrap NON_NULL / LIST wrappers to get the underlying named type.
|
|
74
|
+
*/
|
|
75
|
+
function unwrapType(ref) {
|
|
76
|
+
if ((ref.kind === 'NON_NULL' || ref.kind === 'LIST') && ref.ofType) {
|
|
77
|
+
return unwrapType(ref.ofType);
|
|
78
|
+
}
|
|
79
|
+
return ref;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check if the return type (after unwrapping) is an OBJECT type.
|
|
83
|
+
*/
|
|
84
|
+
function hasObjectReturnType(returnType) {
|
|
85
|
+
const base = unwrapType(returnType);
|
|
86
|
+
return base.kind === 'OBJECT';
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build a default select string from the return type's top-level scalar fields.
|
|
90
|
+
* For OBJECT return types with known fields, generates a comma-separated list
|
|
91
|
+
* of all top-level field names (e.g. 'clientMutationId,result').
|
|
92
|
+
* Falls back to 'clientMutationId' for mutations without known fields.
|
|
93
|
+
*/
|
|
94
|
+
function buildDefaultSelectString(returnType, isMutation) {
|
|
95
|
+
const base = unwrapType(returnType);
|
|
96
|
+
if (base.fields && base.fields.length > 0) {
|
|
97
|
+
return base.fields.map((f) => f.name).join(',');
|
|
98
|
+
}
|
|
99
|
+
if (isMutation) {
|
|
100
|
+
return 'clientMutationId';
|
|
101
|
+
}
|
|
102
|
+
return '';
|
|
103
|
+
}
|
|
104
|
+
function buildOrmCustomCall(opKind, opName, argsExpr, selectExpr) {
|
|
105
|
+
const callArgs = [argsExpr];
|
|
106
|
+
if (selectExpr) {
|
|
107
|
+
callArgs.push(t.objectExpression([
|
|
108
|
+
t.objectProperty(t.identifier('select'), selectExpr),
|
|
109
|
+
]));
|
|
110
|
+
}
|
|
111
|
+
return t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.memberExpression(t.identifier('client'), t.identifier(opKind)), t.identifier(opName)), callArgs), t.identifier('execute')), []);
|
|
74
112
|
}
|
|
75
113
|
function generateCustomCommand(op, options) {
|
|
76
114
|
const commandName = (0, komoji_1.toKebabCase)(op.name);
|
|
@@ -81,8 +119,29 @@ function generateCustomCommand(op, options) {
|
|
|
81
119
|
if (options?.saveToken) {
|
|
82
120
|
imports.push('getStore');
|
|
83
121
|
}
|
|
122
|
+
// Check if any argument is an INPUT_OBJECT (i.e. takes JSON input like { input: SomeInput })
|
|
123
|
+
const hasInputObjectArg = op.args.some((arg) => {
|
|
124
|
+
const base = unwrapType(arg.type);
|
|
125
|
+
return base.kind === 'INPUT_OBJECT';
|
|
126
|
+
});
|
|
127
|
+
// Check if return type is OBJECT (needs --select flag)
|
|
128
|
+
const isObjectReturn = hasObjectReturnType(op.returnType);
|
|
129
|
+
const utilsPath = options?.executorImportPath
|
|
130
|
+
? options.executorImportPath.replace(/\/executor$/, '/utils')
|
|
131
|
+
: '../utils';
|
|
84
132
|
statements.push(createImportDeclaration('inquirerer', ['CLIOptions', 'Inquirerer']));
|
|
85
133
|
statements.push(createImportDeclaration(executorPath, imports));
|
|
134
|
+
// Build the list of utils imports needed
|
|
135
|
+
const utilsImports = [];
|
|
136
|
+
if (hasInputObjectArg) {
|
|
137
|
+
utilsImports.push('parseMutationInput');
|
|
138
|
+
}
|
|
139
|
+
if (isObjectReturn) {
|
|
140
|
+
utilsImports.push('buildSelectFromPaths');
|
|
141
|
+
}
|
|
142
|
+
if (utilsImports.length > 0) {
|
|
143
|
+
statements.push(createImportDeclaration(utilsPath, utilsImports));
|
|
144
|
+
}
|
|
86
145
|
const questionsArray = op.args.length > 0
|
|
87
146
|
? (0, arg_mapper_1.buildQuestionsArray)(op.args)
|
|
88
147
|
: t.arrayExpression([]);
|
|
@@ -111,11 +170,35 @@ function generateCustomCommand(op, options) {
|
|
|
111
170
|
bodyStatements.push(t.variableDeclaration('const', [
|
|
112
171
|
t.variableDeclarator(t.identifier('client'), t.callExpression(t.identifier('getClient'), getClientArgs)),
|
|
113
172
|
]));
|
|
173
|
+
// For mutations with INPUT_OBJECT args (like `input: SignUpInput`),
|
|
174
|
+
// parse JSON strings from CLI into proper objects
|
|
175
|
+
if (hasInputObjectArg && op.args.length > 0) {
|
|
176
|
+
bodyStatements.push(t.variableDeclaration('const', [
|
|
177
|
+
t.variableDeclarator(t.identifier('parsedAnswers'), t.callExpression(t.identifier('parseMutationInput'), [
|
|
178
|
+
t.identifier('answers'),
|
|
179
|
+
])),
|
|
180
|
+
]));
|
|
181
|
+
}
|
|
114
182
|
const argsExpr = op.args.length > 0
|
|
115
|
-
?
|
|
183
|
+
? (hasInputObjectArg
|
|
184
|
+
? t.identifier('parsedAnswers')
|
|
185
|
+
: t.identifier('answers'))
|
|
116
186
|
: t.objectExpression([]);
|
|
187
|
+
// For OBJECT return types, generate runtime select from --select flag
|
|
188
|
+
// For scalar return types, no select is needed
|
|
189
|
+
let selectExpr;
|
|
190
|
+
if (isObjectReturn) {
|
|
191
|
+
const defaultSelect = buildDefaultSelectString(op.returnType, op.kind === 'mutation');
|
|
192
|
+
// Generate: const selectFields = buildSelectFromPaths(argv.select ?? 'defaultFields')
|
|
193
|
+
bodyStatements.push(t.variableDeclaration('const', [
|
|
194
|
+
t.variableDeclarator(t.identifier('selectFields'), t.callExpression(t.identifier('buildSelectFromPaths'), [
|
|
195
|
+
t.logicalExpression('??', t.memberExpression(t.identifier('argv'), t.identifier('select')), t.stringLiteral(defaultSelect)),
|
|
196
|
+
])),
|
|
197
|
+
]));
|
|
198
|
+
selectExpr = t.identifier('selectFields');
|
|
199
|
+
}
|
|
117
200
|
bodyStatements.push(t.variableDeclaration('const', [
|
|
118
|
-
t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCustomCall(opKind, op.name, argsExpr))),
|
|
201
|
+
t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCustomCall(opKind, op.name, argsExpr, selectExpr))),
|
|
119
202
|
]));
|
|
120
203
|
if (options?.saveToken) {
|
|
121
204
|
bodyStatements.push(t.ifStatement(t.logicalExpression('&&', t.memberExpression(t.identifier('argv'), t.identifier('saveToken')), t.identifier('result')), t.blockStatement([
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This is the RUNTIME code that gets copied to generated output.
|
|
5
5
|
* Provides helpers for CLI commands: type coercion (string CLI args -> proper
|
|
6
|
-
* GraphQL types), field filtering (strip extra minimist fields),
|
|
7
|
-
* mutation input parsing.
|
|
6
|
+
* GraphQL types), field filtering (strip extra minimist fields),
|
|
7
|
+
* mutation input parsing, and select field parsing.
|
|
8
8
|
*
|
|
9
9
|
* NOTE: This file is read at codegen time and written to output.
|
|
10
10
|
* Any changes here will affect all generated CLI utils.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import objectPath from 'nested-obj';
|
|
14
|
+
|
|
13
15
|
export type FieldType =
|
|
14
16
|
| 'string'
|
|
15
17
|
| 'boolean'
|
|
@@ -142,3 +144,58 @@ export function parseMutationInput(
|
|
|
142
144
|
}
|
|
143
145
|
return answers;
|
|
144
146
|
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Build a select object from a comma-separated list of dot-notation paths.
|
|
150
|
+
* Uses `nested-obj` to parse paths like 'clientMutationId,result.accessToken,result.userId'
|
|
151
|
+
* into the nested structure expected by the ORM:
|
|
152
|
+
*
|
|
153
|
+
* { clientMutationId: true, result: { select: { accessToken: true, userId: true } } }
|
|
154
|
+
*
|
|
155
|
+
* Paths without dots set the key to `true` (scalar select).
|
|
156
|
+
* Paths with dots create nested `{ select: { ... } }` wrappers, matching the
|
|
157
|
+
* ORM's expected structure for OBJECT sub-fields (e.g. `SignUpPayloadSelect.result`).
|
|
158
|
+
*
|
|
159
|
+
* @param paths - Comma-separated dot-notation field paths (e.g. 'clientMutationId,result.accessToken')
|
|
160
|
+
* @returns The nested select object for the ORM
|
|
161
|
+
*/
|
|
162
|
+
export function buildSelectFromPaths(
|
|
163
|
+
paths: string,
|
|
164
|
+
): Record<string, unknown> {
|
|
165
|
+
const result: Record<string, unknown> = {};
|
|
166
|
+
const trimmedPaths = paths
|
|
167
|
+
.split(',')
|
|
168
|
+
.map((p) => p.trim())
|
|
169
|
+
.filter((p) => p.length > 0);
|
|
170
|
+
|
|
171
|
+
for (const path of trimmedPaths) {
|
|
172
|
+
if (!path.includes('.')) {
|
|
173
|
+
// Simple scalar field: clientMutationId -> { clientMutationId: true }
|
|
174
|
+
result[path] = true;
|
|
175
|
+
} else {
|
|
176
|
+
// Nested path: result.accessToken -> { result: { select: { accessToken: true } } }
|
|
177
|
+
// Convert dot-notation to ORM's { select: { ... } } nesting pattern
|
|
178
|
+
const parts = path.split('.');
|
|
179
|
+
let current = result;
|
|
180
|
+
for (let i = 0; i < parts.length; i++) {
|
|
181
|
+
const part = parts[i];
|
|
182
|
+
if (i === parts.length - 1) {
|
|
183
|
+
// Leaf node: set to true
|
|
184
|
+
objectPath.set(current, part, true);
|
|
185
|
+
} else {
|
|
186
|
+
// Intermediate node: ensure { select: { ... } } wrapper exists
|
|
187
|
+
if (!current[part] || typeof current[part] !== 'object') {
|
|
188
|
+
current[part] = { select: {} };
|
|
189
|
+
}
|
|
190
|
+
const wrapper = current[part] as Record<string, unknown>;
|
|
191
|
+
if (!wrapper.select || typeof wrapper.select !== 'object') {
|
|
192
|
+
wrapper.select = {};
|
|
193
|
+
}
|
|
194
|
+
current = wrapper.select as Record<string, unknown>;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
@@ -33,8 +33,46 @@ function buildErrorCatch(errorMessage) {
|
|
|
33
33
|
t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
|
|
34
34
|
]));
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Unwrap NON_NULL / LIST wrappers to get the underlying named type.
|
|
38
|
+
*/
|
|
39
|
+
function unwrapType(ref) {
|
|
40
|
+
if ((ref.kind === 'NON_NULL' || ref.kind === 'LIST') && ref.ofType) {
|
|
41
|
+
return unwrapType(ref.ofType);
|
|
42
|
+
}
|
|
43
|
+
return ref;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if the return type (after unwrapping) is an OBJECT type.
|
|
47
|
+
*/
|
|
48
|
+
function hasObjectReturnType(returnType) {
|
|
49
|
+
const base = unwrapType(returnType);
|
|
50
|
+
return base.kind === 'OBJECT';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build a default select string from the return type's top-level scalar fields.
|
|
54
|
+
* For OBJECT return types with known fields, generates a comma-separated list
|
|
55
|
+
* of all top-level field names (e.g. 'clientMutationId,result').
|
|
56
|
+
* Falls back to 'clientMutationId' for mutations without known fields.
|
|
57
|
+
*/
|
|
58
|
+
function buildDefaultSelectString(returnType, isMutation) {
|
|
59
|
+
const base = unwrapType(returnType);
|
|
60
|
+
if (base.fields && base.fields.length > 0) {
|
|
61
|
+
return base.fields.map((f) => f.name).join(',');
|
|
62
|
+
}
|
|
63
|
+
if (isMutation) {
|
|
64
|
+
return 'clientMutationId';
|
|
65
|
+
}
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
function buildOrmCustomCall(opKind, opName, argsExpr, selectExpr) {
|
|
69
|
+
const callArgs = [argsExpr];
|
|
70
|
+
if (selectExpr) {
|
|
71
|
+
callArgs.push(t.objectExpression([
|
|
72
|
+
t.objectProperty(t.identifier('select'), selectExpr),
|
|
73
|
+
]));
|
|
74
|
+
}
|
|
75
|
+
return t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.memberExpression(t.identifier('client'), t.identifier(opKind)), t.identifier(opName)), callArgs), t.identifier('execute')), []);
|
|
38
76
|
}
|
|
39
77
|
export function generateCustomCommand(op, options) {
|
|
40
78
|
const commandName = toKebabCase(op.name);
|
|
@@ -45,8 +83,29 @@ export function generateCustomCommand(op, options) {
|
|
|
45
83
|
if (options?.saveToken) {
|
|
46
84
|
imports.push('getStore');
|
|
47
85
|
}
|
|
86
|
+
// Check if any argument is an INPUT_OBJECT (i.e. takes JSON input like { input: SomeInput })
|
|
87
|
+
const hasInputObjectArg = op.args.some((arg) => {
|
|
88
|
+
const base = unwrapType(arg.type);
|
|
89
|
+
return base.kind === 'INPUT_OBJECT';
|
|
90
|
+
});
|
|
91
|
+
// Check if return type is OBJECT (needs --select flag)
|
|
92
|
+
const isObjectReturn = hasObjectReturnType(op.returnType);
|
|
93
|
+
const utilsPath = options?.executorImportPath
|
|
94
|
+
? options.executorImportPath.replace(/\/executor$/, '/utils')
|
|
95
|
+
: '../utils';
|
|
48
96
|
statements.push(createImportDeclaration('inquirerer', ['CLIOptions', 'Inquirerer']));
|
|
49
97
|
statements.push(createImportDeclaration(executorPath, imports));
|
|
98
|
+
// Build the list of utils imports needed
|
|
99
|
+
const utilsImports = [];
|
|
100
|
+
if (hasInputObjectArg) {
|
|
101
|
+
utilsImports.push('parseMutationInput');
|
|
102
|
+
}
|
|
103
|
+
if (isObjectReturn) {
|
|
104
|
+
utilsImports.push('buildSelectFromPaths');
|
|
105
|
+
}
|
|
106
|
+
if (utilsImports.length > 0) {
|
|
107
|
+
statements.push(createImportDeclaration(utilsPath, utilsImports));
|
|
108
|
+
}
|
|
50
109
|
const questionsArray = op.args.length > 0
|
|
51
110
|
? buildQuestionsArray(op.args)
|
|
52
111
|
: t.arrayExpression([]);
|
|
@@ -75,11 +134,35 @@ export function generateCustomCommand(op, options) {
|
|
|
75
134
|
bodyStatements.push(t.variableDeclaration('const', [
|
|
76
135
|
t.variableDeclarator(t.identifier('client'), t.callExpression(t.identifier('getClient'), getClientArgs)),
|
|
77
136
|
]));
|
|
137
|
+
// For mutations with INPUT_OBJECT args (like `input: SignUpInput`),
|
|
138
|
+
// parse JSON strings from CLI into proper objects
|
|
139
|
+
if (hasInputObjectArg && op.args.length > 0) {
|
|
140
|
+
bodyStatements.push(t.variableDeclaration('const', [
|
|
141
|
+
t.variableDeclarator(t.identifier('parsedAnswers'), t.callExpression(t.identifier('parseMutationInput'), [
|
|
142
|
+
t.identifier('answers'),
|
|
143
|
+
])),
|
|
144
|
+
]));
|
|
145
|
+
}
|
|
78
146
|
const argsExpr = op.args.length > 0
|
|
79
|
-
?
|
|
147
|
+
? (hasInputObjectArg
|
|
148
|
+
? t.identifier('parsedAnswers')
|
|
149
|
+
: t.identifier('answers'))
|
|
80
150
|
: t.objectExpression([]);
|
|
151
|
+
// For OBJECT return types, generate runtime select from --select flag
|
|
152
|
+
// For scalar return types, no select is needed
|
|
153
|
+
let selectExpr;
|
|
154
|
+
if (isObjectReturn) {
|
|
155
|
+
const defaultSelect = buildDefaultSelectString(op.returnType, op.kind === 'mutation');
|
|
156
|
+
// Generate: const selectFields = buildSelectFromPaths(argv.select ?? 'defaultFields')
|
|
157
|
+
bodyStatements.push(t.variableDeclaration('const', [
|
|
158
|
+
t.variableDeclarator(t.identifier('selectFields'), t.callExpression(t.identifier('buildSelectFromPaths'), [
|
|
159
|
+
t.logicalExpression('??', t.memberExpression(t.identifier('argv'), t.identifier('select')), t.stringLiteral(defaultSelect)),
|
|
160
|
+
])),
|
|
161
|
+
]));
|
|
162
|
+
selectExpr = t.identifier('selectFields');
|
|
163
|
+
}
|
|
81
164
|
bodyStatements.push(t.variableDeclaration('const', [
|
|
82
|
-
t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCustomCall(opKind, op.name, argsExpr))),
|
|
165
|
+
t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCustomCall(opKind, op.name, argsExpr, selectExpr))),
|
|
83
166
|
]));
|
|
84
167
|
if (options?.saveToken) {
|
|
85
168
|
bodyStatements.push(t.ifStatement(t.logicalExpression('&&', t.memberExpression(t.identifier('argv'), t.identifier('saveToken')), t.identifier('result')), t.blockStatement([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-codegen",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "GraphQL SDK generator for Constructive databases with React Query hooks",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"tsx": "^4.21.0",
|
|
101
101
|
"typescript": "^5.9.3"
|
|
102
102
|
},
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "b4e199d40e7495f25bafb6e8d318e731bd19b32c"
|
|
104
104
|
}
|