@constructive-io/graphql-codegen 4.1.3 → 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.
@@ -79,30 +79,36 @@ function unwrapType(ref) {
79
79
  return ref;
80
80
  }
81
81
  /**
82
- * Build a select object expression from return-type fields.
83
- * If the return type has known fields, generates { field1: true, field2: true, ... }.
84
- * Falls back to { clientMutationId: true } for mutations without known fields.
82
+ * Check if the return type (after unwrapping) is an OBJECT type.
85
83
  */
86
- function buildSelectObject(returnType, isMutation) {
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) {
87
95
  const base = unwrapType(returnType);
88
96
  if (base.fields && base.fields.length > 0) {
89
- return t.objectExpression(base.fields.map((f) => t.objectProperty(t.identifier(f.name), t.booleanLiteral(true))));
97
+ return base.fields.map((f) => f.name).join(',');
90
98
  }
91
- // Fallback: all PostGraphile mutation payloads have clientMutationId
92
99
  if (isMutation) {
93
- return t.objectExpression([
94
- t.objectProperty(t.identifier('clientMutationId'), t.booleanLiteral(true)),
95
- ]);
100
+ return 'clientMutationId';
96
101
  }
97
- return t.objectExpression([]);
102
+ return '';
98
103
  }
99
104
  function buildOrmCustomCall(opKind, opName, argsExpr, selectExpr) {
100
- return t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.memberExpression(t.identifier('client'), t.identifier(opKind)), t.identifier(opName)), [
101
- argsExpr,
102
- t.objectExpression([
105
+ const callArgs = [argsExpr];
106
+ if (selectExpr) {
107
+ callArgs.push(t.objectExpression([
103
108
  t.objectProperty(t.identifier('select'), selectExpr),
104
- ]),
105
- ]), t.identifier('execute')), []);
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')), []);
106
112
  }
107
113
  function generateCustomCommand(op, options) {
108
114
  const commandName = (0, komoji_1.toKebabCase)(op.name);
@@ -118,13 +124,23 @@ function generateCustomCommand(op, options) {
118
124
  const base = unwrapType(arg.type);
119
125
  return base.kind === 'INPUT_OBJECT';
120
126
  });
127
+ // Check if return type is OBJECT (needs --select flag)
128
+ const isObjectReturn = hasObjectReturnType(op.returnType);
121
129
  const utilsPath = options?.executorImportPath
122
130
  ? options.executorImportPath.replace(/\/executor$/, '/utils')
123
131
  : '../utils';
124
132
  statements.push(createImportDeclaration('inquirerer', ['CLIOptions', 'Inquirerer']));
125
133
  statements.push(createImportDeclaration(executorPath, imports));
134
+ // Build the list of utils imports needed
135
+ const utilsImports = [];
126
136
  if (hasInputObjectArg) {
127
- statements.push(createImportDeclaration(utilsPath, ['parseMutationInput']));
137
+ utilsImports.push('parseMutationInput');
138
+ }
139
+ if (isObjectReturn) {
140
+ utilsImports.push('buildSelectFromPaths');
141
+ }
142
+ if (utilsImports.length > 0) {
143
+ statements.push(createImportDeclaration(utilsPath, utilsImports));
128
144
  }
129
145
  const questionsArray = op.args.length > 0
130
146
  ? (0, arg_mapper_1.buildQuestionsArray)(op.args)
@@ -168,7 +184,19 @@ function generateCustomCommand(op, options) {
168
184
  ? t.identifier('parsedAnswers')
169
185
  : t.identifier('answers'))
170
186
  : t.objectExpression([]);
171
- const selectExpr = buildSelectObject(op.returnType, op.kind === 'mutation');
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
+ }
172
200
  bodyStatements.push(t.variableDeclaration('const', [
173
201
  t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCustomCall(opKind, op.name, argsExpr, selectExpr))),
174
202
  ]));
@@ -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), and
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
+ }
@@ -43,30 +43,36 @@ function unwrapType(ref) {
43
43
  return ref;
44
44
  }
45
45
  /**
46
- * Build a select object expression from return-type fields.
47
- * If the return type has known fields, generates { field1: true, field2: true, ... }.
48
- * Falls back to { clientMutationId: true } for mutations without known fields.
46
+ * Check if the return type (after unwrapping) is an OBJECT type.
49
47
  */
50
- function buildSelectObject(returnType, isMutation) {
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) {
51
59
  const base = unwrapType(returnType);
52
60
  if (base.fields && base.fields.length > 0) {
53
- return t.objectExpression(base.fields.map((f) => t.objectProperty(t.identifier(f.name), t.booleanLiteral(true))));
61
+ return base.fields.map((f) => f.name).join(',');
54
62
  }
55
- // Fallback: all PostGraphile mutation payloads have clientMutationId
56
63
  if (isMutation) {
57
- return t.objectExpression([
58
- t.objectProperty(t.identifier('clientMutationId'), t.booleanLiteral(true)),
59
- ]);
64
+ return 'clientMutationId';
60
65
  }
61
- return t.objectExpression([]);
66
+ return '';
62
67
  }
63
68
  function buildOrmCustomCall(opKind, opName, argsExpr, selectExpr) {
64
- return t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.memberExpression(t.identifier('client'), t.identifier(opKind)), t.identifier(opName)), [
65
- argsExpr,
66
- t.objectExpression([
69
+ const callArgs = [argsExpr];
70
+ if (selectExpr) {
71
+ callArgs.push(t.objectExpression([
67
72
  t.objectProperty(t.identifier('select'), selectExpr),
68
- ]),
69
- ]), t.identifier('execute')), []);
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')), []);
70
76
  }
71
77
  export function generateCustomCommand(op, options) {
72
78
  const commandName = toKebabCase(op.name);
@@ -82,13 +88,23 @@ export function generateCustomCommand(op, options) {
82
88
  const base = unwrapType(arg.type);
83
89
  return base.kind === 'INPUT_OBJECT';
84
90
  });
91
+ // Check if return type is OBJECT (needs --select flag)
92
+ const isObjectReturn = hasObjectReturnType(op.returnType);
85
93
  const utilsPath = options?.executorImportPath
86
94
  ? options.executorImportPath.replace(/\/executor$/, '/utils')
87
95
  : '../utils';
88
96
  statements.push(createImportDeclaration('inquirerer', ['CLIOptions', 'Inquirerer']));
89
97
  statements.push(createImportDeclaration(executorPath, imports));
98
+ // Build the list of utils imports needed
99
+ const utilsImports = [];
90
100
  if (hasInputObjectArg) {
91
- statements.push(createImportDeclaration(utilsPath, ['parseMutationInput']));
101
+ utilsImports.push('parseMutationInput');
102
+ }
103
+ if (isObjectReturn) {
104
+ utilsImports.push('buildSelectFromPaths');
105
+ }
106
+ if (utilsImports.length > 0) {
107
+ statements.push(createImportDeclaration(utilsPath, utilsImports));
92
108
  }
93
109
  const questionsArray = op.args.length > 0
94
110
  ? buildQuestionsArray(op.args)
@@ -132,7 +148,19 @@ export function generateCustomCommand(op, options) {
132
148
  ? t.identifier('parsedAnswers')
133
149
  : t.identifier('answers'))
134
150
  : t.objectExpression([]);
135
- const selectExpr = buildSelectObject(op.returnType, op.kind === 'mutation');
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
+ }
136
164
  bodyStatements.push(t.variableDeclaration('const', [
137
165
  t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCustomCall(opKind, op.name, argsExpr, selectExpr))),
138
166
  ]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-codegen",
3
- "version": "4.1.3",
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": "f80e2e5e112cab91eac53e61c5b257f4ac88efb2"
103
+ "gitHead": "b4e199d40e7495f25bafb6e8d318e731bd19b32c"
104
104
  }