@graphql-codegen/visitor-plugin-common 7.0.0-alpha-20260105124559-f9262ad32043cda0a35998bceda372bdbb1b034c → 7.0.0-alpha-20260106134001-96cfbeda34da3ef03f382f529dae5d10a950b657

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.
@@ -173,15 +173,17 @@ class BaseResolversVisitor extends base_visitor_js_1.BaseVisitor {
173
173
  const isMapped = this.config.mappers[typeName];
174
174
  const isScalar = this.config.scalars[typeName];
175
175
  const hasDefaultMapper = !!this.config.defaultMapper?.type;
176
- if (isRootType) {
177
- prev[typeName] = applyWrapper(this.config.rootValueType.type);
178
- return prev;
179
- }
176
+ // Check for mappers first, even for root types, to allow overriding rootValueType
180
177
  if (isMapped && this.config.mappers[typeName].type && !hasPlaceholder(this.config.mappers[typeName].type)) {
181
178
  this.markMapperAsUsed(typeName);
182
179
  prev[typeName] = applyWrapper(this.config.mappers[typeName].type);
180
+ return prev;
181
+ }
182
+ if (isRootType) {
183
+ prev[typeName] = applyWrapper(this.config.rootValueType.type);
184
+ return prev;
183
185
  }
184
- else if ((0, graphql_1.isEnumType)(schemaType) && this.config.enumValues[typeName]) {
186
+ if ((0, graphql_1.isEnumType)(schemaType) && this.config.enumValues[typeName]) {
185
187
  const isExternalFile = !!this.config.enumValues[typeName].sourceFile;
186
188
  prev[typeName] = isExternalFile
187
189
  ? this.convertName(this.config.enumValues[typeName].typeIdentifier, {
@@ -5,11 +5,16 @@ const tslib_1 = require("tslib");
5
5
  const auto_bind_1 = tslib_1.__importDefault(require("auto-bind"));
6
6
  const naming_js_1 = require("./naming.js");
7
7
  const utils_js_1 = require("./utils.js");
8
+ const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers");
8
9
  class BaseVisitor {
9
10
  _parsedConfig;
10
11
  _declarationBlockConfig = {};
11
12
  scalars;
12
13
  constructor(rawConfig, additionalConfig) {
14
+ const importExtension = (0, plugin_helpers_1.normalizeImportExtension)({
15
+ emitLegacyCommonJSImports: rawConfig.emitLegacyCommonJSImports,
16
+ importExtension: rawConfig.importExtension,
17
+ });
13
18
  this._parsedConfig = {
14
19
  convert: (0, naming_js_1.convertFactory)(rawConfig),
15
20
  typesPrefix: rawConfig.typesPrefix || '',
@@ -22,6 +27,7 @@ class BaseVisitor {
22
27
  allowEnumStringTypes: !!rawConfig.allowEnumStringTypes,
23
28
  inlineFragmentTypes: rawConfig.inlineFragmentTypes ?? 'inline',
24
29
  emitLegacyCommonJSImports: rawConfig.emitLegacyCommonJSImports === undefined ? true : !!rawConfig.emitLegacyCommonJSImports,
30
+ importExtension,
25
31
  printFieldsOnNewLines: rawConfig.printFieldsOnNewLines ?? false,
26
32
  includeExternalFragments: rawConfig.includeExternalFragments ?? false,
27
33
  ...(additionalConfig || {}),
@@ -296,7 +296,11 @@ class ClientSideBaseVisitor extends base_visitor_js_1.BaseVisitor {
296
296
  }
297
297
  clearExtension(path) {
298
298
  const extension = (0, path_1.extname)(path);
299
- if (!this.config.emitLegacyCommonJSImports && extension === '.js') {
299
+ const importExtension = (0, plugin_helpers_1.normalizeImportExtension)({
300
+ emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
301
+ importExtension: this.config.importExtension,
302
+ });
303
+ if (extension === importExtension) {
300
304
  return path;
301
305
  }
302
306
  if (EXTENSIONS_TO_REMOVE.includes(extension)) {
@@ -330,9 +334,10 @@ class ClientSideBaseVisitor extends base_visitor_js_1.BaseVisitor {
330
334
  if (this._collectedOperations.length > 0) {
331
335
  if (this.config.importDocumentNodeExternallyFrom === 'near-operation-file' && this._documents.length === 1) {
332
336
  let documentPath = `./${this.clearExtension((0, path_1.basename)(this._documents[0].location))}`;
333
- if (!this.config.emitLegacyCommonJSImports) {
334
- documentPath += '.js';
335
- }
337
+ documentPath += (0, plugin_helpers_1.normalizeImportExtension)({
338
+ emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
339
+ importExtension: this.config.importExtension,
340
+ });
336
341
  this._imports.add(`import * as Operations from '${documentPath}';`);
337
342
  }
338
343
  else {
@@ -350,6 +355,10 @@ class ClientSideBaseVisitor extends base_visitor_js_1.BaseVisitor {
350
355
  }
351
356
  const excludeFragments = options.excludeFragments || this.config.globalNamespace || this.config.documentMode !== DocumentMode.graphQLTag;
352
357
  if (!excludeFragments) {
358
+ const importExtension = (0, plugin_helpers_1.normalizeImportExtension)({
359
+ emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
360
+ importExtension: this.config.importExtension,
361
+ });
353
362
  const deduplicatedImports = Object.values((0, utils_js_1.groupBy)(this.config.fragmentImports, fi => fi.importSource.path))
354
363
  .map((fragmentImports) => ({
355
364
  ...fragmentImports[0],
@@ -358,6 +367,7 @@ class ClientSideBaseVisitor extends base_visitor_js_1.BaseVisitor {
358
367
  identifiers: (0, utils_js_1.unique)((0, utils_js_1.flatten)(fragmentImports.map(fi => fi.importSource.identifiers)), identifier => identifier.name),
359
368
  },
360
369
  emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
370
+ importExtension,
361
371
  }))
362
372
  .filter(fragmentImport => fragmentImport.outputPath !== fragmentImport.importSource.path);
363
373
  for (const fragmentImport of deduplicatedImports) {
package/cjs/imports.js CHANGED
@@ -11,6 +11,7 @@ exports.buildTypeImport = buildTypeImport;
11
11
  const tslib_1 = require("tslib");
12
12
  const path_1 = require("path");
13
13
  const parse_filepath_1 = tslib_1.__importDefault(require("parse-filepath"));
14
+ const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers");
14
15
  function generateFragmentImportStatement(statement, kind) {
15
16
  const { importSource: fragmentImportSource, ...rest } = statement;
16
17
  const { identifiers, path, namespace } = fragmentImportSource;
@@ -33,7 +34,12 @@ function generateImportStatement(statement) {
33
34
  const importNames = importSource.identifiers?.length
34
35
  ? `{ ${Array.from(new Set(importSource.identifiers)).join(', ')} }`
35
36
  : '*';
36
- const importExtension = importPath.startsWith('/') || importPath.startsWith('.') ? (statement.emitLegacyCommonJSImports ? '' : '.js') : '';
37
+ const importExtension = importPath.startsWith('/') || importPath.startsWith('.')
38
+ ? (0, plugin_helpers_1.normalizeImportExtension)({
39
+ emitLegacyCommonJSImports: statement.emitLegacyCommonJSImports,
40
+ importExtension: statement.importExtension,
41
+ })
42
+ : '';
37
43
  const importAlias = importSource.namespace ? ` as ${importSource.namespace}` : '';
38
44
  const importStatement = typesImport ? 'import type' : 'import';
39
45
  return `${importStatement} ${importNames}${importAlias} from '${importPath}${importExtension}';${importAlias ? '\n' : ''}`;
@@ -687,7 +687,20 @@ class SelectionSetToObject {
687
687
  buildParentFieldName(typeName, parentName) {
688
688
  // queries/mutations/fragments are guaranteed to be unique type names,
689
689
  // so we can skip affixing the field names with typeName
690
- return operationTypes.includes(typeName) ? parentName : `${parentName}_${typeName}`;
690
+ if (operationTypes.includes(typeName)) {
691
+ return parentName;
692
+ }
693
+ const schemaType = this._schema.getType(typeName);
694
+ // Check if current selection set has fragments (e.g., "... AppNotificationFragment" or "... on AppNotification")
695
+ const hasFragment = this._selectionSet?.selections?.some(selection => selection.kind === graphql_1.Kind.INLINE_FRAGMENT || selection.kind === graphql_1.Kind.FRAGMENT_SPREAD) ?? false;
696
+ // When the parent schema type is an interface:
697
+ // - If we're processing inline fragments, use the concrete type name
698
+ // - If we're processing the interface directly, use the interface name
699
+ // - If we're in a named fragment, always use the concrete type name
700
+ if ((0, graphql_1.isObjectType)(schemaType) && this._parentSchemaType && (0, graphql_1.isInterfaceType)(this._parentSchemaType) && !hasFragment) {
701
+ return `${parentName}_${this._parentSchemaType.name}`;
702
+ }
703
+ return `${parentName}_${typeName}`;
691
704
  }
692
705
  }
693
706
  exports.SelectionSetToObject = SelectionSetToObject;
@@ -169,15 +169,17 @@ export class BaseResolversVisitor extends BaseVisitor {
169
169
  const isMapped = this.config.mappers[typeName];
170
170
  const isScalar = this.config.scalars[typeName];
171
171
  const hasDefaultMapper = !!this.config.defaultMapper?.type;
172
- if (isRootType) {
173
- prev[typeName] = applyWrapper(this.config.rootValueType.type);
174
- return prev;
175
- }
172
+ // Check for mappers first, even for root types, to allow overriding rootValueType
176
173
  if (isMapped && this.config.mappers[typeName].type && !hasPlaceholder(this.config.mappers[typeName].type)) {
177
174
  this.markMapperAsUsed(typeName);
178
175
  prev[typeName] = applyWrapper(this.config.mappers[typeName].type);
176
+ return prev;
177
+ }
178
+ if (isRootType) {
179
+ prev[typeName] = applyWrapper(this.config.rootValueType.type);
180
+ return prev;
179
181
  }
180
- else if (isEnumType(schemaType) && this.config.enumValues[typeName]) {
182
+ if (isEnumType(schemaType) && this.config.enumValues[typeName]) {
181
183
  const isExternalFile = !!this.config.enumValues[typeName].sourceFile;
182
184
  prev[typeName] = isExternalFile
183
185
  ? this.convertName(this.config.enumValues[typeName].typeIdentifier, {
@@ -1,11 +1,16 @@
1
1
  import autoBind from 'auto-bind';
2
2
  import { convertFactory } from './naming.js';
3
3
  import { getConfigValue } from './utils.js';
4
+ import { normalizeImportExtension } from '@graphql-codegen/plugin-helpers';
4
5
  export class BaseVisitor {
5
6
  _parsedConfig;
6
7
  _declarationBlockConfig = {};
7
8
  scalars;
8
9
  constructor(rawConfig, additionalConfig) {
10
+ const importExtension = normalizeImportExtension({
11
+ emitLegacyCommonJSImports: rawConfig.emitLegacyCommonJSImports,
12
+ importExtension: rawConfig.importExtension,
13
+ });
9
14
  this._parsedConfig = {
10
15
  convert: convertFactory(rawConfig),
11
16
  typesPrefix: rawConfig.typesPrefix || '',
@@ -18,6 +23,7 @@ export class BaseVisitor {
18
23
  allowEnumStringTypes: !!rawConfig.allowEnumStringTypes,
19
24
  inlineFragmentTypes: rawConfig.inlineFragmentTypes ?? 'inline',
20
25
  emitLegacyCommonJSImports: rawConfig.emitLegacyCommonJSImports === undefined ? true : !!rawConfig.emitLegacyCommonJSImports,
26
+ importExtension,
21
27
  printFieldsOnNewLines: rawConfig.printFieldsOnNewLines ?? false,
22
28
  includeExternalFragments: rawConfig.includeExternalFragments ?? false,
23
29
  ...(additionalConfig || {}),
@@ -1,5 +1,5 @@
1
1
  import { basename, extname } from 'path';
2
- import { oldVisit } from '@graphql-codegen/plugin-helpers';
2
+ import { normalizeImportExtension, oldVisit } from '@graphql-codegen/plugin-helpers';
3
3
  import { optimizeDocumentNode } from '@graphql-tools/optimize';
4
4
  import autoBind from 'auto-bind';
5
5
  import { pascalCase } from 'change-case-all';
@@ -292,7 +292,11 @@ export class ClientSideBaseVisitor extends BaseVisitor {
292
292
  }
293
293
  clearExtension(path) {
294
294
  const extension = extname(path);
295
- if (!this.config.emitLegacyCommonJSImports && extension === '.js') {
295
+ const importExtension = normalizeImportExtension({
296
+ emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
297
+ importExtension: this.config.importExtension,
298
+ });
299
+ if (extension === importExtension) {
296
300
  return path;
297
301
  }
298
302
  if (EXTENSIONS_TO_REMOVE.includes(extension)) {
@@ -326,9 +330,10 @@ export class ClientSideBaseVisitor extends BaseVisitor {
326
330
  if (this._collectedOperations.length > 0) {
327
331
  if (this.config.importDocumentNodeExternallyFrom === 'near-operation-file' && this._documents.length === 1) {
328
332
  let documentPath = `./${this.clearExtension(basename(this._documents[0].location))}`;
329
- if (!this.config.emitLegacyCommonJSImports) {
330
- documentPath += '.js';
331
- }
333
+ documentPath += normalizeImportExtension({
334
+ emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
335
+ importExtension: this.config.importExtension,
336
+ });
332
337
  this._imports.add(`import * as Operations from '${documentPath}';`);
333
338
  }
334
339
  else {
@@ -346,6 +351,10 @@ export class ClientSideBaseVisitor extends BaseVisitor {
346
351
  }
347
352
  const excludeFragments = options.excludeFragments || this.config.globalNamespace || this.config.documentMode !== DocumentMode.graphQLTag;
348
353
  if (!excludeFragments) {
354
+ const importExtension = normalizeImportExtension({
355
+ emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
356
+ importExtension: this.config.importExtension,
357
+ });
349
358
  const deduplicatedImports = Object.values(groupBy(this.config.fragmentImports, fi => fi.importSource.path))
350
359
  .map((fragmentImports) => ({
351
360
  ...fragmentImports[0],
@@ -354,6 +363,7 @@ export class ClientSideBaseVisitor extends BaseVisitor {
354
363
  identifiers: unique(flatten(fragmentImports.map(fi => fi.importSource.identifiers)), identifier => identifier.name),
355
364
  },
356
365
  emitLegacyCommonJSImports: this.config.emitLegacyCommonJSImports,
366
+ importExtension,
357
367
  }))
358
368
  .filter(fragmentImport => fragmentImport.outputPath !== fragmentImport.importSource.path);
359
369
  for (const fragmentImport of deduplicatedImports) {
package/esm/imports.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { dirname, isAbsolute, join, relative, resolve } from 'path';
2
2
  import parse from 'parse-filepath';
3
+ import { normalizeImportExtension } from '@graphql-codegen/plugin-helpers';
3
4
  export function generateFragmentImportStatement(statement, kind) {
4
5
  const { importSource: fragmentImportSource, ...rest } = statement;
5
6
  const { identifiers, path, namespace } = fragmentImportSource;
@@ -22,7 +23,12 @@ export function generateImportStatement(statement) {
22
23
  const importNames = importSource.identifiers?.length
23
24
  ? `{ ${Array.from(new Set(importSource.identifiers)).join(', ')} }`
24
25
  : '*';
25
- const importExtension = importPath.startsWith('/') || importPath.startsWith('.') ? (statement.emitLegacyCommonJSImports ? '' : '.js') : '';
26
+ const importExtension = importPath.startsWith('/') || importPath.startsWith('.')
27
+ ? normalizeImportExtension({
28
+ emitLegacyCommonJSImports: statement.emitLegacyCommonJSImports,
29
+ importExtension: statement.importExtension,
30
+ })
31
+ : '';
26
32
  const importAlias = importSource.namespace ? ` as ${importSource.namespace}` : '';
27
33
  const importStatement = typesImport ? 'import type' : 'import';
28
34
  return `${importStatement} ${importNames}${importAlias} from '${importPath}${importExtension}';${importAlias ? '\n' : ''}`;
@@ -683,7 +683,20 @@ export class SelectionSetToObject {
683
683
  buildParentFieldName(typeName, parentName) {
684
684
  // queries/mutations/fragments are guaranteed to be unique type names,
685
685
  // so we can skip affixing the field names with typeName
686
- return operationTypes.includes(typeName) ? parentName : `${parentName}_${typeName}`;
686
+ if (operationTypes.includes(typeName)) {
687
+ return parentName;
688
+ }
689
+ const schemaType = this._schema.getType(typeName);
690
+ // Check if current selection set has fragments (e.g., "... AppNotificationFragment" or "... on AppNotification")
691
+ const hasFragment = this._selectionSet?.selections?.some(selection => selection.kind === Kind.INLINE_FRAGMENT || selection.kind === Kind.FRAGMENT_SPREAD) ?? false;
692
+ // When the parent schema type is an interface:
693
+ // - If we're processing inline fragments, use the concrete type name
694
+ // - If we're processing the interface directly, use the interface name
695
+ // - If we're in a named fragment, always use the concrete type name
696
+ if (isObjectType(schemaType) && this._parentSchemaType && isInterfaceType(this._parentSchemaType) && !hasFragment) {
697
+ return `${parentName}_${this._parentSchemaType.name}`;
698
+ }
699
+ return `${parentName}_${typeName}`;
687
700
  }
688
701
  }
689
702
  function formatUnion(members) {
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@graphql-codegen/visitor-plugin-common",
3
- "version": "7.0.0-alpha-20260105124559-f9262ad32043cda0a35998bceda372bdbb1b034c",
3
+ "version": "7.0.0-alpha-20260106134001-96cfbeda34da3ef03f382f529dae5d10a950b657",
4
4
  "peerDependencies": {
5
5
  "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
6
6
  },
7
7
  "dependencies": {
8
8
  "@graphql-tools/optimize": "^2.0.0",
9
- "@graphql-codegen/plugin-helpers": "^6.0.0",
9
+ "@graphql-codegen/plugin-helpers": "^6.1.0",
10
10
  "@graphql-tools/relay-operation-optimizer": "^7.0.0",
11
11
  "@graphql-tools/utils": "^10.0.0",
12
12
  "auto-bind": "~4.0.0",
@@ -20,7 +20,8 @@ export interface ParsedConfig {
20
20
  useTypeImports: boolean;
21
21
  allowEnumStringTypes: boolean;
22
22
  inlineFragmentTypes: InlineFragmentTypeOptions;
23
- emitLegacyCommonJSImports: boolean;
23
+ emitLegacyCommonJSImports?: boolean;
24
+ importExtension: '' | `.${string}`;
24
25
  printFieldsOnNewLines: boolean;
25
26
  includeExternalFragments: boolean;
26
27
  }
@@ -343,11 +344,17 @@ export interface RawConfig {
343
344
  */
344
345
  inlineFragmentTypes?: InlineFragmentTypeOptions;
345
346
  /**
347
+ * @deprecated Please use `importExtension` instead.
346
348
  * @default true
347
349
  * @description Emit legacy common js imports.
348
350
  * Default it will be `true` this way it ensure that generated code works with [non-compliant bundlers](https://github.com/dotansimha/graphql-code-generator/issues/8065).
349
351
  */
350
352
  emitLegacyCommonJSImports?: boolean;
353
+ /**
354
+ * @description Append this extension to all imports.
355
+ * Useful for ESM environments that require file extensions in import statements.
356
+ */
357
+ importExtension?: '' | `.${string}`;
351
358
  /**
352
359
  * @default false
353
360
  * @description If you prefer to have each field in generated types printed on a new line, set this to true.
@@ -20,7 +20,8 @@ export interface ParsedConfig {
20
20
  useTypeImports: boolean;
21
21
  allowEnumStringTypes: boolean;
22
22
  inlineFragmentTypes: InlineFragmentTypeOptions;
23
- emitLegacyCommonJSImports: boolean;
23
+ emitLegacyCommonJSImports?: boolean;
24
+ importExtension: '' | `.${string}`;
24
25
  printFieldsOnNewLines: boolean;
25
26
  includeExternalFragments: boolean;
26
27
  }
@@ -343,11 +344,17 @@ export interface RawConfig {
343
344
  */
344
345
  inlineFragmentTypes?: InlineFragmentTypeOptions;
345
346
  /**
347
+ * @deprecated Please use `importExtension` instead.
346
348
  * @default true
347
349
  * @description Emit legacy common js imports.
348
350
  * Default it will be `true` this way it ensure that generated code works with [non-compliant bundlers](https://github.com/dotansimha/graphql-code-generator/issues/8065).
349
351
  */
350
352
  emitLegacyCommonJSImports?: boolean;
353
+ /**
354
+ * @description Append this extension to all imports.
355
+ * Useful for ESM environments that require file extensions in import statements.
356
+ */
357
+ importExtension?: '' | `.${string}`;
351
358
  /**
352
359
  * @default false
353
360
  * @description If you prefer to have each field in generated types printed on a new line, set this to true.
@@ -5,7 +5,8 @@ export type ImportDeclaration<T = string> = {
5
5
  baseOutputDir: string;
6
6
  baseDir: string;
7
7
  typesImport: boolean;
8
- emitLegacyCommonJSImports: boolean;
8
+ emitLegacyCommonJSImports?: boolean;
9
+ importExtension: '' | `.${string}`;
9
10
  };
10
11
  export type ImportSource<T = string> = {
11
12
  /**
@@ -5,7 +5,8 @@ export type ImportDeclaration<T = string> = {
5
5
  baseOutputDir: string;
6
6
  baseDir: string;
7
7
  typesImport: boolean;
8
- emitLegacyCommonJSImports: boolean;
8
+ emitLegacyCommonJSImports?: boolean;
9
+ importExtension: '' | `.${string}`;
9
10
  };
10
11
  export type ImportSource<T = string> = {
11
12
  /**