@nmarks/graphql-codegen-per-operation-file-preset 1.0.5 → 1.0.7
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 +243 -54
- package/esm/index.js +243 -54
- package/package.json +1 -1
- package/typings/index.d.cts +15 -0
- package/typings/index.d.ts +15 -0
package/cjs/index.js
CHANGED
|
@@ -24,97 +24,178 @@ function extractDefinitions(document) {
|
|
|
24
24
|
return definitions;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* - Enums
|
|
30
|
-
* - Custom scalars that aren't mapped to primitives
|
|
31
|
-
* - Input types (in variables)
|
|
27
|
+
* Finds "local" fragments - fragments defined in the same source file as the current definition.
|
|
28
|
+
* These are initially local but become external after we split the file per-operation.
|
|
32
29
|
*
|
|
33
|
-
*
|
|
30
|
+
* Example:
|
|
31
|
+
* Source file has: query MyQuery { ...MyFragment } + fragment MyFragment { ... }
|
|
32
|
+
* After split: MyQuery.ts needs to import from MyFragment.ts
|
|
33
|
+
*/
|
|
34
|
+
function findLocalFragments(allDefinitions, currentDefinitionName) {
|
|
35
|
+
return allDefinitions
|
|
36
|
+
.filter(d => d.definition.kind === graphql_1.Kind.FRAGMENT_DEFINITION &&
|
|
37
|
+
d.name !== currentDefinitionName)
|
|
38
|
+
.map(d => d.definition);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Creates LoadedFragment objects for local fragments so they can be treated as external fragments
|
|
42
|
+
* during code generation. This ensures proper validation and type generation.
|
|
43
|
+
*/
|
|
44
|
+
function createLoadedFragments(fragments) {
|
|
45
|
+
return fragments.map(frag => ({
|
|
46
|
+
level: 0,
|
|
47
|
+
isExternal: true,
|
|
48
|
+
name: frag.name.value,
|
|
49
|
+
onType: frag.typeCondition.name.value,
|
|
50
|
+
node: frag,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Creates fragment import declarations for local fragments.
|
|
55
|
+
* After splitting, these fragments will be in separate files, so we need to generate imports.
|
|
56
|
+
*
|
|
57
|
+
* Example: query references RetailerServicesData → import from './RetailerServicesData.ts'
|
|
58
|
+
*/
|
|
59
|
+
function createLocalFragmentImports(localFragments, sourceLocation, currentFilename, folder, extension, options) {
|
|
60
|
+
return localFragments.map((frag) => {
|
|
61
|
+
var _a;
|
|
62
|
+
const fragmentFilePath = (0, utils_js_1.generateOperationFilePath)(sourceLocation, frag.name.value, folder, extension);
|
|
63
|
+
// Generate import identifiers: both the document (for runtime) and type (for TypeScript)
|
|
64
|
+
const identifiers = [
|
|
65
|
+
{
|
|
66
|
+
name: `${frag.name.value}FragmentDoc`,
|
|
67
|
+
kind: 'document',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: `${frag.name.value}Fragment`,
|
|
71
|
+
kind: 'type',
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
return {
|
|
75
|
+
baseDir: options.baseDir,
|
|
76
|
+
baseOutputDir: options.baseOutputDir,
|
|
77
|
+
outputPath: currentFilename,
|
|
78
|
+
importSource: {
|
|
79
|
+
path: fragmentFilePath,
|
|
80
|
+
identifiers,
|
|
81
|
+
},
|
|
82
|
+
emitLegacyCommonJSImports: options.emitLegacyCommonJSImports,
|
|
83
|
+
importExtension: options.importExtension,
|
|
84
|
+
typesImport: (_a = options.useTypeImports) !== null && _a !== void 0 ? _a : false,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* TYPES IMPORT OPTIMIZATION
|
|
90
|
+
*
|
|
91
|
+
* Determines if `import * as Types from '...'` is actually needed for this specific operation/fragment.
|
|
92
|
+
*
|
|
93
|
+
* Background:
|
|
94
|
+
* The standard `isUsingTypes()` function is too broad - it returns true for ANY schema type reference,
|
|
95
|
+
* including object types. But object types don't need imports since they're generated inline.
|
|
96
|
+
*
|
|
97
|
+
* We ONLY need the Types import when generated code actually references the Types namespace:
|
|
98
|
+
*
|
|
99
|
+
* ✅ NEEDS IMPORT:
|
|
100
|
+
* - Operations (query/mutation/subscription) - generate `Types.Exact<{...}>` for Variables type
|
|
101
|
+
* - Enums - generate `role: Types.Role`
|
|
102
|
+
* - Custom scalars (unless mapped to primitives) - generate `date: Types.DateTime`
|
|
103
|
+
* - Input types - used in variables as `Types.MyInput`
|
|
104
|
+
*
|
|
105
|
+
* ❌ NO IMPORT NEEDED:
|
|
106
|
+
* - Fragments with only scalars/object types - no Types namespace references
|
|
107
|
+
* - Custom scalars mapped to primitives (JSON → any) - uses TypeScript primitives
|
|
108
|
+
* - Object type references (User, Product) - generated inline, not from Types
|
|
109
|
+
*
|
|
110
|
+
* This optimization reduces unnecessary imports and makes generated files cleaner.
|
|
34
111
|
*/
|
|
35
112
|
function needsSchemaTypesImport(document, schema, config) {
|
|
36
113
|
const builtInScalars = ['String', 'Int', 'Float', 'Boolean', 'ID'];
|
|
37
114
|
const primitiveTypes = ['any', 'string', 'number', 'boolean', 'null', 'undefined', 'void', 'never', 'unknown'];
|
|
38
115
|
let needsImport = false;
|
|
39
|
-
// Get scalar mappings from config
|
|
40
116
|
const scalarMappings = config.scalars || {};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
117
|
+
/**
|
|
118
|
+
* STEP 1: Check for operations (queries/mutations/subscriptions)
|
|
119
|
+
* All operations generate a Variables type that uses Types.Exact, even without variables:
|
|
120
|
+
* `export type MyQueryVariables = Types.Exact<{ [key: string]: never; }>;`
|
|
121
|
+
*/
|
|
44
122
|
let hasOperation = false;
|
|
45
|
-
// Collect all type names referenced in the document
|
|
46
123
|
const referencedTypeNames = new Set();
|
|
47
124
|
(0, graphql_1.visit)(document, {
|
|
48
|
-
// Operations always need Types import for their Variables type
|
|
49
125
|
OperationDefinition: () => {
|
|
50
126
|
hasOperation = true;
|
|
51
127
|
},
|
|
52
|
-
// Check variable types for input types or custom scalars
|
|
53
128
|
VariableDefinition: (node) => {
|
|
54
129
|
const typeName = getBaseTypeName(node.type);
|
|
55
130
|
referencedTypeNames.add(typeName);
|
|
56
131
|
},
|
|
57
|
-
// Collect fragment type conditions
|
|
58
132
|
FragmentDefinition: (node) => {
|
|
59
133
|
referencedTypeNames.add(node.typeCondition.name.value);
|
|
60
134
|
},
|
|
61
135
|
});
|
|
62
|
-
// All operations generate Variables type that uses Types.Exact
|
|
63
136
|
if (hasOperation) {
|
|
64
137
|
return true;
|
|
65
138
|
}
|
|
66
|
-
|
|
139
|
+
/**
|
|
140
|
+
* STEP 2: Check if any schema types actually need importing
|
|
141
|
+
*
|
|
142
|
+
* Helper that checks if a GraphQL type requires an import from Types namespace.
|
|
143
|
+
* Returns true only for enums, custom scalars (unless mapped to primitives), and input types.
|
|
144
|
+
*/
|
|
67
145
|
const checkType = (type) => {
|
|
68
146
|
if (!type)
|
|
69
147
|
return false;
|
|
70
|
-
// Unwrap
|
|
148
|
+
// Unwrap wrapper types (List, NonNull)
|
|
71
149
|
while ('ofType' in type && type.ofType) {
|
|
72
150
|
type = type.ofType;
|
|
73
151
|
}
|
|
74
|
-
// Enums always need
|
|
152
|
+
// Enums always need import: `role: Types.Role`
|
|
75
153
|
if ((0, graphql_1.isEnumType)(type)) {
|
|
76
154
|
return true;
|
|
77
155
|
}
|
|
78
|
-
// Custom scalars
|
|
156
|
+
// Custom scalars - depends on mapping
|
|
79
157
|
if ((0, graphql_1.isScalarType)(type) && !builtInScalars.includes(type.name)) {
|
|
80
|
-
// Check if this scalar is mapped to a primitive type
|
|
81
158
|
const mapping = scalarMappings[type.name];
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
// If mapped to an object like { input: 'any', output: 'any' }, check both
|
|
88
|
-
if (typeof mapping === 'object') {
|
|
89
|
-
const inputMapping = mapping.input || mapping.output;
|
|
90
|
-
const outputMapping = mapping.output || mapping.input;
|
|
91
|
-
if (primitiveTypes.includes(inputMapping) && primitiveTypes.includes(outputMapping)) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
159
|
+
// No mapping = defaults to 'any' = no import needed
|
|
160
|
+
if (!mapping) {
|
|
161
|
+
return false;
|
|
95
162
|
}
|
|
96
|
-
|
|
97
|
-
|
|
163
|
+
// Mapped to primitive (string, any, number, etc.) = no import needed
|
|
164
|
+
if (typeof mapping === 'string' && primitiveTypes.includes(mapping)) {
|
|
98
165
|
return false;
|
|
99
166
|
}
|
|
100
|
-
//
|
|
167
|
+
// Mapped to object with input/output
|
|
168
|
+
if (typeof mapping === 'object') {
|
|
169
|
+
const inputMapping = mapping.input || mapping.output;
|
|
170
|
+
const outputMapping = mapping.output || mapping.input;
|
|
171
|
+
if (primitiveTypes.includes(inputMapping) && primitiveTypes.includes(outputMapping)) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Scalar mapped to custom type (e.g., Date) = needs import
|
|
101
176
|
return true;
|
|
102
177
|
}
|
|
103
|
-
// Input types need
|
|
178
|
+
// Input types always need import: `input: Types.MyInput`
|
|
104
179
|
if ((0, graphql_1.isInputObjectType)(type)) {
|
|
105
180
|
return true;
|
|
106
181
|
}
|
|
107
182
|
return false;
|
|
108
183
|
};
|
|
109
|
-
|
|
184
|
+
/**
|
|
185
|
+
* STEP 3: Check variable types
|
|
186
|
+
* Variables can use enums, input types, or custom scalars directly
|
|
187
|
+
*/
|
|
110
188
|
for (const typeName of referencedTypeNames) {
|
|
111
189
|
const type = schema.getType(typeName);
|
|
112
190
|
if (checkType(type)) {
|
|
113
191
|
return true;
|
|
114
192
|
}
|
|
115
193
|
}
|
|
116
|
-
|
|
117
|
-
|
|
194
|
+
/**
|
|
195
|
+
* STEP 4: Check field return types
|
|
196
|
+
* Use TypeInfo to properly resolve field types through the schema.
|
|
197
|
+
* This catches cases like: user { role } where 'role' returns an enum.
|
|
198
|
+
*/
|
|
118
199
|
const typeInfo = new graphql_1.TypeInfo(schema);
|
|
119
200
|
(0, graphql_1.visit)(document, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
|
|
120
201
|
Field: () => {
|
|
@@ -138,12 +219,28 @@ function getBaseTypeName(type) {
|
|
|
138
219
|
}
|
|
139
220
|
return '';
|
|
140
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* PER-OPERATION-FILE PRESET
|
|
224
|
+
*
|
|
225
|
+
* Generates ONE FILE PER OPERATION/FRAGMENT instead of one file per source file.
|
|
226
|
+
*
|
|
227
|
+
* Architecture:
|
|
228
|
+
* 1. Fragment Resolution: Analyze all documents to build fragment registry and dependencies
|
|
229
|
+
* 2. Document Splitting: Split each source document into separate operations/fragments
|
|
230
|
+
* 3. Local Fragment Handling: Treat same-file fragments as external after splitting
|
|
231
|
+
* 4. Import Optimization: Only add Types import when actually needed (enums, scalars, operations)
|
|
232
|
+
*
|
|
233
|
+
* Example:
|
|
234
|
+
* Input: src/user.ts with query GetUser + fragment UserFields
|
|
235
|
+
* Output: src/__generated__/GetUser.ts + src/__generated__/UserFields.ts
|
|
236
|
+
*/
|
|
141
237
|
exports.preset = {
|
|
142
238
|
buildGeneratesSection: options => {
|
|
143
239
|
var _a, _b;
|
|
144
240
|
const schemaObject = options.schemaAst
|
|
145
241
|
? options.schemaAst
|
|
146
242
|
: (0, graphql_1.buildASTSchema)(options.schema, options.config);
|
|
243
|
+
// Extract configuration with defaults
|
|
147
244
|
const baseDir = options.presetConfig.cwd || process.cwd();
|
|
148
245
|
const extension = options.presetConfig.extension || '.ts';
|
|
149
246
|
const folder = options.presetConfig.folder || '__generated__';
|
|
@@ -157,8 +254,13 @@ exports.preset = {
|
|
|
157
254
|
...options.pluginMap,
|
|
158
255
|
add: add_1.default,
|
|
159
256
|
};
|
|
160
|
-
|
|
161
|
-
|
|
257
|
+
/**
|
|
258
|
+
* PHASE 1: FRAGMENT RESOLUTION
|
|
259
|
+
*
|
|
260
|
+
* Analyze all documents to build fragment registry and resolve dependencies.
|
|
261
|
+
* This uses our adapted fragment-resolver that generates paths based on fragment NAMES
|
|
262
|
+
* (not source file names), enabling proper imports after splitting.
|
|
263
|
+
*/
|
|
162
264
|
const sources = (0, resolve_document_imports_js_1.resolveDocumentImports)(options, schemaObject, {
|
|
163
265
|
baseDir,
|
|
164
266
|
folder,
|
|
@@ -170,36 +272,99 @@ exports.preset = {
|
|
|
170
272
|
typesImport: (_a = options.config.useTypeImports) !== null && _a !== void 0 ? _a : false,
|
|
171
273
|
}, (0, visitor_plugin_common_1.getConfigValue)(options.config.dedupeFragments, false));
|
|
172
274
|
const artifacts = [];
|
|
173
|
-
|
|
275
|
+
/**
|
|
276
|
+
* MAIN SPLITTING LOGIC
|
|
277
|
+
*
|
|
278
|
+
* For each source document (input file), we:
|
|
279
|
+
* 1. Extract all operations and fragments
|
|
280
|
+
* 2. Create a separate output file for EACH operation/fragment
|
|
281
|
+
* 3. Handle fragment dependencies (both external and local)
|
|
282
|
+
*/
|
|
174
283
|
for (const source of sources) {
|
|
175
284
|
const definitions = extractDefinitions(source.documents[0].document);
|
|
285
|
+
// Process each operation/fragment definition separately
|
|
176
286
|
for (const { name, definition } of definitions) {
|
|
287
|
+
// Generate output file path: sourceDir/folder/OperationName.extension
|
|
177
288
|
const filename = (0, utils_js_1.generateOperationFilePath)(source.documents[0].location, name, folder, extension);
|
|
178
|
-
// Create a document with
|
|
289
|
+
// Create a document with ONLY this single definition
|
|
179
290
|
const singleDefDocument = {
|
|
180
291
|
kind: graphql_1.Kind.DOCUMENT,
|
|
181
292
|
definitions: [definition],
|
|
182
293
|
};
|
|
294
|
+
// Note: Initially create source with just the single definition
|
|
295
|
+
// We'll update it later to include external fragments
|
|
183
296
|
const singleDefSource = {
|
|
184
|
-
rawSDL: source.documents[0].rawSDL, //
|
|
185
|
-
document: singleDefDocument,
|
|
297
|
+
rawSDL: source.documents[0].rawSDL, // Contains all original SDL (not filtered)
|
|
298
|
+
document: singleDefDocument, // Initially just this one definition
|
|
186
299
|
location: source.documents[0].location,
|
|
187
300
|
};
|
|
188
|
-
|
|
301
|
+
/**
|
|
302
|
+
* HANDLE LOCAL FRAGMENTS
|
|
303
|
+
*
|
|
304
|
+
* Problem: If source file has both query + fragment in same gql template:
|
|
305
|
+
* gql`query MyQuery { ...MyFrag } fragment MyFrag { ... }`
|
|
306
|
+
*
|
|
307
|
+
* After splitting, MyQuery needs to import from MyFrag.ts, but fragment
|
|
308
|
+
* resolver marked MyFrag as "local" (same document).
|
|
309
|
+
*
|
|
310
|
+
* Solution: Treat local fragments as external after splitting.
|
|
311
|
+
*/
|
|
312
|
+
const localFragments = findLocalFragments(definitions, name);
|
|
313
|
+
const localFragmentNodes = createLoadedFragments(localFragments);
|
|
314
|
+
// Combine external fragments (from other files) + local fragments (same source file)
|
|
315
|
+
const allExternalFragments = [
|
|
316
|
+
...source.externalFragments,
|
|
317
|
+
...localFragmentNodes,
|
|
318
|
+
];
|
|
319
|
+
/**
|
|
320
|
+
* UPDATE FRAGMENT IMPORTS
|
|
321
|
+
*
|
|
322
|
+
* 1. External fragments: Update outputPath to this operation's file
|
|
323
|
+
* 2. Local fragments: Generate new imports pointing to their split files
|
|
324
|
+
*/
|
|
189
325
|
const updatedFragmentImports = source.fragmentImports.map(fragmentImport => ({
|
|
190
326
|
...fragmentImport,
|
|
191
|
-
outputPath: filename,
|
|
327
|
+
outputPath: filename,
|
|
192
328
|
}));
|
|
193
|
-
|
|
329
|
+
const localFragmentImports = createLocalFragmentImports(localFragments, source.documents[0].location, filename, folder, extension, {
|
|
330
|
+
baseDir,
|
|
331
|
+
baseOutputDir: options.baseOutputDir,
|
|
332
|
+
emitLegacyCommonJSImports: options.config.emitLegacyCommonJSImports,
|
|
333
|
+
importExtension: options.config.importExtension,
|
|
334
|
+
useTypeImports: options.config.useTypeImports,
|
|
335
|
+
});
|
|
336
|
+
const allFragmentImports = [
|
|
337
|
+
...updatedFragmentImports,
|
|
338
|
+
...localFragmentImports,
|
|
339
|
+
];
|
|
340
|
+
/**
|
|
341
|
+
* ADD EXTERNAL FRAGMENTS TO DOCUMENT
|
|
342
|
+
*
|
|
343
|
+
* The plugin needs external fragment definitions in the document to properly resolve types.
|
|
344
|
+
* Without these, fragment spreads can't be expanded correctly.
|
|
345
|
+
*/
|
|
194
346
|
const singleDefDocumentWithFragments = {
|
|
195
347
|
...singleDefDocument,
|
|
196
348
|
definitions: [
|
|
197
349
|
...singleDefDocument.definitions,
|
|
198
|
-
...
|
|
350
|
+
...allExternalFragments.map(fragment => fragment.node),
|
|
199
351
|
],
|
|
200
352
|
};
|
|
353
|
+
// Update the source to use the document with external fragments
|
|
354
|
+
singleDefSource.document = singleDefDocumentWithFragments;
|
|
355
|
+
/**
|
|
356
|
+
* CHECK IF TYPES IMPORT IS NEEDED
|
|
357
|
+
*
|
|
358
|
+
* We only add `import * as Types from '...'` if the operation actually uses:
|
|
359
|
+
* - Enums (e.g., Role.ADMIN)
|
|
360
|
+
* - Custom scalars mapped to non-primitives
|
|
361
|
+
* - Input types (in variables)
|
|
362
|
+
* - Operations (all operations generate Variables type using Types.Exact)
|
|
363
|
+
*
|
|
364
|
+
* We DON'T import for just object type references (e.g., fragment on User)
|
|
365
|
+
*/
|
|
201
366
|
const needsTypesImport = needsSchemaTypesImport(singleDefDocumentWithFragments, schemaObject, options.config);
|
|
202
|
-
// Generate the types import statement if needed
|
|
367
|
+
// Generate the schema types import statement if needed
|
|
203
368
|
const importStatements = [];
|
|
204
369
|
if (needsTypesImport && !options.config.globalNamespace) {
|
|
205
370
|
const schemaTypesImportStatement = (0, visitor_plugin_common_1.generateImportStatement)({
|
|
@@ -216,6 +381,13 @@ exports.preset = {
|
|
|
216
381
|
});
|
|
217
382
|
importStatements.push(schemaTypesImportStatement);
|
|
218
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* BUILD PLUGIN CONFIGURATION
|
|
386
|
+
*
|
|
387
|
+
* Plugins are processed in order:
|
|
388
|
+
* 1. 'add' plugin - adds import statements
|
|
389
|
+
* 2. User plugins - generate actual operation code (typescript-operations, etc.)
|
|
390
|
+
*/
|
|
219
391
|
const plugins = [
|
|
220
392
|
...importStatements.map(importStatement => ({
|
|
221
393
|
add: { content: importStatement },
|
|
@@ -226,9 +398,26 @@ exports.preset = {
|
|
|
226
398
|
...options.config,
|
|
227
399
|
exportFragmentSpreadSubTypes: true,
|
|
228
400
|
namespacedImportName: importTypesNamespace,
|
|
229
|
-
externalFragments:
|
|
230
|
-
fragmentImports:
|
|
401
|
+
externalFragments: allExternalFragments, // Includes both external + local fragments
|
|
402
|
+
fragmentImports: allFragmentImports, // Import declarations for all fragments
|
|
231
403
|
};
|
|
404
|
+
// DEBUG: Log fragment generation details
|
|
405
|
+
if (name === 'Items_item') {
|
|
406
|
+
console.log('\n=== GENERATING Items_item ===');
|
|
407
|
+
console.log('External fragments:', allExternalFragments.map(f => ({ name: f.name, level: f.level })));
|
|
408
|
+
console.log('Fragment imports:', allFragmentImports.map(fi => fi.importSource.path));
|
|
409
|
+
console.log('Document selections:', definition.selectionSet.selections.map((s) => s.kind === 'Field' ? s.name.value : `...${s.name.value}`));
|
|
410
|
+
console.log('Document definitions (should be 1):', singleDefDocument.definitions.length);
|
|
411
|
+
console.log('singleDefDocumentWithFragments definitions:', singleDefDocumentWithFragments.definitions.length);
|
|
412
|
+
console.log(' - Main definition:', singleDefDocumentWithFragments.definitions[0].kind);
|
|
413
|
+
console.log(' - External fragments added:', singleDefDocumentWithFragments.definitions.slice(1).map((d) => d.name.value));
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* CREATE GENERATION ARTIFACT
|
|
417
|
+
*
|
|
418
|
+
* Each artifact represents one output file with its configuration.
|
|
419
|
+
* The codegen core will process each artifact through the plugin pipeline.
|
|
420
|
+
*/
|
|
232
421
|
artifacts.push({
|
|
233
422
|
...options,
|
|
234
423
|
filename,
|
|
@@ -239,7 +428,7 @@ exports.preset = {
|
|
|
239
428
|
schema: options.schema,
|
|
240
429
|
schemaAst: schemaObject,
|
|
241
430
|
skipDocumentsValidation: typeof options.config.skipDocumentsValidation === 'undefined'
|
|
242
|
-
? { skipDuplicateValidation: true }
|
|
431
|
+
? { skipDuplicateValidation: true } // Skip duplicate validation by default
|
|
243
432
|
: options.config.skipDocumentsValidation,
|
|
244
433
|
});
|
|
245
434
|
}
|
package/esm/index.js
CHANGED
|
@@ -20,97 +20,178 @@ function extractDefinitions(document) {
|
|
|
20
20
|
return definitions;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* - Enums
|
|
26
|
-
* - Custom scalars that aren't mapped to primitives
|
|
27
|
-
* - Input types (in variables)
|
|
23
|
+
* Finds "local" fragments - fragments defined in the same source file as the current definition.
|
|
24
|
+
* These are initially local but become external after we split the file per-operation.
|
|
28
25
|
*
|
|
29
|
-
*
|
|
26
|
+
* Example:
|
|
27
|
+
* Source file has: query MyQuery { ...MyFragment } + fragment MyFragment { ... }
|
|
28
|
+
* After split: MyQuery.ts needs to import from MyFragment.ts
|
|
29
|
+
*/
|
|
30
|
+
function findLocalFragments(allDefinitions, currentDefinitionName) {
|
|
31
|
+
return allDefinitions
|
|
32
|
+
.filter(d => d.definition.kind === Kind.FRAGMENT_DEFINITION &&
|
|
33
|
+
d.name !== currentDefinitionName)
|
|
34
|
+
.map(d => d.definition);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Creates LoadedFragment objects for local fragments so they can be treated as external fragments
|
|
38
|
+
* during code generation. This ensures proper validation and type generation.
|
|
39
|
+
*/
|
|
40
|
+
function createLoadedFragments(fragments) {
|
|
41
|
+
return fragments.map(frag => ({
|
|
42
|
+
level: 0,
|
|
43
|
+
isExternal: true,
|
|
44
|
+
name: frag.name.value,
|
|
45
|
+
onType: frag.typeCondition.name.value,
|
|
46
|
+
node: frag,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates fragment import declarations for local fragments.
|
|
51
|
+
* After splitting, these fragments will be in separate files, so we need to generate imports.
|
|
52
|
+
*
|
|
53
|
+
* Example: query references RetailerServicesData → import from './RetailerServicesData.ts'
|
|
54
|
+
*/
|
|
55
|
+
function createLocalFragmentImports(localFragments, sourceLocation, currentFilename, folder, extension, options) {
|
|
56
|
+
return localFragments.map((frag) => {
|
|
57
|
+
var _a;
|
|
58
|
+
const fragmentFilePath = generateOperationFilePath(sourceLocation, frag.name.value, folder, extension);
|
|
59
|
+
// Generate import identifiers: both the document (for runtime) and type (for TypeScript)
|
|
60
|
+
const identifiers = [
|
|
61
|
+
{
|
|
62
|
+
name: `${frag.name.value}FragmentDoc`,
|
|
63
|
+
kind: 'document',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: `${frag.name.value}Fragment`,
|
|
67
|
+
kind: 'type',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
return {
|
|
71
|
+
baseDir: options.baseDir,
|
|
72
|
+
baseOutputDir: options.baseOutputDir,
|
|
73
|
+
outputPath: currentFilename,
|
|
74
|
+
importSource: {
|
|
75
|
+
path: fragmentFilePath,
|
|
76
|
+
identifiers,
|
|
77
|
+
},
|
|
78
|
+
emitLegacyCommonJSImports: options.emitLegacyCommonJSImports,
|
|
79
|
+
importExtension: options.importExtension,
|
|
80
|
+
typesImport: (_a = options.useTypeImports) !== null && _a !== void 0 ? _a : false,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* TYPES IMPORT OPTIMIZATION
|
|
86
|
+
*
|
|
87
|
+
* Determines if `import * as Types from '...'` is actually needed for this specific operation/fragment.
|
|
88
|
+
*
|
|
89
|
+
* Background:
|
|
90
|
+
* The standard `isUsingTypes()` function is too broad - it returns true for ANY schema type reference,
|
|
91
|
+
* including object types. But object types don't need imports since they're generated inline.
|
|
92
|
+
*
|
|
93
|
+
* We ONLY need the Types import when generated code actually references the Types namespace:
|
|
94
|
+
*
|
|
95
|
+
* ✅ NEEDS IMPORT:
|
|
96
|
+
* - Operations (query/mutation/subscription) - generate `Types.Exact<{...}>` for Variables type
|
|
97
|
+
* - Enums - generate `role: Types.Role`
|
|
98
|
+
* - Custom scalars (unless mapped to primitives) - generate `date: Types.DateTime`
|
|
99
|
+
* - Input types - used in variables as `Types.MyInput`
|
|
100
|
+
*
|
|
101
|
+
* ❌ NO IMPORT NEEDED:
|
|
102
|
+
* - Fragments with only scalars/object types - no Types namespace references
|
|
103
|
+
* - Custom scalars mapped to primitives (JSON → any) - uses TypeScript primitives
|
|
104
|
+
* - Object type references (User, Product) - generated inline, not from Types
|
|
105
|
+
*
|
|
106
|
+
* This optimization reduces unnecessary imports and makes generated files cleaner.
|
|
30
107
|
*/
|
|
31
108
|
function needsSchemaTypesImport(document, schema, config) {
|
|
32
109
|
const builtInScalars = ['String', 'Int', 'Float', 'Boolean', 'ID'];
|
|
33
110
|
const primitiveTypes = ['any', 'string', 'number', 'boolean', 'null', 'undefined', 'void', 'never', 'unknown'];
|
|
34
111
|
let needsImport = false;
|
|
35
|
-
// Get scalar mappings from config
|
|
36
112
|
const scalarMappings = config.scalars || {};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
113
|
+
/**
|
|
114
|
+
* STEP 1: Check for operations (queries/mutations/subscriptions)
|
|
115
|
+
* All operations generate a Variables type that uses Types.Exact, even without variables:
|
|
116
|
+
* `export type MyQueryVariables = Types.Exact<{ [key: string]: never; }>;`
|
|
117
|
+
*/
|
|
40
118
|
let hasOperation = false;
|
|
41
|
-
// Collect all type names referenced in the document
|
|
42
119
|
const referencedTypeNames = new Set();
|
|
43
120
|
visit(document, {
|
|
44
|
-
// Operations always need Types import for their Variables type
|
|
45
121
|
OperationDefinition: () => {
|
|
46
122
|
hasOperation = true;
|
|
47
123
|
},
|
|
48
|
-
// Check variable types for input types or custom scalars
|
|
49
124
|
VariableDefinition: (node) => {
|
|
50
125
|
const typeName = getBaseTypeName(node.type);
|
|
51
126
|
referencedTypeNames.add(typeName);
|
|
52
127
|
},
|
|
53
|
-
// Collect fragment type conditions
|
|
54
128
|
FragmentDefinition: (node) => {
|
|
55
129
|
referencedTypeNames.add(node.typeCondition.name.value);
|
|
56
130
|
},
|
|
57
131
|
});
|
|
58
|
-
// All operations generate Variables type that uses Types.Exact
|
|
59
132
|
if (hasOperation) {
|
|
60
133
|
return true;
|
|
61
134
|
}
|
|
62
|
-
|
|
135
|
+
/**
|
|
136
|
+
* STEP 2: Check if any schema types actually need importing
|
|
137
|
+
*
|
|
138
|
+
* Helper that checks if a GraphQL type requires an import from Types namespace.
|
|
139
|
+
* Returns true only for enums, custom scalars (unless mapped to primitives), and input types.
|
|
140
|
+
*/
|
|
63
141
|
const checkType = (type) => {
|
|
64
142
|
if (!type)
|
|
65
143
|
return false;
|
|
66
|
-
// Unwrap
|
|
144
|
+
// Unwrap wrapper types (List, NonNull)
|
|
67
145
|
while ('ofType' in type && type.ofType) {
|
|
68
146
|
type = type.ofType;
|
|
69
147
|
}
|
|
70
|
-
// Enums always need
|
|
148
|
+
// Enums always need import: `role: Types.Role`
|
|
71
149
|
if (isEnumType(type)) {
|
|
72
150
|
return true;
|
|
73
151
|
}
|
|
74
|
-
// Custom scalars
|
|
152
|
+
// Custom scalars - depends on mapping
|
|
75
153
|
if (isScalarType(type) && !builtInScalars.includes(type.name)) {
|
|
76
|
-
// Check if this scalar is mapped to a primitive type
|
|
77
154
|
const mapping = scalarMappings[type.name];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
// If mapped to an object like { input: 'any', output: 'any' }, check both
|
|
84
|
-
if (typeof mapping === 'object') {
|
|
85
|
-
const inputMapping = mapping.input || mapping.output;
|
|
86
|
-
const outputMapping = mapping.output || mapping.input;
|
|
87
|
-
if (primitiveTypes.includes(inputMapping) && primitiveTypes.includes(outputMapping)) {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
155
|
+
// No mapping = defaults to 'any' = no import needed
|
|
156
|
+
if (!mapping) {
|
|
157
|
+
return false;
|
|
91
158
|
}
|
|
92
|
-
|
|
93
|
-
|
|
159
|
+
// Mapped to primitive (string, any, number, etc.) = no import needed
|
|
160
|
+
if (typeof mapping === 'string' && primitiveTypes.includes(mapping)) {
|
|
94
161
|
return false;
|
|
95
162
|
}
|
|
96
|
-
//
|
|
163
|
+
// Mapped to object with input/output
|
|
164
|
+
if (typeof mapping === 'object') {
|
|
165
|
+
const inputMapping = mapping.input || mapping.output;
|
|
166
|
+
const outputMapping = mapping.output || mapping.input;
|
|
167
|
+
if (primitiveTypes.includes(inputMapping) && primitiveTypes.includes(outputMapping)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Scalar mapped to custom type (e.g., Date) = needs import
|
|
97
172
|
return true;
|
|
98
173
|
}
|
|
99
|
-
// Input types need
|
|
174
|
+
// Input types always need import: `input: Types.MyInput`
|
|
100
175
|
if (isInputObjectType(type)) {
|
|
101
176
|
return true;
|
|
102
177
|
}
|
|
103
178
|
return false;
|
|
104
179
|
};
|
|
105
|
-
|
|
180
|
+
/**
|
|
181
|
+
* STEP 3: Check variable types
|
|
182
|
+
* Variables can use enums, input types, or custom scalars directly
|
|
183
|
+
*/
|
|
106
184
|
for (const typeName of referencedTypeNames) {
|
|
107
185
|
const type = schema.getType(typeName);
|
|
108
186
|
if (checkType(type)) {
|
|
109
187
|
return true;
|
|
110
188
|
}
|
|
111
189
|
}
|
|
112
|
-
|
|
113
|
-
|
|
190
|
+
/**
|
|
191
|
+
* STEP 4: Check field return types
|
|
192
|
+
* Use TypeInfo to properly resolve field types through the schema.
|
|
193
|
+
* This catches cases like: user { role } where 'role' returns an enum.
|
|
194
|
+
*/
|
|
114
195
|
const typeInfo = new TypeInfo(schema);
|
|
115
196
|
visit(document, visitWithTypeInfo(typeInfo, {
|
|
116
197
|
Field: () => {
|
|
@@ -134,12 +215,28 @@ function getBaseTypeName(type) {
|
|
|
134
215
|
}
|
|
135
216
|
return '';
|
|
136
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* PER-OPERATION-FILE PRESET
|
|
220
|
+
*
|
|
221
|
+
* Generates ONE FILE PER OPERATION/FRAGMENT instead of one file per source file.
|
|
222
|
+
*
|
|
223
|
+
* Architecture:
|
|
224
|
+
* 1. Fragment Resolution: Analyze all documents to build fragment registry and dependencies
|
|
225
|
+
* 2. Document Splitting: Split each source document into separate operations/fragments
|
|
226
|
+
* 3. Local Fragment Handling: Treat same-file fragments as external after splitting
|
|
227
|
+
* 4. Import Optimization: Only add Types import when actually needed (enums, scalars, operations)
|
|
228
|
+
*
|
|
229
|
+
* Example:
|
|
230
|
+
* Input: src/user.ts with query GetUser + fragment UserFields
|
|
231
|
+
* Output: src/__generated__/GetUser.ts + src/__generated__/UserFields.ts
|
|
232
|
+
*/
|
|
137
233
|
export const preset = {
|
|
138
234
|
buildGeneratesSection: options => {
|
|
139
235
|
var _a, _b;
|
|
140
236
|
const schemaObject = options.schemaAst
|
|
141
237
|
? options.schemaAst
|
|
142
238
|
: buildASTSchema(options.schema, options.config);
|
|
239
|
+
// Extract configuration with defaults
|
|
143
240
|
const baseDir = options.presetConfig.cwd || process.cwd();
|
|
144
241
|
const extension = options.presetConfig.extension || '.ts';
|
|
145
242
|
const folder = options.presetConfig.folder || '__generated__';
|
|
@@ -153,8 +250,13 @@ export const preset = {
|
|
|
153
250
|
...options.pluginMap,
|
|
154
251
|
add: addPlugin,
|
|
155
252
|
};
|
|
156
|
-
|
|
157
|
-
|
|
253
|
+
/**
|
|
254
|
+
* PHASE 1: FRAGMENT RESOLUTION
|
|
255
|
+
*
|
|
256
|
+
* Analyze all documents to build fragment registry and resolve dependencies.
|
|
257
|
+
* This uses our adapted fragment-resolver that generates paths based on fragment NAMES
|
|
258
|
+
* (not source file names), enabling proper imports after splitting.
|
|
259
|
+
*/
|
|
158
260
|
const sources = resolveDocumentImports(options, schemaObject, {
|
|
159
261
|
baseDir,
|
|
160
262
|
folder,
|
|
@@ -166,36 +268,99 @@ export const preset = {
|
|
|
166
268
|
typesImport: (_a = options.config.useTypeImports) !== null && _a !== void 0 ? _a : false,
|
|
167
269
|
}, getConfigValue(options.config.dedupeFragments, false));
|
|
168
270
|
const artifacts = [];
|
|
169
|
-
|
|
271
|
+
/**
|
|
272
|
+
* MAIN SPLITTING LOGIC
|
|
273
|
+
*
|
|
274
|
+
* For each source document (input file), we:
|
|
275
|
+
* 1. Extract all operations and fragments
|
|
276
|
+
* 2. Create a separate output file for EACH operation/fragment
|
|
277
|
+
* 3. Handle fragment dependencies (both external and local)
|
|
278
|
+
*/
|
|
170
279
|
for (const source of sources) {
|
|
171
280
|
const definitions = extractDefinitions(source.documents[0].document);
|
|
281
|
+
// Process each operation/fragment definition separately
|
|
172
282
|
for (const { name, definition } of definitions) {
|
|
283
|
+
// Generate output file path: sourceDir/folder/OperationName.extension
|
|
173
284
|
const filename = generateOperationFilePath(source.documents[0].location, name, folder, extension);
|
|
174
|
-
// Create a document with
|
|
285
|
+
// Create a document with ONLY this single definition
|
|
175
286
|
const singleDefDocument = {
|
|
176
287
|
kind: Kind.DOCUMENT,
|
|
177
288
|
definitions: [definition],
|
|
178
289
|
};
|
|
290
|
+
// Note: Initially create source with just the single definition
|
|
291
|
+
// We'll update it later to include external fragments
|
|
179
292
|
const singleDefSource = {
|
|
180
|
-
rawSDL: source.documents[0].rawSDL, //
|
|
181
|
-
document: singleDefDocument,
|
|
293
|
+
rawSDL: source.documents[0].rawSDL, // Contains all original SDL (not filtered)
|
|
294
|
+
document: singleDefDocument, // Initially just this one definition
|
|
182
295
|
location: source.documents[0].location,
|
|
183
296
|
};
|
|
184
|
-
|
|
297
|
+
/**
|
|
298
|
+
* HANDLE LOCAL FRAGMENTS
|
|
299
|
+
*
|
|
300
|
+
* Problem: If source file has both query + fragment in same gql template:
|
|
301
|
+
* gql`query MyQuery { ...MyFrag } fragment MyFrag { ... }`
|
|
302
|
+
*
|
|
303
|
+
* After splitting, MyQuery needs to import from MyFrag.ts, but fragment
|
|
304
|
+
* resolver marked MyFrag as "local" (same document).
|
|
305
|
+
*
|
|
306
|
+
* Solution: Treat local fragments as external after splitting.
|
|
307
|
+
*/
|
|
308
|
+
const localFragments = findLocalFragments(definitions, name);
|
|
309
|
+
const localFragmentNodes = createLoadedFragments(localFragments);
|
|
310
|
+
// Combine external fragments (from other files) + local fragments (same source file)
|
|
311
|
+
const allExternalFragments = [
|
|
312
|
+
...source.externalFragments,
|
|
313
|
+
...localFragmentNodes,
|
|
314
|
+
];
|
|
315
|
+
/**
|
|
316
|
+
* UPDATE FRAGMENT IMPORTS
|
|
317
|
+
*
|
|
318
|
+
* 1. External fragments: Update outputPath to this operation's file
|
|
319
|
+
* 2. Local fragments: Generate new imports pointing to their split files
|
|
320
|
+
*/
|
|
185
321
|
const updatedFragmentImports = source.fragmentImports.map(fragmentImport => ({
|
|
186
322
|
...fragmentImport,
|
|
187
|
-
outputPath: filename,
|
|
323
|
+
outputPath: filename,
|
|
188
324
|
}));
|
|
189
|
-
|
|
325
|
+
const localFragmentImports = createLocalFragmentImports(localFragments, source.documents[0].location, filename, folder, extension, {
|
|
326
|
+
baseDir,
|
|
327
|
+
baseOutputDir: options.baseOutputDir,
|
|
328
|
+
emitLegacyCommonJSImports: options.config.emitLegacyCommonJSImports,
|
|
329
|
+
importExtension: options.config.importExtension,
|
|
330
|
+
useTypeImports: options.config.useTypeImports,
|
|
331
|
+
});
|
|
332
|
+
const allFragmentImports = [
|
|
333
|
+
...updatedFragmentImports,
|
|
334
|
+
...localFragmentImports,
|
|
335
|
+
];
|
|
336
|
+
/**
|
|
337
|
+
* ADD EXTERNAL FRAGMENTS TO DOCUMENT
|
|
338
|
+
*
|
|
339
|
+
* The plugin needs external fragment definitions in the document to properly resolve types.
|
|
340
|
+
* Without these, fragment spreads can't be expanded correctly.
|
|
341
|
+
*/
|
|
190
342
|
const singleDefDocumentWithFragments = {
|
|
191
343
|
...singleDefDocument,
|
|
192
344
|
definitions: [
|
|
193
345
|
...singleDefDocument.definitions,
|
|
194
|
-
...
|
|
346
|
+
...allExternalFragments.map(fragment => fragment.node),
|
|
195
347
|
],
|
|
196
348
|
};
|
|
349
|
+
// Update the source to use the document with external fragments
|
|
350
|
+
singleDefSource.document = singleDefDocumentWithFragments;
|
|
351
|
+
/**
|
|
352
|
+
* CHECK IF TYPES IMPORT IS NEEDED
|
|
353
|
+
*
|
|
354
|
+
* We only add `import * as Types from '...'` if the operation actually uses:
|
|
355
|
+
* - Enums (e.g., Role.ADMIN)
|
|
356
|
+
* - Custom scalars mapped to non-primitives
|
|
357
|
+
* - Input types (in variables)
|
|
358
|
+
* - Operations (all operations generate Variables type using Types.Exact)
|
|
359
|
+
*
|
|
360
|
+
* We DON'T import for just object type references (e.g., fragment on User)
|
|
361
|
+
*/
|
|
197
362
|
const needsTypesImport = needsSchemaTypesImport(singleDefDocumentWithFragments, schemaObject, options.config);
|
|
198
|
-
// Generate the types import statement if needed
|
|
363
|
+
// Generate the schema types import statement if needed
|
|
199
364
|
const importStatements = [];
|
|
200
365
|
if (needsTypesImport && !options.config.globalNamespace) {
|
|
201
366
|
const schemaTypesImportStatement = generateImportStatement({
|
|
@@ -212,6 +377,13 @@ export const preset = {
|
|
|
212
377
|
});
|
|
213
378
|
importStatements.push(schemaTypesImportStatement);
|
|
214
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* BUILD PLUGIN CONFIGURATION
|
|
382
|
+
*
|
|
383
|
+
* Plugins are processed in order:
|
|
384
|
+
* 1. 'add' plugin - adds import statements
|
|
385
|
+
* 2. User plugins - generate actual operation code (typescript-operations, etc.)
|
|
386
|
+
*/
|
|
215
387
|
const plugins = [
|
|
216
388
|
...importStatements.map(importStatement => ({
|
|
217
389
|
add: { content: importStatement },
|
|
@@ -222,9 +394,26 @@ export const preset = {
|
|
|
222
394
|
...options.config,
|
|
223
395
|
exportFragmentSpreadSubTypes: true,
|
|
224
396
|
namespacedImportName: importTypesNamespace,
|
|
225
|
-
externalFragments:
|
|
226
|
-
fragmentImports:
|
|
397
|
+
externalFragments: allExternalFragments, // Includes both external + local fragments
|
|
398
|
+
fragmentImports: allFragmentImports, // Import declarations for all fragments
|
|
227
399
|
};
|
|
400
|
+
// DEBUG: Log fragment generation details
|
|
401
|
+
if (name === 'Items_item') {
|
|
402
|
+
console.log('\n=== GENERATING Items_item ===');
|
|
403
|
+
console.log('External fragments:', allExternalFragments.map(f => ({ name: f.name, level: f.level })));
|
|
404
|
+
console.log('Fragment imports:', allFragmentImports.map(fi => fi.importSource.path));
|
|
405
|
+
console.log('Document selections:', definition.selectionSet.selections.map((s) => s.kind === 'Field' ? s.name.value : `...${s.name.value}`));
|
|
406
|
+
console.log('Document definitions (should be 1):', singleDefDocument.definitions.length);
|
|
407
|
+
console.log('singleDefDocumentWithFragments definitions:', singleDefDocumentWithFragments.definitions.length);
|
|
408
|
+
console.log(' - Main definition:', singleDefDocumentWithFragments.definitions[0].kind);
|
|
409
|
+
console.log(' - External fragments added:', singleDefDocumentWithFragments.definitions.slice(1).map((d) => d.name.value));
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* CREATE GENERATION ARTIFACT
|
|
413
|
+
*
|
|
414
|
+
* Each artifact represents one output file with its configuration.
|
|
415
|
+
* The codegen core will process each artifact through the plugin pipeline.
|
|
416
|
+
*/
|
|
228
417
|
artifacts.push({
|
|
229
418
|
...options,
|
|
230
419
|
filename,
|
|
@@ -235,7 +424,7 @@ export const preset = {
|
|
|
235
424
|
schema: options.schema,
|
|
236
425
|
schemaAst: schemaObject,
|
|
237
426
|
skipDocumentsValidation: typeof options.config.skipDocumentsValidation === 'undefined'
|
|
238
|
-
? { skipDuplicateValidation: true }
|
|
427
|
+
? { skipDuplicateValidation: true } // Skip duplicate validation by default
|
|
239
428
|
: options.config.skipDocumentsValidation,
|
|
240
429
|
});
|
|
241
430
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nmarks/graphql-codegen-per-operation-file-preset",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "GraphQL Code Generator preset for generating one file per operation/fragment",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"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"
|
package/typings/index.d.cts
CHANGED
|
@@ -127,5 +127,20 @@ export type PerOperationFileConfig = {
|
|
|
127
127
|
*/
|
|
128
128
|
importTypesNamespace?: string;
|
|
129
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* PER-OPERATION-FILE PRESET
|
|
132
|
+
*
|
|
133
|
+
* Generates ONE FILE PER OPERATION/FRAGMENT instead of one file per source file.
|
|
134
|
+
*
|
|
135
|
+
* Architecture:
|
|
136
|
+
* 1. Fragment Resolution: Analyze all documents to build fragment registry and dependencies
|
|
137
|
+
* 2. Document Splitting: Split each source document into separate operations/fragments
|
|
138
|
+
* 3. Local Fragment Handling: Treat same-file fragments as external after splitting
|
|
139
|
+
* 4. Import Optimization: Only add Types import when actually needed (enums, scalars, operations)
|
|
140
|
+
*
|
|
141
|
+
* Example:
|
|
142
|
+
* Input: src/user.ts with query GetUser + fragment UserFields
|
|
143
|
+
* Output: src/__generated__/GetUser.ts + src/__generated__/UserFields.ts
|
|
144
|
+
*/
|
|
130
145
|
export declare const preset: Types.OutputPreset<PerOperationFileConfig>;
|
|
131
146
|
export default preset;
|
package/typings/index.d.ts
CHANGED
|
@@ -127,5 +127,20 @@ export type PerOperationFileConfig = {
|
|
|
127
127
|
*/
|
|
128
128
|
importTypesNamespace?: string;
|
|
129
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* PER-OPERATION-FILE PRESET
|
|
132
|
+
*
|
|
133
|
+
* Generates ONE FILE PER OPERATION/FRAGMENT instead of one file per source file.
|
|
134
|
+
*
|
|
135
|
+
* Architecture:
|
|
136
|
+
* 1. Fragment Resolution: Analyze all documents to build fragment registry and dependencies
|
|
137
|
+
* 2. Document Splitting: Split each source document into separate operations/fragments
|
|
138
|
+
* 3. Local Fragment Handling: Treat same-file fragments as external after splitting
|
|
139
|
+
* 4. Import Optimization: Only add Types import when actually needed (enums, scalars, operations)
|
|
140
|
+
*
|
|
141
|
+
* Example:
|
|
142
|
+
* Input: src/user.ts with query GetUser + fragment UserFields
|
|
143
|
+
* Output: src/__generated__/GetUser.ts + src/__generated__/UserFields.ts
|
|
144
|
+
*/
|
|
130
145
|
export declare const preset: Types.OutputPreset<PerOperationFileConfig>;
|
|
131
146
|
export default preset;
|