@graphql-codegen/typescript-operations 6.0.0-alpha-20251124142004-67952cd52352f89ebcdb97bfca7c34bc89fbacc3 → 6.0.0-alpha-20251224115216-0c4a535bdb152e75b9296d4c259f7dad0a13158f

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/cjs/index.js CHANGED
@@ -25,11 +25,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.TypeScriptDocumentsVisitor = exports.plugin = void 0;
27
27
  const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers");
28
+ const schema_ast_1 = require("@graphql-codegen/schema-ast");
28
29
  const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
29
30
  const graphql_1 = require("graphql");
30
31
  const visitor_js_1 = require("./visitor.js");
31
32
  Object.defineProperty(exports, "TypeScriptDocumentsVisitor", { enumerable: true, get: function () { return visitor_js_1.TypeScriptDocumentsVisitor; } });
32
- const plugin = async (inputSchema, rawDocuments, config) => {
33
+ const plugin = async (inputSchema, rawDocuments, config, { outputFile }) => {
33
34
  const schema = config.nullability?.errorHandlingClient ? await semanticToStrict(inputSchema) : inputSchema;
34
35
  const documents = config.flattenGeneratedTypes
35
36
  ? (0, visitor_plugin_common_1.optimizeOperations)(schema, rawDocuments, {
@@ -37,29 +38,23 @@ const plugin = async (inputSchema, rawDocuments, config) => {
37
38
  })
38
39
  : rawDocuments;
39
40
  const allAst = (0, graphql_1.concatAST)(documents.map(v => v.document));
40
- const allFragments = [
41
- ...allAst.definitions.filter(d => d.kind === graphql_1.Kind.FRAGMENT_DEFINITION).map(fragmentDef => ({
42
- node: fragmentDef,
43
- name: fragmentDef.name.value,
44
- onType: fragmentDef.typeCondition.name.value,
45
- isExternal: false,
46
- })),
47
- ...(config.externalFragments || []),
48
- ];
49
- const visitor = new visitor_js_1.TypeScriptDocumentsVisitor(schema, config, allFragments);
50
- const visitorResult = (0, plugin_helpers_1.oldVisit)(allAst, {
51
- leave: visitor,
52
- });
53
- let content = visitorResult.definitions.join('\n');
41
+ const visitor = new visitor_js_1.TypeScriptDocumentsVisitor(schema, config, allAst, outputFile);
42
+ const operationsResult = (0, plugin_helpers_1.oldVisit)(allAst, { leave: visitor });
43
+ const operationsDefinitions = operationsResult.definitions;
54
44
  if (config.addOperationExport) {
55
- const exportConsts = [];
56
45
  for (const d of allAst.definitions) {
57
46
  if ('name' in d) {
58
- exportConsts.push(`export declare const ${d.name.value}: import("graphql").DocumentNode;`);
47
+ operationsDefinitions.push(`export declare const ${d.name.value}: import("graphql").DocumentNode;`);
59
48
  }
60
49
  }
61
- content = visitorResult.definitions.concat(exportConsts).join('\n');
62
50
  }
51
+ const schemaTypes = (0, plugin_helpers_1.oldVisit)((0, schema_ast_1.transformSchemaAST)(schema, config).ast, { leave: visitor });
52
+ // IMPORTANT: when a visitor leaves a node with no transformation logic,
53
+ // It will leave the node as an object.
54
+ // Here, we filter in nodes that have been turned into strings, i.e. they have been transformed
55
+ // This way, we do not have to explicitly declare a method for every node type to convert them to null
56
+ const schemaTypesDefinitions = schemaTypes.definitions.filter(def => typeof def === 'string');
57
+ let content = [...schemaTypesDefinitions, ...operationsDefinitions].join('\n');
63
58
  if (config.globalNamespace) {
64
59
  content = `
65
60
  declare global {
@@ -69,8 +64,11 @@ const plugin = async (inputSchema, rawDocuments, config) => {
69
64
  return {
70
65
  prepend: [
71
66
  ...visitor.getImports(),
67
+ ...visitor.getExternalSchemaTypeImports(),
68
+ ...visitor.getEnumsImports(),
72
69
  ...visitor.getGlobalDeclarations(visitor.config.noExport),
73
- 'type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };',
70
+ visitor.getExactUtilityType(),
71
+ visitor.getIncrementalUtilityType(),
74
72
  ],
75
73
  content,
76
74
  };
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TypeScriptOperationVariablesToObject = void 0;
3
+ exports.TypeScriptOperationVariablesToObject = exports.SCALARS = void 0;
4
4
  const typescript_1 = require("@graphql-codegen/typescript");
5
- const SCALARS = {
5
+ exports.SCALARS = {
6
6
  ID: 'string | number',
7
7
  String: 'string',
8
8
  Int: 'number',
@@ -24,7 +24,7 @@ class TypeScriptOperationVariablesToObject extends typescript_1.TypeScriptOperat
24
24
  return type?.endsWith(MAYBE_SUFFIX) ? type : `${type}${MAYBE_SUFFIX}`;
25
25
  }
26
26
  getScalar(name) {
27
- return this._scalars?.[name]?.input ?? SCALARS[name] ?? 'any';
27
+ return this._scalars?.[name]?.input ?? exports.SCALARS[name] ?? 'any';
28
28
  }
29
29
  }
30
30
  exports.TypeScriptOperationVariablesToObject = TypeScriptOperationVariablesToObject;
@@ -26,15 +26,7 @@ class TypeScriptSelectionSetProcessor extends visitor_plugin_common_1.BaseSelect
26
26
  });
27
27
  let resString = formattedUnionTransform('Pick', parentName, escapedFieldNames);
28
28
  if (hasConditionals) {
29
- const avoidOptional =
30
- // TODO: check type and exec only if relevant
31
- this.config.avoidOptionals === true ||
32
- (typeof this.config.avoidOptionals === 'object' &&
33
- (this.config.avoidOptionals.field ||
34
- this.config.avoidOptionals.inputValue ||
35
- this.config.avoidOptionals.object));
36
- const transform = avoidOptional ? 'MakeMaybe' : 'MakeOptional';
37
- resString = `${this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : ''}${formattedUnionTransform(transform, resString, escapedConditionalsList)}`;
29
+ resString = `${this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : ''}${formattedUnionTransform('MakeOptional', resString, escapedConditionalsList)}`;
38
30
  }
39
31
  return [resString];
40
32
  }
package/cjs/visitor.js CHANGED
@@ -8,7 +8,9 @@ const graphql_1 = require("graphql");
8
8
  const ts_operation_variables_to_object_js_1 = require("./ts-operation-variables-to-object.js");
9
9
  const ts_selection_set_processor_js_1 = require("./ts-selection-set-processor.js");
10
10
  class TypeScriptDocumentsVisitor extends visitor_plugin_common_1.BaseDocumentsVisitor {
11
- constructor(schema, config, allFragments) {
11
+ _usedNamedInputTypes = {};
12
+ _outputPath;
13
+ constructor(schema, config, documentNode, outputPath) {
12
14
  super(config, {
13
15
  arrayInputCoercion: (0, visitor_plugin_common_1.getConfigValue)(config.arrayInputCoercion, true),
14
16
  noExport: (0, visitor_plugin_common_1.getConfigValue)(config.noExport, false),
@@ -18,7 +20,15 @@ class TypeScriptDocumentsVisitor extends visitor_plugin_common_1.BaseDocumentsVi
18
20
  preResolveTypes: (0, visitor_plugin_common_1.getConfigValue)(config.preResolveTypes, true),
19
21
  mergeFragmentTypes: (0, visitor_plugin_common_1.getConfigValue)(config.mergeFragmentTypes, false),
20
22
  allowUndefinedQueryVariables: (0, visitor_plugin_common_1.getConfigValue)(config.allowUndefinedQueryVariables, false),
23
+ enumType: (0, visitor_plugin_common_1.getConfigValue)(config.enumType, 'string-literal'),
24
+ enumValues: (0, visitor_plugin_common_1.parseEnumValues)({
25
+ schema,
26
+ mapOrStr: config.enumValues,
27
+ ignoreEnumValuesFromSchema: config.ignoreEnumValuesFromSchema,
28
+ }),
29
+ futureProofEnums: (0, visitor_plugin_common_1.getConfigValue)(config.futureProofEnums, false),
21
30
  }, schema);
31
+ this._outputPath = outputPath;
22
32
  (0, auto_bind_1.default)(this);
23
33
  const preResolveTypes = (0, visitor_plugin_common_1.getConfigValue)(config.preResolveTypes, true);
24
34
  const defaultMaybeValue = 'T | null';
@@ -34,44 +44,349 @@ class TypeScriptDocumentsVisitor extends visitor_plugin_common_1.BaseDocumentsVi
34
44
  const listModifier = this.config.immutableTypes ? 'ReadonlyArray' : 'Array';
35
45
  return `${listModifier}<${type}>`;
36
46
  };
37
- const formatNamedField = (name, type, isConditional = false, isOptional = false) => {
38
- const optional = isOptional || isConditional || (!this.config.avoidOptionals.field && !!type && !(0, graphql_1.isNonNullType)(type));
39
- return (this.config.immutableTypes ? `readonly ${name}` : name) + (optional ? '?' : '');
40
- };
47
+ const allFragments = [
48
+ ...documentNode.definitions.filter(d => d.kind === graphql_1.Kind.FRAGMENT_DEFINITION).map(fragmentDef => ({
49
+ node: fragmentDef,
50
+ name: fragmentDef.name.value,
51
+ onType: fragmentDef.typeCondition.name.value,
52
+ isExternal: false,
53
+ })),
54
+ ...(config.externalFragments || []),
55
+ ];
56
+ this._usedNamedInputTypes = this.collectUsedInputTypes({ schema, documentNode });
41
57
  const processorConfig = {
42
58
  namespacedImportName: this.config.namespacedImportName,
43
59
  convertName: this.convertName.bind(this),
44
60
  enumPrefix: this.config.enumPrefix,
45
61
  enumSuffix: this.config.enumSuffix,
46
62
  scalars: this.scalars,
47
- formatNamedField,
63
+ formatNamedField: ({ name, isOptional }) => {
64
+ return (this.config.immutableTypes ? `readonly ${name}` : name) + (isOptional ? '?' : '');
65
+ },
48
66
  wrapTypeWithModifiers(baseType, type) {
49
67
  return (0, visitor_plugin_common_1.wrapTypeWithModifiers)(baseType, type, { wrapOptional, wrapArray });
50
68
  },
51
- avoidOptionals: this.config.avoidOptionals,
52
69
  printFieldsOnNewLines: this.config.printFieldsOnNewLines,
53
70
  };
54
71
  const processor = new (preResolveTypes ? visitor_plugin_common_1.PreResolveTypesProcessor : ts_selection_set_processor_js_1.TypeScriptSelectionSetProcessor)(processorConfig);
55
72
  this.setSelectionSetHandler(new visitor_plugin_common_1.SelectionSetToObject(processor, this.scalars, this.schema, this.convertName.bind(this), this.getFragmentSuffix.bind(this), allFragments, this.config));
56
73
  const enumsNames = Object.keys(schema.getTypeMap()).filter(typeName => (0, graphql_1.isEnumType)(schema.getType(typeName)));
57
- this.setVariablesTransformer(new ts_operation_variables_to_object_js_1.TypeScriptOperationVariablesToObject(this.scalars, this.convertName.bind(this), this.config.avoidOptionals, this.config.immutableTypes, this.config.namespacedImportName, enumsNames, this.config.enumPrefix, this.config.enumSuffix, this.config.enumValues, this.config.arrayInputCoercion, undefined, 'InputMaybe'));
74
+ this.setVariablesTransformer(new ts_operation_variables_to_object_js_1.TypeScriptOperationVariablesToObject(this.scalars, this.convertName.bind(this),
75
+ // FIXME: this is the legacy avoidOptionals which was used to make Result fields non-optional. This use case is no longer valid.
76
+ // It's also being used for Variables so people could already be using it.
77
+ // Maybe it's better to deprecate and remove, to see what users think.
78
+ this.config.avoidOptionals, this.config.immutableTypes, this.config.namespacedImportName, enumsNames, this.config.enumPrefix, this.config.enumSuffix, this.config.enumValues, this.config.arrayInputCoercion, undefined, undefined));
58
79
  this._declarationBlockConfig = {
59
80
  ignoreExport: this.config.noExport,
81
+ enumNameValueSeparator: ' =',
60
82
  };
61
83
  }
84
+ EnumTypeDefinition(node) {
85
+ const enumName = node.name.value;
86
+ if (!this._usedNamedInputTypes[enumName] || this.config.importSchemaTypesFrom) {
87
+ return null;
88
+ }
89
+ return (0, visitor_plugin_common_1.convertSchemaEnumToDeclarationBlockString)({
90
+ schema: this._schema,
91
+ node,
92
+ declarationBlockConfig: this._declarationBlockConfig,
93
+ enumName,
94
+ enumValues: this.config.enumValues,
95
+ futureProofEnums: this.config.futureProofEnums,
96
+ ignoreEnumValuesFromSchema: this.config.ignoreEnumValuesFromSchema,
97
+ outputType: this.config.enumType,
98
+ naming: {
99
+ convert: this.config.convert,
100
+ typesPrefix: this.config.typesPrefix,
101
+ typesSuffix: this.config.typesSuffix,
102
+ useTypesPrefix: this.config.enumPrefix,
103
+ useTypesSuffix: this.config.enumSuffix,
104
+ },
105
+ });
106
+ }
107
+ InputObjectTypeDefinition(node) {
108
+ const inputTypeName = node.name.value;
109
+ if (!this._usedNamedInputTypes[inputTypeName]) {
110
+ return null;
111
+ }
112
+ if ((0, visitor_plugin_common_1.isOneOfInputObjectType)(this._schema.getType(inputTypeName))) {
113
+ return new visitor_plugin_common_1.DeclarationBlock(this._declarationBlockConfig)
114
+ .asKind('type')
115
+ .withName(this.convertName(node))
116
+ .withComment(node.description?.value)
117
+ .withContent(`\n` + (node.fields || []).join('\n |')).string;
118
+ }
119
+ return new visitor_plugin_common_1.DeclarationBlock(this._declarationBlockConfig)
120
+ .asKind('type')
121
+ .withName(this.convertName(node))
122
+ .withComment(node.description?.value)
123
+ .withBlock((node.fields || []).join('\n')).string;
124
+ }
125
+ InputValueDefinition(node, _key, _parent, _path, ancestors) {
126
+ const oneOfDetails = parseOneOfInputValue({
127
+ node,
128
+ schema: this._schema,
129
+ ancestors,
130
+ });
131
+ // 1. Flatten GraphQL type nodes to make it easier to turn into string
132
+ // GraphQL type nodes may have `NonNullType` type before each `ListType` or `NamedType`
133
+ // This make it a bit harder to know whether a `ListType` or `Namedtype` is nullable without looking at the node before it.
134
+ // Flattening it into an array where the nullability is in `ListType` and `NamedType` makes it easier to code,
135
+ //
136
+ // So, we recursively call `collectAndFlattenTypeNodes` to handle the following scenarios:
137
+ // - [Thing]
138
+ // - [Thing!]
139
+ // - [Thing]!
140
+ // - [Thing!]!
141
+ const typeNodes = [];
142
+ collectAndFlattenTypeNodes({
143
+ currentTypeNode: node.type,
144
+ isPreviousNodeNonNullable: oneOfDetails.isOneOfInputValue, // If the InputValue is part of @oneOf input, we treat it as non-null (even if it must be null in the schema)
145
+ typeNodes,
146
+ });
147
+ // 2. Generate the type of a TypeScript field declaration
148
+ // e.g. `field?: string`, then the `string` is the `typePart`
149
+ let typePart = '';
150
+ // We call `.reverse()` here to get the base type node first
151
+ for (const typeNode of typeNodes.reverse()) {
152
+ if (typeNode.type === 'NamedType') {
153
+ const usedInputType = this._usedNamedInputTypes[typeNode.name];
154
+ if (!usedInputType) {
155
+ continue;
156
+ }
157
+ typePart = usedInputType.tsType; // If the schema is correct, when reversing typeNodes, the first node would be `NamedType`, which means we can safely set it as the base for typePart
158
+ if (usedInputType.tsType !== 'any' && !typeNode.isNonNullable) {
159
+ typePart += ' | null | undefined';
160
+ }
161
+ continue;
162
+ }
163
+ if (typeNode.type === 'ListType') {
164
+ typePart = `Array<${typePart}>`;
165
+ if (!typeNode.isNonNullable) {
166
+ typePart += ' | null | undefined';
167
+ }
168
+ }
169
+ }
170
+ // TODO: eddeee888 check if we want to support `directiveArgumentAndInputFieldMappings` for operations
171
+ // if (node.directives && this.config.directiveArgumentAndInputFieldMappings) {
172
+ // typePart =
173
+ // getDirectiveOverrideType({
174
+ // directives: node.directives,
175
+ // directiveArgumentAndInputFieldMappings: this.config.directiveArgumentAndInputFieldMappings,
176
+ // }) || typePart;
177
+ // }
178
+ const addOptionalSign = !oneOfDetails.isOneOfInputValue &&
179
+ !this.config.avoidOptionals.inputValue &&
180
+ (node.type.kind !== graphql_1.Kind.NON_NULL_TYPE ||
181
+ (!this.config.avoidOptionals.defaultValue && node.defaultValue !== undefined));
182
+ // 3. Generate the keyPart of the TypeScript field declaration
183
+ // e.g. `field?: string`, then the `field?` is the `keyPart`
184
+ const keyPart = `${node.name.value}${addOptionalSign ? '?' : ''}`;
185
+ // 4. other parts of TypeScript field declaration
186
+ const commentPart = (0, visitor_plugin_common_1.getNodeComment)(node);
187
+ const readonlyPart = this.config.immutableTypes ? 'readonly ' : '';
188
+ const currentInputValue = commentPart + (0, visitor_plugin_common_1.indent)(`${readonlyPart}${keyPart}: ${typePart};`);
189
+ // 5. Check if field is part of `@oneOf` input type
190
+ // If yes, we must generate a union member where the current inputValue must be provieded, and the others are not
191
+ // e.g.
192
+ // ```graphql
193
+ // input UserInput {
194
+ // byId: ID
195
+ // byEmail: String
196
+ // byLegacyId: ID
197
+ // }
198
+ // ```
199
+ //
200
+ // Then, the generated type is:
201
+ // ```ts
202
+ // type UserInput =
203
+ // | { byId: string | number; byEmail?: never; byLegacyId?: never }
204
+ // | { byId?: never; byEmail: string; byLegacyId?: never }
205
+ // | { byId?: never; byEmail?: never; byLegacyId: string | number }
206
+ // ```
207
+ if (oneOfDetails.isOneOfInputValue) {
208
+ const fieldParts = [];
209
+ for (const fieldName of Object.keys(oneOfDetails.parentType.getFields())) {
210
+ if (fieldName === node.name.value) {
211
+ fieldParts.push(currentInputValue);
212
+ continue;
213
+ }
214
+ fieldParts.push(`${readonlyPart}${fieldName}?: never;`);
215
+ }
216
+ return (0, visitor_plugin_common_1.indent)(`{ ${fieldParts.join(' ')} }`);
217
+ }
218
+ // If field is not part of @oneOf input type, then it's a input value, just return as-is
219
+ return currentInputValue;
220
+ }
62
221
  getImports() {
63
222
  return !this.config.globalNamespace &&
64
223
  (this.config.inlineFragmentTypes === 'combine' || this.config.inlineFragmentTypes === 'mask')
65
224
  ? this.config.fragmentImports.map(fragmentImport => (0, visitor_plugin_common_1.generateFragmentImportStatement)(fragmentImport, 'type'))
66
225
  : [];
67
226
  }
227
+ getExternalSchemaTypeImports() {
228
+ if (!this.config.importSchemaTypesFrom) {
229
+ return [];
230
+ }
231
+ const hasTypesToImport = Object.keys(this._usedNamedInputTypes).length > 0;
232
+ if (!hasTypesToImport) {
233
+ return [];
234
+ }
235
+ return [
236
+ (0, visitor_plugin_common_1.generateImportStatement)({
237
+ baseDir: process.cwd(),
238
+ baseOutputDir: '',
239
+ outputPath: this._outputPath,
240
+ importSource: {
241
+ path: this.config.importSchemaTypesFrom,
242
+ namespace: this.config.namespacedImportName,
243
+ identifiers: [],
244
+ },
245
+ typesImport: true,
246
+ // FIXME: rebase with master for the new extension
247
+ emitLegacyCommonJSImports: true,
248
+ }),
249
+ ];
250
+ }
68
251
  getPunctuation(_declarationKind) {
69
252
  return ';';
70
253
  }
71
254
  applyVariablesWrapper(variablesBlock, operationType) {
72
- const prefix = this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : '';
73
255
  const extraType = this.config.allowUndefinedQueryVariables && operationType === 'Query' ? ' | undefined' : '';
74
- return `${prefix}Exact<${variablesBlock === '{}' ? `{ [key: string]: never; }` : variablesBlock}>${extraType}`;
256
+ return `Exact<${variablesBlock === '{}' ? `{ [key: string]: never; }` : variablesBlock}>${extraType}`;
257
+ }
258
+ collectInnerTypesRecursively(node, usedInputTypes) {
259
+ if (usedInputTypes[node.name]) {
260
+ return;
261
+ }
262
+ if (node instanceof graphql_1.GraphQLEnumType) {
263
+ usedInputTypes[node.name] = {
264
+ type: 'GraphQLEnumType',
265
+ node,
266
+ tsType: this.convertName(node.name),
267
+ };
268
+ return;
269
+ }
270
+ if (node instanceof graphql_1.GraphQLScalarType) {
271
+ usedInputTypes[node.name] = {
272
+ type: 'GraphQLScalarType',
273
+ node,
274
+ tsType: (ts_operation_variables_to_object_js_1.SCALARS[node.name] || this.config.scalars?.[node.name]?.input.type) ?? 'any',
275
+ };
276
+ return;
277
+ }
278
+ // GraphQLInputObjectType
279
+ usedInputTypes[node.name] = {
280
+ type: 'GraphQLInputObjectType',
281
+ node,
282
+ tsType: this.convertName(node.name),
283
+ };
284
+ const fields = node.getFields();
285
+ for (const field of Object.values(fields)) {
286
+ const fieldType = (0, graphql_1.getNamedType)(field.type);
287
+ this.collectInnerTypesRecursively(fieldType, usedInputTypes);
288
+ }
289
+ }
290
+ collectUsedInputTypes({ schema, documentNode, }) {
291
+ const schemaTypes = schema.getTypeMap();
292
+ const usedInputTypes = {};
293
+ // Collect input enums and input types
294
+ (0, graphql_1.visit)(documentNode, {
295
+ VariableDefinition: variableDefinitionNode => {
296
+ (0, graphql_1.visit)(variableDefinitionNode, {
297
+ NamedType: namedTypeNode => {
298
+ const foundInputType = schemaTypes[namedTypeNode.name.value];
299
+ if (foundInputType &&
300
+ (foundInputType instanceof graphql_1.GraphQLInputObjectType ||
301
+ foundInputType instanceof graphql_1.GraphQLScalarType ||
302
+ foundInputType instanceof graphql_1.GraphQLEnumType) &&
303
+ !(0, visitor_plugin_common_1.isNativeNamedType)(foundInputType)) {
304
+ this.collectInnerTypesRecursively(foundInputType, usedInputTypes);
305
+ }
306
+ },
307
+ });
308
+ },
309
+ });
310
+ // Collect output enums
311
+ const typeInfo = new graphql_1.TypeInfo(schema);
312
+ (0, graphql_1.visit)(documentNode,
313
+ // AST doesn’t include field types (they are defined in schema) - only names.
314
+ // TypeInfo is a stateful helper that tracks typing context while walking the AST
315
+ // visitWithTypeInfo wires that context into a visitor.
316
+ (0, graphql_1.visitWithTypeInfo)(typeInfo, {
317
+ Field: () => {
318
+ const fieldType = typeInfo.getType();
319
+ if (fieldType) {
320
+ const namedType = (0, graphql_1.getNamedType)(fieldType);
321
+ if (namedType instanceof graphql_1.GraphQLEnumType) {
322
+ usedInputTypes[namedType.name] = {
323
+ type: 'GraphQLEnumType',
324
+ node: namedType,
325
+ tsType: this.convertName(namedType.name),
326
+ };
327
+ }
328
+ }
329
+ },
330
+ }));
331
+ return usedInputTypes;
332
+ }
333
+ getEnumsImports() {
334
+ const usedEnumMap = {};
335
+ for (const [enumName, enumDetails] of Object.entries(this.config.enumValues)) {
336
+ if (this._usedNamedInputTypes[enumName]) {
337
+ usedEnumMap[enumName] = enumDetails;
338
+ }
339
+ }
340
+ return (0, visitor_plugin_common_1.getEnumsImports)({
341
+ enumValues: usedEnumMap,
342
+ useTypeImports: this.config.useTypeImports,
343
+ });
344
+ }
345
+ getExactUtilityType() {
346
+ if (!this.config.generatesOperationTypes) {
347
+ return null;
348
+ }
349
+ return 'type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };';
350
+ }
351
+ getIncrementalUtilityType() {
352
+ if (!this.config.generatesOperationTypes) {
353
+ return null;
354
+ }
355
+ // Note: `export` here is important for 2 reasons
356
+ // 1. It is not always used in the rest of the file, so this is a safe way to avoid lint rules (in tsconfig or eslint) complaining it's not used in the current file.
357
+ // 2. In Client Preset, it is used by fragment-masking.ts, so it needs `export`
358
+ return "export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };";
75
359
  }
76
360
  }
77
361
  exports.TypeScriptDocumentsVisitor = TypeScriptDocumentsVisitor;
362
+ function parseOneOfInputValue({ node, schema, ancestors, }) {
363
+ const realParentDef = ancestors?.[ancestors.length - 1];
364
+ if (realParentDef) {
365
+ const parentType = schema.getType(realParentDef.name.value);
366
+ if ((0, visitor_plugin_common_1.isOneOfInputObjectType)(parentType)) {
367
+ if (node.type.kind === graphql_1.Kind.NON_NULL_TYPE) {
368
+ throw new Error('Fields on an input object type can not be non-nullable. It seems like the schema was not validated.');
369
+ }
370
+ return { isOneOfInputValue: true, realParentDef, parentType };
371
+ }
372
+ }
373
+ return { isOneOfInputValue: false };
374
+ }
375
+ function collectAndFlattenTypeNodes({ currentTypeNode, isPreviousNodeNonNullable, typeNodes, }) {
376
+ if (currentTypeNode.kind === graphql_1.Kind.NON_NULL_TYPE) {
377
+ const nextTypeNode = currentTypeNode.type;
378
+ collectAndFlattenTypeNodes({ currentTypeNode: nextTypeNode, isPreviousNodeNonNullable: true, typeNodes });
379
+ }
380
+ else if (currentTypeNode.kind === graphql_1.Kind.LIST_TYPE) {
381
+ typeNodes.push({ type: 'ListType', isNonNullable: isPreviousNodeNonNullable });
382
+ const nextTypeNode = currentTypeNode.type;
383
+ collectAndFlattenTypeNodes({ currentTypeNode: nextTypeNode, isPreviousNodeNonNullable: false, typeNodes });
384
+ }
385
+ else if (currentTypeNode.kind === graphql_1.Kind.NAMED_TYPE) {
386
+ typeNodes.push({
387
+ type: 'NamedType',
388
+ isNonNullable: isPreviousNodeNonNullable,
389
+ name: currentTypeNode.name.value,
390
+ });
391
+ }
392
+ }
package/esm/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { oldVisit } from '@graphql-codegen/plugin-helpers';
2
+ import { transformSchemaAST } from '@graphql-codegen/schema-ast';
2
3
  import { optimizeOperations } from '@graphql-codegen/visitor-plugin-common';
3
- import { concatAST, Kind } from 'graphql';
4
+ import { concatAST } from 'graphql';
4
5
  import { TypeScriptDocumentsVisitor } from './visitor.js';
5
- export const plugin = async (inputSchema, rawDocuments, config) => {
6
+ export const plugin = async (inputSchema, rawDocuments, config, { outputFile }) => {
6
7
  const schema = config.nullability?.errorHandlingClient ? await semanticToStrict(inputSchema) : inputSchema;
7
8
  const documents = config.flattenGeneratedTypes
8
9
  ? optimizeOperations(schema, rawDocuments, {
@@ -10,29 +11,23 @@ export const plugin = async (inputSchema, rawDocuments, config) => {
10
11
  })
11
12
  : rawDocuments;
12
13
  const allAst = concatAST(documents.map(v => v.document));
13
- const allFragments = [
14
- ...allAst.definitions.filter(d => d.kind === Kind.FRAGMENT_DEFINITION).map(fragmentDef => ({
15
- node: fragmentDef,
16
- name: fragmentDef.name.value,
17
- onType: fragmentDef.typeCondition.name.value,
18
- isExternal: false,
19
- })),
20
- ...(config.externalFragments || []),
21
- ];
22
- const visitor = new TypeScriptDocumentsVisitor(schema, config, allFragments);
23
- const visitorResult = oldVisit(allAst, {
24
- leave: visitor,
25
- });
26
- let content = visitorResult.definitions.join('\n');
14
+ const visitor = new TypeScriptDocumentsVisitor(schema, config, allAst, outputFile);
15
+ const operationsResult = oldVisit(allAst, { leave: visitor });
16
+ const operationsDefinitions = operationsResult.definitions;
27
17
  if (config.addOperationExport) {
28
- const exportConsts = [];
29
18
  for (const d of allAst.definitions) {
30
19
  if ('name' in d) {
31
- exportConsts.push(`export declare const ${d.name.value}: import("graphql").DocumentNode;`);
20
+ operationsDefinitions.push(`export declare const ${d.name.value}: import("graphql").DocumentNode;`);
32
21
  }
33
22
  }
34
- content = visitorResult.definitions.concat(exportConsts).join('\n');
35
23
  }
24
+ const schemaTypes = oldVisit(transformSchemaAST(schema, config).ast, { leave: visitor });
25
+ // IMPORTANT: when a visitor leaves a node with no transformation logic,
26
+ // It will leave the node as an object.
27
+ // Here, we filter in nodes that have been turned into strings, i.e. they have been transformed
28
+ // This way, we do not have to explicitly declare a method for every node type to convert them to null
29
+ const schemaTypesDefinitions = schemaTypes.definitions.filter(def => typeof def === 'string');
30
+ let content = [...schemaTypesDefinitions, ...operationsDefinitions].join('\n');
36
31
  if (config.globalNamespace) {
37
32
  content = `
38
33
  declare global {
@@ -42,8 +37,11 @@ export const plugin = async (inputSchema, rawDocuments, config) => {
42
37
  return {
43
38
  prepend: [
44
39
  ...visitor.getImports(),
40
+ ...visitor.getExternalSchemaTypeImports(),
41
+ ...visitor.getEnumsImports(),
45
42
  ...visitor.getGlobalDeclarations(visitor.config.noExport),
46
- 'type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };',
43
+ visitor.getExactUtilityType(),
44
+ visitor.getIncrementalUtilityType(),
47
45
  ],
48
46
  content,
49
47
  };
@@ -1,5 +1,5 @@
1
1
  import { TypeScriptOperationVariablesToObject as TSOperationVariablesToObject } from '@graphql-codegen/typescript';
2
- const SCALARS = {
2
+ export const SCALARS = {
3
3
  ID: 'string | number',
4
4
  String: 'string',
5
5
  Int: 'number',
@@ -23,15 +23,7 @@ export class TypeScriptSelectionSetProcessor extends BaseSelectionSetProcessor {
23
23
  });
24
24
  let resString = formattedUnionTransform('Pick', parentName, escapedFieldNames);
25
25
  if (hasConditionals) {
26
- const avoidOptional =
27
- // TODO: check type and exec only if relevant
28
- this.config.avoidOptionals === true ||
29
- (typeof this.config.avoidOptionals === 'object' &&
30
- (this.config.avoidOptionals.field ||
31
- this.config.avoidOptionals.inputValue ||
32
- this.config.avoidOptionals.object));
33
- const transform = avoidOptional ? 'MakeMaybe' : 'MakeOptional';
34
- resString = `${this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : ''}${formattedUnionTransform(transform, resString, escapedConditionalsList)}`;
26
+ resString = `${this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : ''}${formattedUnionTransform('MakeOptional', resString, escapedConditionalsList)}`;
35
27
  }
36
28
  return [resString];
37
29
  }