@nmarks/graphql-codegen-per-operation-file-preset 1.0.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.
package/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # @graphql-codegen/per-operation-file-preset
2
+
3
+ Generate **one file per GraphQL operation or fragment**, named after the operation/fragment itself.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @graphql-codegen/per-operation-file-preset
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ // codegen.ts
15
+ import type { CodegenConfig } from '@graphql-codegen/cli';
16
+
17
+ const config: CodegenConfig = {
18
+ schema: 'schema.graphql',
19
+ documents: 'src/**/*.ts',
20
+ generates: {
21
+ 'src/': {
22
+ preset: 'per-operation-file',
23
+ presetConfig: {
24
+ baseTypesPath: 'types.ts',
25
+ },
26
+ plugins: ['typescript-operations'],
27
+ },
28
+ },
29
+ };
30
+ export default config;
31
+ ```
32
+
33
+ ## How It Works
34
+
35
+ Unlike the `near-operation-file` preset which generates one file per source file, this preset generates **one file per operation/fragment**.
36
+
37
+ ### Example
38
+
39
+ **Input:**
40
+ ```typescript
41
+ // src/queries/user.ts
42
+ export const GetUserQuery = gql`
43
+ query GetUser($id: ID!) {
44
+ user(id: $id) {
45
+ id
46
+ name
47
+ }
48
+ }
49
+ `;
50
+
51
+ export const GetUsersQuery = gql`
52
+ query GetUsers {
53
+ users {
54
+ id
55
+ name
56
+ }
57
+ }
58
+ `;
59
+ ```
60
+
61
+ **Output:**
62
+ ```
63
+ src/queries/__generated__/GetUser.ts # Generated types for GetUser query
64
+ src/queries/__generated__/GetUsers.ts # Generated types for GetUsers query
65
+ ```
66
+
67
+ ### Fragments
68
+
69
+ **Input:**
70
+ ```typescript
71
+ // src/fragments/user.ts
72
+ export const UserFieldsFragment = gql`
73
+ fragment UserFields on User {
74
+ id
75
+ name
76
+ email
77
+ }
78
+ `;
79
+ ```
80
+
81
+ **Output:**
82
+ ```
83
+ src/fragments/__generated__/UserFields.ts
84
+ ```
85
+
86
+ ## Configuration
87
+
88
+ ### `baseTypesPath` (required)
89
+
90
+ Path to your base schema types file (generated by the `typescript` plugin).
91
+
92
+ ```typescript
93
+ presetConfig: {
94
+ baseTypesPath: 'types.ts'
95
+ }
96
+ ```
97
+
98
+ You can also use npm packages:
99
+ ```typescript
100
+ presetConfig: {
101
+ baseTypesPath: '~@myapp/graphql-types'
102
+ }
103
+ ```
104
+
105
+ ### `folder` (optional)
106
+
107
+ Folder name for generated files, relative to the source file location.
108
+
109
+ **Default:** `__generated__`
110
+
111
+ ```typescript
112
+ presetConfig: {
113
+ baseTypesPath: 'types.ts',
114
+ folder: 'generated' // Use 'generated' instead of '__generated__'
115
+ }
116
+ ```
117
+
118
+ ### `extension` (optional)
119
+
120
+ File extension for generated files.
121
+
122
+ **Default:** `.ts`
123
+
124
+ ```typescript
125
+ presetConfig: {
126
+ baseTypesPath: 'types.ts',
127
+ extension: '.generated.ts'
128
+ }
129
+ ```
130
+
131
+ ### `importTypesNamespace` (optional)
132
+
133
+ Namespace for importing base schema types.
134
+
135
+ **Default:** `Types`
136
+
137
+ ```typescript
138
+ presetConfig: {
139
+ baseTypesPath: 'types.ts',
140
+ importTypesNamespace: 'SchemaTypes'
141
+ }
142
+ ```
143
+
144
+ ### `cwd` (optional)
145
+
146
+ Override the current working directory.
147
+
148
+ **Default:** `process.cwd()`
149
+
150
+ ```typescript
151
+ presetConfig: {
152
+ baseTypesPath: 'types.ts',
153
+ cwd: '/custom/path'
154
+ }
155
+ ```
156
+
157
+ ## Fragment Imports
158
+
159
+ The preset automatically handles fragment imports. If an operation uses a fragment from another file, it will generate the correct import statement.
160
+
161
+ **Example:**
162
+
163
+ ```typescript
164
+ // src/fragments/user.ts
165
+ fragment UserFields on User { id name }
166
+
167
+ // src/queries/user.ts
168
+ query GetUser($id: ID!) {
169
+ user(id: $id) {
170
+ ...UserFields
171
+ }
172
+ }
173
+ ```
174
+
175
+ **Generated:**
176
+
177
+ ```typescript
178
+ // src/queries/__generated__/GetUser.ts
179
+ import { UserFieldsFragment, UserFieldsFragmentDoc } from '../../fragments/__generated__/UserFields';
180
+ // ... rest of generated code
181
+ ```
182
+
183
+ ## Comparison with near-operation-file
184
+
185
+ | Feature | near-operation-file | per-operation-file |
186
+ |---------|-------------------|-------------------|
187
+ | **File Strategy** | One file per source file | One file per operation/fragment |
188
+ | **Output Naming** | Based on source filename | Based on operation/fragment name |
189
+ | **Multiple Operations** | Combined in one file | Split into separate files |
190
+ | **Fragment Imports** | ✅ Supported | ✅ Supported |
191
+ | **Use Case** | Co-location with source | Migration from other tools, explicit operation files |
192
+
193
+ ## When to Use This Preset
194
+
195
+ Use `per-operation-file` when:
196
+ - ✅ Migrating from tools that generate one file per operation
197
+ - ✅ You want explicit, discoverable operation files
198
+ - ✅ You have multiple operations per source file
199
+ - ✅ You want operation names to match generated file names
200
+
201
+ Use `near-operation-file` when:
202
+ - ✅ You want generated files to mirror your source structure
203
+ - ✅ You prefer one-to-one mapping between source and generated files
204
+ - ✅ You typically have one operation per source file
205
+
206
+ ## License
207
+
208
+ MIT
209
+
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = buildFragmentResolver;
4
+ const graphql_1 = require("graphql");
5
+ const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
6
+ const utils_js_1 = require("./utils.js");
7
+ /**
8
+ * Creates fragment imports based on possible types and usage
9
+ */
10
+ function createFragmentImports(baseVisitor, fragmentName, possibleTypes, usedTypes) {
11
+ const fragmentImports = [];
12
+ // Always include the document import
13
+ fragmentImports.push({
14
+ name: baseVisitor.getFragmentVariableName(fragmentName),
15
+ kind: 'document',
16
+ });
17
+ const fragmentSuffix = baseVisitor.getFragmentSuffix(fragmentName);
18
+ if (possibleTypes.length === 1) {
19
+ fragmentImports.push({
20
+ name: baseVisitor.convertName(fragmentName, {
21
+ useTypesPrefix: true,
22
+ suffix: fragmentSuffix,
23
+ }),
24
+ kind: 'type',
25
+ });
26
+ }
27
+ else if (possibleTypes.length > 0) {
28
+ const typesToImport = usedTypes && usedTypes.length > 0 ? usedTypes : possibleTypes;
29
+ typesToImport.forEach(typeName => {
30
+ fragmentImports.push({
31
+ name: baseVisitor.convertName(fragmentName, {
32
+ useTypesPrefix: true,
33
+ suffix: `_${typeName}` + (fragmentSuffix.length > 0 ? `_${fragmentSuffix}` : ''),
34
+ }),
35
+ kind: 'type',
36
+ });
37
+ });
38
+ }
39
+ return fragmentImports;
40
+ }
41
+ /**
42
+ * Used by `buildFragmentResolver` to build a mapping of fragmentNames to paths, importNames, and other useful info
43
+ *
44
+ * KEY DIFFERENCE from near-operation-file:
45
+ * The filePath is generated based on the fragment NAME, not the source file location
46
+ */
47
+ function buildFragmentRegistry(baseVisitor, { folder, extension }, { documents }, schemaObject) {
48
+ const duplicateFragmentNames = [];
49
+ const registry = documents.reduce((prev, documentRecord) => {
50
+ const fragments = documentRecord.document.definitions.filter(d => d.kind === graphql_1.Kind.FRAGMENT_DEFINITION);
51
+ for (const fragment of fragments) {
52
+ const schemaType = schemaObject.getType(fragment.typeCondition.name.value);
53
+ if (!schemaType) {
54
+ throw new Error(`Fragment "${fragment.name.value}" is set on non-existing type "${fragment.typeCondition.name.value}"!`);
55
+ }
56
+ const fragmentName = fragment.name.value;
57
+ // KEY CHANGE: Generate path based on fragment name, not source file
58
+ const filePath = (0, utils_js_1.generateOperationFilePath)(documentRecord.location, fragmentName, folder, extension);
59
+ const possibleTypes = (0, visitor_plugin_common_1.getPossibleTypes)(schemaObject, schemaType);
60
+ const possibleTypeNames = possibleTypes.map(t => t.name);
61
+ const imports = createFragmentImports(baseVisitor, fragment.name.value, possibleTypeNames);
62
+ if (prev[fragmentName] && (0, graphql_1.print)(fragment) !== (0, graphql_1.print)(prev[fragmentName].node)) {
63
+ duplicateFragmentNames.push(fragmentName);
64
+ }
65
+ prev[fragmentName] = {
66
+ filePath,
67
+ imports,
68
+ onType: fragment.typeCondition.name.value,
69
+ node: fragment,
70
+ possibleTypes: possibleTypeNames,
71
+ };
72
+ }
73
+ return prev;
74
+ }, {});
75
+ if (duplicateFragmentNames.length) {
76
+ throw new Error(`Multiple fragments with the name(s) "${duplicateFragmentNames.join(', ')}" were found.`);
77
+ }
78
+ return registry;
79
+ }
80
+ /**
81
+ * Creates a BaseVisitor with standard configuration
82
+ */
83
+ function createBaseVisitor(config, schemaObject) {
84
+ return new visitor_plugin_common_1.BaseVisitor(config, {
85
+ scalars: (0, visitor_plugin_common_1.buildScalarsFromConfig)(schemaObject, config),
86
+ dedupeOperationSuffix: (0, visitor_plugin_common_1.getConfigValue)(config.dedupeOperationSuffix, false),
87
+ omitOperationSuffix: (0, visitor_plugin_common_1.getConfigValue)(config.omitOperationSuffix, false),
88
+ fragmentVariablePrefix: (0, visitor_plugin_common_1.getConfigValue)(config.fragmentVariablePrefix, ''),
89
+ fragmentVariableSuffix: (0, visitor_plugin_common_1.getConfigValue)(config.fragmentVariableSuffix, 'FragmentDoc'),
90
+ });
91
+ }
92
+ /**
93
+ * Builds a fragment "resolver" that collects `externalFragments` definitions and `fragmentImportStatements`
94
+ */
95
+ function buildFragmentResolver(collectorOptions, presetOptions, schemaObject, dedupeFragments = false) {
96
+ const { config } = presetOptions;
97
+ const baseVisitor = createBaseVisitor(config, schemaObject);
98
+ const fragmentRegistry = buildFragmentRegistry(baseVisitor, collectorOptions, presetOptions, schemaObject);
99
+ const { baseOutputDir } = presetOptions;
100
+ const { baseDir, typesImport } = collectorOptions;
101
+ function resolveFragments(generatedFilePath, documentFileContent) {
102
+ const { fragmentsInUse, usedFragmentTypes } = (0, utils_js_1.analyzeFragmentUsage)(documentFileContent, fragmentRegistry, schemaObject);
103
+ const externalFragments = [];
104
+ const fragmentFileImports = {};
105
+ for (const [fragmentName, level] of Object.entries(fragmentsInUse)) {
106
+ const fragmentDetails = fragmentRegistry[fragmentName];
107
+ if (!fragmentDetails)
108
+ continue;
109
+ // add top level references to the import object
110
+ // we don't check or global namespace because the calling config can do so
111
+ if (level === 0 ||
112
+ (dedupeFragments &&
113
+ ['OperationDefinition', 'FragmentDefinition'].includes(documentFileContent.definitions[0].kind))) {
114
+ if (fragmentDetails.filePath !== generatedFilePath) {
115
+ // don't emit imports to same location
116
+ const usedTypesForFragment = usedFragmentTypes[fragmentName] || [];
117
+ const filteredImports = createFragmentImports(baseVisitor, fragmentName, fragmentDetails.possibleTypes, usedTypesForFragment);
118
+ if (!fragmentFileImports[fragmentDetails.filePath]) {
119
+ fragmentFileImports[fragmentDetails.filePath] = [];
120
+ }
121
+ fragmentFileImports[fragmentDetails.filePath].push(...filteredImports);
122
+ }
123
+ }
124
+ externalFragments.push({
125
+ level,
126
+ isExternal: true,
127
+ name: fragmentName,
128
+ onType: fragmentDetails.onType,
129
+ node: fragmentDetails.node,
130
+ });
131
+ }
132
+ return {
133
+ externalFragments,
134
+ fragmentImports: Object.entries(fragmentFileImports).map(([fragmentsFilePath, identifiers]) => ({
135
+ baseDir,
136
+ baseOutputDir,
137
+ outputPath: generatedFilePath,
138
+ importSource: {
139
+ path: fragmentsFilePath,
140
+ identifiers,
141
+ },
142
+ emitLegacyCommonJSImports: presetOptions.config.emitLegacyCommonJSImports,
143
+ importExtension: presetOptions.config.importExtension,
144
+ typesImport,
145
+ })),
146
+ };
147
+ }
148
+ return resolveFragments;
149
+ }
package/cjs/index.js ADDED
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preset = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path_1 = require("path");
6
+ const graphql_1 = require("graphql");
7
+ const add_1 = tslib_1.__importDefault(require("@graphql-codegen/add"));
8
+ const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
9
+ const resolve_document_imports_js_1 = require("./resolve-document-imports.js");
10
+ const utils_js_1 = require("./utils.js");
11
+ /**
12
+ * Extract operation and fragment names from a document
13
+ */
14
+ function extractDefinitions(document) {
15
+ const definitions = [];
16
+ for (const def of document.definitions) {
17
+ if (def.kind === graphql_1.Kind.OPERATION_DEFINITION && def.name) {
18
+ definitions.push({ name: def.name.value, definition: def });
19
+ }
20
+ else if (def.kind === graphql_1.Kind.FRAGMENT_DEFINITION) {
21
+ definitions.push({ name: def.name.value, definition: def });
22
+ }
23
+ }
24
+ return definitions;
25
+ }
26
+ exports.preset = {
27
+ buildGeneratesSection: options => {
28
+ var _a;
29
+ const schemaObject = options.schemaAst
30
+ ? options.schemaAst
31
+ : (0, graphql_1.buildASTSchema)(options.schema, options.config);
32
+ const baseDir = options.presetConfig.cwd || process.cwd();
33
+ const extension = options.presetConfig.extension || '.ts';
34
+ const folder = options.presetConfig.folder || '__generated__';
35
+ const importTypesNamespace = options.presetConfig.importTypesNamespace || 'Types';
36
+ const { baseTypesPath } = options.presetConfig;
37
+ if (!baseTypesPath) {
38
+ throw new Error(`Preset "per-operation-file" requires you to specify "baseTypesPath" configuration!`);
39
+ }
40
+ const shouldAbsolute = !baseTypesPath.startsWith('~');
41
+ const pluginMap = {
42
+ ...options.pluginMap,
43
+ add: add_1.default,
44
+ };
45
+ // Resolve fragment dependencies using our adapted fragment resolver
46
+ // Fragment paths will be generated based on fragment names
47
+ const sources = (0, resolve_document_imports_js_1.resolveDocumentImports)(options, schemaObject, {
48
+ baseDir,
49
+ folder,
50
+ extension,
51
+ schemaTypesSource: {
52
+ path: shouldAbsolute ? (0, path_1.join)(options.baseOutputDir, baseTypesPath) : baseTypesPath,
53
+ namespace: importTypesNamespace,
54
+ },
55
+ typesImport: (_a = options.config.useTypeImports) !== null && _a !== void 0 ? _a : false,
56
+ }, (0, visitor_plugin_common_1.getConfigValue)(options.config.dedupeFragments, false));
57
+ const artifacts = [];
58
+ // Now split each source into separate files per operation/fragment
59
+ for (const source of sources) {
60
+ const definitions = extractDefinitions(source.documents[0].document);
61
+ for (const { name, definition } of definitions) {
62
+ const filename = (0, utils_js_1.generateOperationFilePath)(source.documents[0].location, name, folder, extension);
63
+ // Create a document with just this operation/fragment
64
+ const singleDefDocument = {
65
+ kind: graphql_1.Kind.DOCUMENT,
66
+ definitions: [definition],
67
+ };
68
+ const singleDefSource = {
69
+ rawSDL: source.documents[0].rawSDL, // TODO: might need to filter this
70
+ document: singleDefDocument,
71
+ location: source.documents[0].location,
72
+ };
73
+ // Update fragment imports to use the correct output path for this operation
74
+ const updatedFragmentImports = source.fragmentImports.map(fragmentImport => ({
75
+ ...fragmentImport,
76
+ outputPath: filename, // Update to actual output path
77
+ }));
78
+ const plugins = [
79
+ ...(options.config.globalNamespace
80
+ ? []
81
+ : source.importStatements.map(importStatement => ({
82
+ add: { content: importStatement },
83
+ }))),
84
+ ...options.plugins,
85
+ ];
86
+ const config = {
87
+ ...options.config,
88
+ exportFragmentSpreadSubTypes: true,
89
+ namespacedImportName: importTypesNamespace,
90
+ externalFragments: source.externalFragments,
91
+ fragmentImports: updatedFragmentImports,
92
+ };
93
+ artifacts.push({
94
+ ...options,
95
+ filename,
96
+ documents: [singleDefSource],
97
+ plugins,
98
+ pluginMap,
99
+ config,
100
+ schema: options.schema,
101
+ schemaAst: schemaObject,
102
+ skipDocumentsValidation: typeof options.config.skipDocumentsValidation === 'undefined'
103
+ ? { skipDuplicateValidation: true }
104
+ : options.config.skipDocumentsValidation,
105
+ });
106
+ }
107
+ }
108
+ return artifacts;
109
+ },
110
+ };
111
+ exports.default = exports.preset;
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveDocumentImports = resolveDocumentImports;
4
+ const tslib_1 = require("tslib");
5
+ const plugin_helpers_1 = require("@graphql-codegen/plugin-helpers");
6
+ const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
7
+ const fragment_resolver_js_1 = tslib_1.__importDefault(require("./fragment-resolver.js"));
8
+ /**
9
+ * Transform the preset's provided documents into single-file generator sources, while resolving fragment and user-defined imports
10
+ *
11
+ * Resolves user provided imports and fragment imports using the `DocumentImportResolverOptions`.
12
+ * Does not define specific plugins, but rather returns a string[] of `importStatements` for the calling plugin to make use of
13
+ */
14
+ function resolveDocumentImports(presetOptions, schemaObject, importResolverOptions, dedupeFragments = false) {
15
+ const resolveFragments = (0, fragment_resolver_js_1.default)(importResolverOptions, presetOptions, schemaObject, dedupeFragments);
16
+ const { baseOutputDir, documents } = presetOptions;
17
+ const { schemaTypesSource, baseDir, typesImport } = importResolverOptions;
18
+ return documents.map(documentFile => {
19
+ try {
20
+ // NOTE: We pass a placeholder filename here since we'll generate proper filenames per-operation later
21
+ // The important part is that fragment resolution will use the correct paths from the registry
22
+ const placeholderFilePath = documentFile.location;
23
+ const importStatements = [];
24
+ const { externalFragments, fragmentImports } = resolveFragments(placeholderFilePath, documentFile.document);
25
+ const externalFragmentsInjectedDocument = {
26
+ ...documentFile.document,
27
+ definitions: [
28
+ ...documentFile.document.definitions,
29
+ ...externalFragments.map(fragment => fragment.node),
30
+ ],
31
+ };
32
+ if ((0, plugin_helpers_1.isUsingTypes)(externalFragmentsInjectedDocument, [], schemaObject)) {
33
+ const schemaTypesImportStatement = (0, visitor_plugin_common_1.generateImportStatement)({
34
+ baseDir,
35
+ emitLegacyCommonJSImports: presetOptions.config.emitLegacyCommonJSImports,
36
+ importExtension: presetOptions.config.importExtension,
37
+ importSource: (0, visitor_plugin_common_1.resolveImportSource)(schemaTypesSource),
38
+ baseOutputDir,
39
+ outputPath: placeholderFilePath,
40
+ typesImport,
41
+ });
42
+ importStatements.unshift(schemaTypesImportStatement);
43
+ }
44
+ return {
45
+ filename: placeholderFilePath,
46
+ documents: [documentFile],
47
+ importStatements,
48
+ fragmentImports,
49
+ externalFragments,
50
+ };
51
+ }
52
+ catch (e) {
53
+ throw new Error(`Unable to validate GraphQL document! \n
54
+ File ${documentFile.location} caused error:
55
+ ${e.message || e.toString()}`);
56
+ }
57
+ });
58
+ }
package/cjs/utils.js ADDED
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateOperationFilePath = generateOperationFilePath;
4
+ exports.analyzeFragmentUsage = analyzeFragmentUsage;
5
+ exports.extractExternalFragmentsInUse = extractExternalFragmentsInUse;
6
+ const path_1 = require("path");
7
+ const graphql_1 = require("graphql");
8
+ const visitor_plugin_common_1 = require("@graphql-codegen/visitor-plugin-common");
9
+ /**
10
+ * Generate output file path for a specific operation/fragment
11
+ */
12
+ function generateOperationFilePath(sourceLocation, operationName, folder, extension) {
13
+ const dir = (0, path_1.dirname)(sourceLocation);
14
+ return (0, path_1.join)(dir, folder, `${operationName}${extension}`).replace(/\\/g, '/');
15
+ }
16
+ /**
17
+ * Analyzes fragment usage in a GraphQL document.
18
+ * Returns information about which fragments are used and which specific types they're used with.
19
+ */
20
+ function analyzeFragmentUsage(documentNode, fragmentRegistry, schema) {
21
+ const localFragments = getLocalFragments(documentNode);
22
+ const fragmentsInUse = extractExternalFragmentsInUse(documentNode, fragmentRegistry, localFragments);
23
+ const usedFragmentTypes = analyzeFragmentTypeUsage(documentNode, fragmentRegistry, schema, localFragments, fragmentsInUse);
24
+ return { fragmentsInUse, usedFragmentTypes };
25
+ }
26
+ /**
27
+ * Get all fragment definitions that are local to this document
28
+ */
29
+ function getLocalFragments(documentNode) {
30
+ const localFragments = new Set();
31
+ (0, graphql_1.visit)(documentNode, {
32
+ FragmentDefinition: node => {
33
+ localFragments.add(node.name.value);
34
+ },
35
+ });
36
+ return localFragments;
37
+ }
38
+ function extractExternalFragmentsInUse(documentNode, fragmentNameToFile, localFragment, result = {}, level = 0) {
39
+ // Then, look for all used fragments in this document
40
+ (0, graphql_1.visit)(documentNode, {
41
+ FragmentSpread: node => {
42
+ if (!localFragment.has(node.name.value) &&
43
+ (result[node.name.value] === undefined || level < result[node.name.value])) {
44
+ result[node.name.value] = level;
45
+ if (fragmentNameToFile[node.name.value]) {
46
+ extractExternalFragmentsInUse(fragmentNameToFile[node.name.value].node, fragmentNameToFile, localFragment, result, level + 1);
47
+ }
48
+ }
49
+ },
50
+ });
51
+ return result;
52
+ }
53
+ /**
54
+ * Analyze which specific types each fragment is used with (for polymorphic fragments)
55
+ */
56
+ function analyzeFragmentTypeUsage(documentNode, fragmentRegistry, schema, localFragments, fragmentsInUse) {
57
+ const usedFragmentTypes = {};
58
+ const typeInfo = new graphql_1.TypeInfo(schema);
59
+ (0, graphql_1.visit)(documentNode, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
60
+ Field: (node) => {
61
+ if (!node.selectionSet)
62
+ return;
63
+ const fieldType = typeInfo.getType();
64
+ if (!fieldType)
65
+ return;
66
+ const baseType = getBaseType(fieldType);
67
+ if ((0, graphql_1.isObjectType)(baseType) || (0, graphql_1.isInterfaceType)(baseType) || (0, graphql_1.isUnionType)(baseType)) {
68
+ analyzeSelectionSetTypeContext(node.selectionSet, baseType.name, usedFragmentTypes, fragmentRegistry, schema, localFragments);
69
+ }
70
+ },
71
+ }));
72
+ const result = {};
73
+ // Fill in missing types for multi-type fragments
74
+ for (const fragmentName in fragmentsInUse) {
75
+ const fragment = fragmentRegistry[fragmentName];
76
+ if (!fragment || fragment.possibleTypes.length <= 1)
77
+ continue;
78
+ const usedTypes = usedFragmentTypes[fragmentName];
79
+ result[fragmentName] = (usedTypes === null || usedTypes === void 0 ? void 0 : usedTypes.size) > 0 ? Array.from(usedTypes) : fragment.possibleTypes;
80
+ }
81
+ return result;
82
+ }
83
+ /**
84
+ * Analyze fragment usage within a specific selection set and type context
85
+ */
86
+ function analyzeSelectionSetTypeContext(selectionSet, currentTypeName, usedFragmentTypes, fragmentRegistry, schema, localFragments) {
87
+ var _a, _b;
88
+ var _c;
89
+ const { spreads, inlines } = (0, visitor_plugin_common_1.separateSelectionSet)(selectionSet.selections);
90
+ // Process fragment spreads in this type context
91
+ for (const spread of spreads) {
92
+ if (localFragments.has(spread.name.value))
93
+ continue;
94
+ const fragment = fragmentRegistry[spread.name.value];
95
+ if (!fragment || fragment.possibleTypes.length <= 1)
96
+ continue;
97
+ const currentType = schema.getType(currentTypeName);
98
+ if (!currentType)
99
+ continue;
100
+ const possibleTypes = (0, visitor_plugin_common_1.getPossibleTypes)(schema, currentType).map(t => t.name);
101
+ const matchingTypes = possibleTypes.filter(type => fragment.possibleTypes.includes(type));
102
+ if (matchingTypes.length > 0) {
103
+ const typeSet = ((_a = usedFragmentTypes[_c = spread.name.value]) !== null && _a !== void 0 ? _a : (usedFragmentTypes[_c] = new Set()));
104
+ matchingTypes.forEach(type => typeSet.add(type));
105
+ }
106
+ }
107
+ // Process inline fragments
108
+ for (const inline of inlines) {
109
+ if (((_b = inline.typeCondition) === null || _b === void 0 ? void 0 : _b.name.value) && inline.selectionSet) {
110
+ analyzeSelectionSetTypeContext(inline.selectionSet, inline.typeCondition.name.value, usedFragmentTypes, fragmentRegistry, schema, localFragments);
111
+ }
112
+ }
113
+ }
114
+ function getBaseType(type) {
115
+ let baseType = type;
116
+ while ('ofType' in baseType && baseType.ofType) {
117
+ baseType = baseType.ofType;
118
+ }
119
+ return baseType;
120
+ }