@constructive-io/graphql-codegen 2.17.29 → 2.17.31

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 CHANGED
@@ -84,6 +84,103 @@ Endpoint introspection:
84
84
  - Set `input.endpoint` and optional `input.headers`
85
85
  - If your dev server routes by hostname, add `headers: { Host: 'meta8.localhost' }`
86
86
 
87
+ ## Selection Options
88
+
89
+ Configure result field selections, mutation input style, and connection pagination shape.
90
+
91
+ ```ts
92
+ selection: {
93
+ defaultMutationModelFields?: string[]
94
+ modelFields?: Record<string, string[]>
95
+ mutationInputMode?: 'expanded' | 'model' | 'raw' | 'patchCollapsed'
96
+ connectionStyle?: 'nodes' | 'edges'
97
+ forceModelOutput?: boolean
98
+ }
99
+ ```
100
+
101
+ - `defaultMutationModelFields`
102
+ - Sets default fields selected from the object payload returned by mutations when the mutation exposes an OBJECT output.
103
+ - Example: `['id']` will select only the `id` from the returned model unless overridden per model.
104
+
105
+ - `modelFields`
106
+ - Per‑model overrides for returned object payload fields.
107
+ - Example: `{ domain: ['id','domain','subdomain'] }` selects those fields from the `domain` object output.
108
+
109
+ - `mutationInputMode`
110
+ - Controls how mutation variables and `input` are generated.
111
+ - `expanded`: one variable per input property; `input` is a flat object of those variables.
112
+ - `model`: one variable per property; variables are nested under the singular model key inside `input`.
113
+ - `raw`: a single `$input: <CreateXInput>!` variable passed directly as `input: $input`.
114
+ - `patchCollapsed`: a single `$patch: <ModelPatch>!` plus required locator(s) (e.g., `$id`), passed as `input: { id: $id, patch: $patch }`.
115
+
116
+ - `connectionStyle`
117
+ - Standardizes connection queries and nested many selections.
118
+ - `nodes`: emits `totalCount` and `nodes { ... }`.
119
+ - `edges`: emits `totalCount`, `pageInfo { ... }`, and `edges { cursor node { ... } }`.
120
+
121
+ - `forceModelOutput`
122
+ - When `true`, ensures the object payload is selected even if `defaultMutationModelFields` is empty, defaulting to `['id']`.
123
+ - Useful to avoid generating mutations that only return `clientMutationId`.
124
+
125
+ ### Examples
126
+
127
+ Create mutation with raw input:
128
+
129
+ ```graphql
130
+ mutation createDomain($input: CreateDomainInput!) {
131
+ createDomain(input: $input) {
132
+ domain { id, domain, subdomain }
133
+ }
134
+ }
135
+ ```
136
+
137
+ Patch mutation with collapsed patch:
138
+
139
+ ```graphql
140
+ mutation updateDomain($id: UUID!, $patch: DomainPatch!) {
141
+ updateDomain(input: { id: $id, patch: $patch }) {
142
+ domain { id }
143
+ clientMutationId
144
+ }
145
+ }
146
+ ```
147
+
148
+ Edges‑style connection query:
149
+
150
+ ```graphql
151
+ query getDomainsPaginated($first: Int, $after: Cursor) {
152
+ domains(first: $first, after: $after) {
153
+ totalCount
154
+ pageInfo { hasNextPage hasPreviousPage endCursor startCursor }
155
+ edges { cursor node { id domain subdomain } }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## Custom Scalars and Type Overrides
161
+
162
+ - When your schema exposes custom scalars that are not available in the printed SDL or differ across environments, you can configure both TypeScript scalar typings and GraphQL type names used in generated operations.
163
+
164
+ - Add these to your config object:
165
+
166
+ ```json
167
+ {
168
+ "scalars": {
169
+ "LaunchqlInternalTypeHostname": "string",
170
+ "PgpmInternalTypeHostname": "string"
171
+ },
172
+ "typeNameOverrides": {
173
+ "LaunchqlInternalTypeHostname": "String",
174
+ "PgpmInternalTypeHostname": "String"
175
+ }
176
+ }
177
+ ```
178
+
179
+ - `scalars`: maps GraphQL scalar names to TypeScript types for `typescript`/`typescript-operations`/`typescript-graphql-request`/`typescript-react-query` plugins.
180
+ - `typeNameOverrides`: rewrites scalar names in generated GraphQL AST so variable definitions and input fields use compatible built‑in GraphQL types.
181
+
182
+ - These options are also available programmatically through `LaunchQLGenOptions`.
183
+
87
184
  ---
88
185
 
89
186
  ## Education and Tutorials
package/codegen.js CHANGED
@@ -97,8 +97,8 @@ async function getIntrospectionFromEndpoint(endpoint, headers) {
97
97
  const res = await client.request(q);
98
98
  return res;
99
99
  }
100
- function generateKeyedObjFromGqlMap(gqlMap) {
101
- const gen = (0, gql_1.generate)(gqlMap);
100
+ function generateKeyedObjFromGqlMap(gqlMap, selection, typeNameOverrides, typeIndex) {
101
+ const gen = (0, gql_1.generate)(gqlMap, selection, typeNameOverrides, typeIndex);
102
102
  return Object.entries(gen).reduce((acc, [key, val]) => {
103
103
  if (val?.ast)
104
104
  acc[key] = (0, graphql_1.print)(val.ast);
@@ -134,11 +134,12 @@ async function writeOperationsDocuments(docs, dir, format, convention) {
134
134
  await ensureDir(dir);
135
135
  const index = [];
136
136
  for (const key of Object.keys(docs)) {
137
- const filename = getFilename(key, convention) + (format === 'ts' ? '.ts' : '.gql');
137
+ const base = getFilename(key, convention);
138
+ const filename = base + (format === 'ts' ? '.ts' : '.gql');
138
139
  if (format === 'ts') {
139
140
  const code = `import gql from 'graphql-tag'\nexport const ${key} = gql\`\n${docs[key]}\n\``;
140
141
  await writeFileUTF8((0, path_1.join)(dir, filename), code);
141
- index.push(`export * from './${filename}'`);
142
+ index.push(`export * from './${base}'`);
142
143
  }
143
144
  else {
144
145
  await writeFileUTF8((0, path_1.join)(dir, filename), docs[key]);
@@ -152,7 +153,10 @@ async function runCodegen(opts, cwd) {
152
153
  input: { ...(options_1.defaultGraphQLCodegenOptions.input), ...(opts.input || {}) },
153
154
  output: { ...(options_1.defaultGraphQLCodegenOptions.output), ...(opts.output || {}) },
154
155
  documents: { ...(options_1.defaultGraphQLCodegenOptions.documents), ...(opts.documents || {}) },
155
- features: { ...(options_1.defaultGraphQLCodegenOptions.features), ...(opts.features || {}) }
156
+ features: { ...(options_1.defaultGraphQLCodegenOptions.features), ...(opts.features || {}) },
157
+ selection: { ...(options_1.defaultGraphQLCodegenOptions.selection), ...(opts.selection || {}) },
158
+ scalars: { ...(options_1.defaultGraphQLCodegenOptions.scalars || {}), ...(opts.scalars || {}) },
159
+ typeNameOverrides: { ...(options_1.defaultGraphQLCodegenOptions.typeNameOverrides || {}), ...(opts.typeNameOverrides || {}) }
156
160
  };
157
161
  const root = (0, path_1.join)(cwd, options.output.root);
158
162
  const typesFile = (0, path_1.join)(root, options.output.typesFile);
@@ -171,8 +175,9 @@ async function runCodegen(opts, cwd) {
171
175
  const schema = hasEndpoint
172
176
  ? (0, graphql_1.buildClientSchema)(introspection)
173
177
  : (0, graphql_1.buildSchema)(await readFileUTF8(schemaPath));
178
+ const typeIndex = buildTypeIndex(introspection);
174
179
  if (options.features.emitOperations || options.features.emitSdk || options.features.emitReactQuery) {
175
- docs = generateKeyedObjFromGqlMap(gqlMap);
180
+ docs = generateKeyedObjFromGqlMap(gqlMap, options.selection, options.typeNameOverrides, typeIndex);
176
181
  }
177
182
  if (options.features.emitOperations) {
178
183
  await writeOperationsDocuments(docs, operationsDir, options.documents.format, options.documents.convention);
@@ -182,7 +187,7 @@ async function runCodegen(opts, cwd) {
182
187
  filename: typesFile,
183
188
  schema: schema,
184
189
  documents: [],
185
- config: {},
190
+ config: { scalars: options.scalars || {} },
186
191
  plugins: [{ typescript: {} }],
187
192
  pluginMap: { typescript: typescriptPlugin }
188
193
  });
@@ -201,7 +206,7 @@ async function runCodegen(opts, cwd) {
201
206
  filename: sdkFile,
202
207
  schema: schema,
203
208
  documents,
204
- config: {},
209
+ config: { scalars: options.scalars || {} },
205
210
  plugins: [
206
211
  { typescript: {} },
207
212
  { 'typescript-operations': {} },
@@ -230,7 +235,9 @@ async function runCodegen(opts, cwd) {
230
235
  fetcher: options.reactQuery?.fetcher || 'graphql-request',
231
236
  legacyMode: options.reactQuery?.legacyMode || false,
232
237
  exposeDocument: options.reactQuery?.exposeDocument || false,
233
- addInfiniteQuery: options.reactQuery?.addInfiniteQuery || false
238
+ addInfiniteQuery: options.reactQuery?.addInfiniteQuery || false,
239
+ reactQueryVersion: options.reactQuery?.reactQueryVersion || 5,
240
+ scalars: options.scalars || {}
234
241
  };
235
242
  const rqContent = await (0, core_1.codegen)({
236
243
  filename: reactQueryFile,
@@ -265,3 +272,22 @@ async function runCodegenFromJSONConfig(configPath, cwd) {
265
272
  const merged = (0, options_1.mergeGraphQLCodegenOptions)(options_1.defaultGraphQLCodegenOptions, overrides);
266
273
  return runCodegen(merged, cwd);
267
274
  }
275
+ function buildTypeIndex(introspection) {
276
+ const byName = {};
277
+ const types = (introspection && introspection.__schema && introspection.__schema.types) || [];
278
+ for (const t of types) {
279
+ if (t && typeof t.name === 'string' && t.name.length > 0)
280
+ byName[t.name] = t;
281
+ }
282
+ return {
283
+ byName,
284
+ getInputFieldType(typeName, fieldName) {
285
+ const typ = byName[typeName];
286
+ if (!typ || typ.kind !== 'INPUT_OBJECT')
287
+ return null;
288
+ const fields = typ.inputFields || [];
289
+ const f = fields.find((x) => x && x.name === fieldName);
290
+ return f ? f.type : null;
291
+ }
292
+ };
293
+ }
package/esm/codegen.js CHANGED
@@ -57,8 +57,8 @@ async function getIntrospectionFromEndpoint(endpoint, headers) {
57
57
  const res = await client.request(q);
58
58
  return res;
59
59
  }
60
- function generateKeyedObjFromGqlMap(gqlMap) {
61
- const gen = generateGql(gqlMap);
60
+ function generateKeyedObjFromGqlMap(gqlMap, selection, typeNameOverrides, typeIndex) {
61
+ const gen = generateGql(gqlMap, selection, typeNameOverrides, typeIndex);
62
62
  return Object.entries(gen).reduce((acc, [key, val]) => {
63
63
  if (val?.ast)
64
64
  acc[key] = print(val.ast);
@@ -94,11 +94,12 @@ async function writeOperationsDocuments(docs, dir, format, convention) {
94
94
  await ensureDir(dir);
95
95
  const index = [];
96
96
  for (const key of Object.keys(docs)) {
97
- const filename = getFilename(key, convention) + (format === 'ts' ? '.ts' : '.gql');
97
+ const base = getFilename(key, convention);
98
+ const filename = base + (format === 'ts' ? '.ts' : '.gql');
98
99
  if (format === 'ts') {
99
100
  const code = `import gql from 'graphql-tag'\nexport const ${key} = gql\`\n${docs[key]}\n\``;
100
101
  await writeFileUTF8(join(dir, filename), code);
101
- index.push(`export * from './${filename}'`);
102
+ index.push(`export * from './${base}'`);
102
103
  }
103
104
  else {
104
105
  await writeFileUTF8(join(dir, filename), docs[key]);
@@ -112,7 +113,10 @@ export async function runCodegen(opts, cwd) {
112
113
  input: { ...(defaultGraphQLCodegenOptions.input), ...(opts.input || {}) },
113
114
  output: { ...(defaultGraphQLCodegenOptions.output), ...(opts.output || {}) },
114
115
  documents: { ...(defaultGraphQLCodegenOptions.documents), ...(opts.documents || {}) },
115
- features: { ...(defaultGraphQLCodegenOptions.features), ...(opts.features || {}) }
116
+ features: { ...(defaultGraphQLCodegenOptions.features), ...(opts.features || {}) },
117
+ selection: { ...(defaultGraphQLCodegenOptions.selection), ...(opts.selection || {}) },
118
+ scalars: { ...(defaultGraphQLCodegenOptions.scalars || {}), ...(opts.scalars || {}) },
119
+ typeNameOverrides: { ...(defaultGraphQLCodegenOptions.typeNameOverrides || {}), ...(opts.typeNameOverrides || {}) }
116
120
  };
117
121
  const root = join(cwd, options.output.root);
118
122
  const typesFile = join(root, options.output.typesFile);
@@ -131,8 +135,9 @@ export async function runCodegen(opts, cwd) {
131
135
  const schema = hasEndpoint
132
136
  ? buildClientSchema(introspection)
133
137
  : buildSchema(await readFileUTF8(schemaPath));
138
+ const typeIndex = buildTypeIndex(introspection);
134
139
  if (options.features.emitOperations || options.features.emitSdk || options.features.emitReactQuery) {
135
- docs = generateKeyedObjFromGqlMap(gqlMap);
140
+ docs = generateKeyedObjFromGqlMap(gqlMap, options.selection, options.typeNameOverrides, typeIndex);
136
141
  }
137
142
  if (options.features.emitOperations) {
138
143
  await writeOperationsDocuments(docs, operationsDir, options.documents.format, options.documents.convention);
@@ -142,7 +147,7 @@ export async function runCodegen(opts, cwd) {
142
147
  filename: typesFile,
143
148
  schema: schema,
144
149
  documents: [],
145
- config: {},
150
+ config: { scalars: options.scalars || {} },
146
151
  plugins: [{ typescript: {} }],
147
152
  pluginMap: { typescript: typescriptPlugin }
148
153
  });
@@ -161,7 +166,7 @@ export async function runCodegen(opts, cwd) {
161
166
  filename: sdkFile,
162
167
  schema: schema,
163
168
  documents,
164
- config: {},
169
+ config: { scalars: options.scalars || {} },
165
170
  plugins: [
166
171
  { typescript: {} },
167
172
  { 'typescript-operations': {} },
@@ -190,7 +195,9 @@ export async function runCodegen(opts, cwd) {
190
195
  fetcher: options.reactQuery?.fetcher || 'graphql-request',
191
196
  legacyMode: options.reactQuery?.legacyMode || false,
192
197
  exposeDocument: options.reactQuery?.exposeDocument || false,
193
- addInfiniteQuery: options.reactQuery?.addInfiniteQuery || false
198
+ addInfiniteQuery: options.reactQuery?.addInfiniteQuery || false,
199
+ reactQueryVersion: options.reactQuery?.reactQueryVersion || 5,
200
+ scalars: options.scalars || {}
194
201
  };
195
202
  const rqContent = await runCoreCodegen({
196
203
  filename: reactQueryFile,
@@ -225,3 +232,22 @@ export async function runCodegenFromJSONConfig(configPath, cwd) {
225
232
  const merged = mergeGraphQLCodegenOptions(defaultGraphQLCodegenOptions, overrides);
226
233
  return runCodegen(merged, cwd);
227
234
  }
235
+ function buildTypeIndex(introspection) {
236
+ const byName = {};
237
+ const types = (introspection && introspection.__schema && introspection.__schema.types) || [];
238
+ for (const t of types) {
239
+ if (t && typeof t.name === 'string' && t.name.length > 0)
240
+ byName[t.name] = t;
241
+ }
242
+ return {
243
+ byName,
244
+ getInputFieldType(typeName, fieldName) {
245
+ const typ = byName[typeName];
246
+ if (!typ || typ.kind !== 'INPUT_OBJECT')
247
+ return null;
248
+ const fields = typ.inputFields || [];
249
+ const f = fields.find((x) => x && x.name === fieldName);
250
+ return f ? f.type : null;
251
+ }
252
+ };
253
+ }