@nmarks/graphql-codegen-per-operation-file-preset 1.0.9 → 1.0.10
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 +113 -19
- package/esm/index.js +113 -19
- package/package.json +1 -1
package/cjs/index.js
CHANGED
|
@@ -24,18 +24,17 @@ function extractDefinitions(document) {
|
|
|
24
24
|
return definitions;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* Example:
|
|
31
|
-
* Source file has: query MyQuery { ...MyFragment } + fragment MyFragment { ... }
|
|
32
|
-
* After split: MyQuery.ts needs to import from MyFragment.ts
|
|
27
|
+
* Collects all fragment names directly referenced by a definition via FragmentSpread nodes.
|
|
28
|
+
* This only gets DIRECT references - not transitive dependencies.
|
|
33
29
|
*/
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
function getDirectlyReferencedFragments(definition) {
|
|
31
|
+
const referencedFragments = new Set();
|
|
32
|
+
(0, graphql_1.visit)(definition, {
|
|
33
|
+
FragmentSpread: (node) => {
|
|
34
|
+
referencedFragments.add(node.name.value);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
return referencedFragments;
|
|
39
38
|
}
|
|
40
39
|
/**
|
|
41
40
|
* Creates LoadedFragment objects for local fragments so they can be treated as external fragments
|
|
@@ -50,6 +49,54 @@ function createLoadedFragments(fragments) {
|
|
|
50
49
|
node: frag,
|
|
51
50
|
}));
|
|
52
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Analyzes fragment dependencies for a definition.
|
|
54
|
+
*
|
|
55
|
+
* Returns two sets:
|
|
56
|
+
* 1. `allNeeded` - ALL fragments needed (including transitive) - for externalFragments
|
|
57
|
+
* 2. `directlyNeeded` - Only DIRECT dependencies - for fragmentImports
|
|
58
|
+
*
|
|
59
|
+
* Why the distinction?
|
|
60
|
+
* - `externalFragments` must include all transitive deps so the plugin can resolve types
|
|
61
|
+
* - `fragmentImports` should only include direct deps; transitive deps are imported by intermediate files
|
|
62
|
+
*
|
|
63
|
+
* Example: Query → FragA → FragB
|
|
64
|
+
* - Query's externalFragments: [FragA, FragB] (plugin needs both)
|
|
65
|
+
* - Query's fragmentImports: [FragA] (only direct; FragA.ts imports FragB)
|
|
66
|
+
*
|
|
67
|
+
* @param definition - The operation/fragment definition to analyze
|
|
68
|
+
* @param externalFragments - All external fragments available (from other files)
|
|
69
|
+
* @param localFragmentMap - Map of local fragment names to their definitions
|
|
70
|
+
*/
|
|
71
|
+
function analyzeFragmentDependencies(definition, externalFragments, localFragmentMap) {
|
|
72
|
+
const allNeeded = new Set();
|
|
73
|
+
// Build a map for quick lookup of external fragment nodes
|
|
74
|
+
const externalFragmentMap = new Map();
|
|
75
|
+
for (const frag of externalFragments) {
|
|
76
|
+
externalFragmentMap.set(frag.name, frag.node);
|
|
77
|
+
}
|
|
78
|
+
function collectTransitive(fragmentNames) {
|
|
79
|
+
for (const name of fragmentNames) {
|
|
80
|
+
if (allNeeded.has(name))
|
|
81
|
+
continue;
|
|
82
|
+
// Check if this fragment exists (either local or external)
|
|
83
|
+
const localDef = localFragmentMap.get(name);
|
|
84
|
+
const externalDef = externalFragmentMap.get(name);
|
|
85
|
+
const fragmentDef = localDef || externalDef;
|
|
86
|
+
if (fragmentDef) {
|
|
87
|
+
allNeeded.add(name);
|
|
88
|
+
// Get transitive dependencies
|
|
89
|
+
const transitiveDeps = getDirectlyReferencedFragments(fragmentDef);
|
|
90
|
+
collectTransitive(transitiveDeps);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Get direct references from the definition
|
|
95
|
+
const directlyNeeded = getDirectlyReferencedFragments(definition);
|
|
96
|
+
// Collect all transitive dependencies (includes direct ones)
|
|
97
|
+
collectTransitive(directlyNeeded);
|
|
98
|
+
return { allNeeded, directlyNeeded };
|
|
99
|
+
}
|
|
53
100
|
/**
|
|
54
101
|
* Creates fragment import declarations for local fragments.
|
|
55
102
|
* After splitting, these fragments will be in separate files, so we need to generate imports.
|
|
@@ -327,25 +374,72 @@ exports.preset = {
|
|
|
327
374
|
* resolver marked MyFrag as "local" (same document).
|
|
328
375
|
*
|
|
329
376
|
* Solution: Treat local fragments as external after splitting.
|
|
377
|
+
* Only include fragments that are actually referenced by this definition.
|
|
330
378
|
*/
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
379
|
+
// Build a map of local fragment names -> definitions for lookup
|
|
380
|
+
const localFragmentMap = new Map();
|
|
381
|
+
for (const d of definitions) {
|
|
382
|
+
if (d.definition.kind === graphql_1.Kind.FRAGMENT_DEFINITION && d.name !== name) {
|
|
383
|
+
localFragmentMap.set(d.name, d.definition);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* ANALYZE FRAGMENT DEPENDENCIES
|
|
388
|
+
*
|
|
389
|
+
* We need two different sets of fragments:
|
|
390
|
+
* 1. allNeeded - ALL transitive deps for externalFragments (plugin needs these for type resolution)
|
|
391
|
+
* 2. directlyNeeded - Only DIRECT deps for fragmentImports (transitive deps handled by intermediate files)
|
|
392
|
+
*
|
|
393
|
+
* Example: Query → FragA → FragB
|
|
394
|
+
* - externalFragments: [FragA, FragB] (plugin needs both for types)
|
|
395
|
+
* - fragmentImports: [FragA] (only direct; FragA.ts already imports FragB)
|
|
396
|
+
*/
|
|
397
|
+
const { allNeeded, directlyNeeded } = analyzeFragmentDependencies(definition, source.externalFragments, localFragmentMap);
|
|
398
|
+
// Filter local fragments to only those in the transitive dependency set
|
|
399
|
+
// (for externalFragments - plugin needs all of them)
|
|
400
|
+
const allLocalFragments = Array.from(localFragmentMap.entries())
|
|
401
|
+
.filter(([fragName]) => allNeeded.has(fragName))
|
|
402
|
+
.map(([, fragDef]) => fragDef);
|
|
403
|
+
const localFragmentNodes = createLoadedFragments(allLocalFragments);
|
|
404
|
+
// Filter external fragments to only those actually needed by this definition
|
|
405
|
+
const filteredExternalFragments = source.externalFragments.filter(frag => allNeeded.has(frag.name));
|
|
406
|
+
// Combine filtered external fragments + local fragments (all transitive deps)
|
|
334
407
|
const allExternalFragments = [
|
|
335
|
-
...
|
|
408
|
+
...filteredExternalFragments,
|
|
336
409
|
...localFragmentNodes,
|
|
337
410
|
];
|
|
338
411
|
/**
|
|
339
412
|
* UPDATE FRAGMENT IMPORTS
|
|
340
413
|
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
414
|
+
* Only import DIRECT dependencies, not transitive ones.
|
|
415
|
+
* Transitive dependencies are imported by the intermediate fragment files.
|
|
416
|
+
*
|
|
417
|
+
* 1. External fragments: Filter to only DIRECTLY needed ones, update outputPath
|
|
418
|
+
* 2. Local fragments: Generate imports only for DIRECTLY referenced fragments
|
|
343
419
|
*/
|
|
344
|
-
const updatedFragmentImports = source.fragmentImports
|
|
420
|
+
const updatedFragmentImports = source.fragmentImports
|
|
421
|
+
// Filter to only include imports for fragments DIRECTLY referenced
|
|
422
|
+
.filter(fragmentImport => {
|
|
423
|
+
// Check if any of the import's identifiers are for directly needed fragments
|
|
424
|
+
return fragmentImport.importSource.identifiers.some(id => {
|
|
425
|
+
// Check if this identifier's name matches a directly needed fragment
|
|
426
|
+
for (const neededName of directlyNeeded) {
|
|
427
|
+
if (id.name === neededName || id.name.startsWith(neededName)) {
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return false;
|
|
432
|
+
});
|
|
433
|
+
})
|
|
434
|
+
.map(fragmentImport => ({
|
|
345
435
|
...fragmentImport,
|
|
346
436
|
outputPath: filename,
|
|
347
437
|
}));
|
|
348
|
-
|
|
438
|
+
// Only create imports for DIRECTLY referenced local fragments
|
|
439
|
+
const directlyNeededLocalFragments = Array.from(localFragmentMap.entries())
|
|
440
|
+
.filter(([fragName]) => directlyNeeded.has(fragName))
|
|
441
|
+
.map(([, fragDef]) => fragDef);
|
|
442
|
+
const localFragmentImports = createLocalFragmentImports(directlyNeededLocalFragments, fragmentRegistry, source.documents[0].location, filename, folder, extension, {
|
|
349
443
|
baseDir,
|
|
350
444
|
baseOutputDir: options.baseOutputDir,
|
|
351
445
|
emitLegacyCommonJSImports: options.config.emitLegacyCommonJSImports,
|
package/esm/index.js
CHANGED
|
@@ -20,18 +20,17 @@ function extractDefinitions(document) {
|
|
|
20
20
|
return definitions;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Example:
|
|
27
|
-
* Source file has: query MyQuery { ...MyFragment } + fragment MyFragment { ... }
|
|
28
|
-
* After split: MyQuery.ts needs to import from MyFragment.ts
|
|
23
|
+
* Collects all fragment names directly referenced by a definition via FragmentSpread nodes.
|
|
24
|
+
* This only gets DIRECT references - not transitive dependencies.
|
|
29
25
|
*/
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
function getDirectlyReferencedFragments(definition) {
|
|
27
|
+
const referencedFragments = new Set();
|
|
28
|
+
visit(definition, {
|
|
29
|
+
FragmentSpread: (node) => {
|
|
30
|
+
referencedFragments.add(node.name.value);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
return referencedFragments;
|
|
35
34
|
}
|
|
36
35
|
/**
|
|
37
36
|
* Creates LoadedFragment objects for local fragments so they can be treated as external fragments
|
|
@@ -46,6 +45,54 @@ function createLoadedFragments(fragments) {
|
|
|
46
45
|
node: frag,
|
|
47
46
|
}));
|
|
48
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Analyzes fragment dependencies for a definition.
|
|
50
|
+
*
|
|
51
|
+
* Returns two sets:
|
|
52
|
+
* 1. `allNeeded` - ALL fragments needed (including transitive) - for externalFragments
|
|
53
|
+
* 2. `directlyNeeded` - Only DIRECT dependencies - for fragmentImports
|
|
54
|
+
*
|
|
55
|
+
* Why the distinction?
|
|
56
|
+
* - `externalFragments` must include all transitive deps so the plugin can resolve types
|
|
57
|
+
* - `fragmentImports` should only include direct deps; transitive deps are imported by intermediate files
|
|
58
|
+
*
|
|
59
|
+
* Example: Query → FragA → FragB
|
|
60
|
+
* - Query's externalFragments: [FragA, FragB] (plugin needs both)
|
|
61
|
+
* - Query's fragmentImports: [FragA] (only direct; FragA.ts imports FragB)
|
|
62
|
+
*
|
|
63
|
+
* @param definition - The operation/fragment definition to analyze
|
|
64
|
+
* @param externalFragments - All external fragments available (from other files)
|
|
65
|
+
* @param localFragmentMap - Map of local fragment names to their definitions
|
|
66
|
+
*/
|
|
67
|
+
function analyzeFragmentDependencies(definition, externalFragments, localFragmentMap) {
|
|
68
|
+
const allNeeded = new Set();
|
|
69
|
+
// Build a map for quick lookup of external fragment nodes
|
|
70
|
+
const externalFragmentMap = new Map();
|
|
71
|
+
for (const frag of externalFragments) {
|
|
72
|
+
externalFragmentMap.set(frag.name, frag.node);
|
|
73
|
+
}
|
|
74
|
+
function collectTransitive(fragmentNames) {
|
|
75
|
+
for (const name of fragmentNames) {
|
|
76
|
+
if (allNeeded.has(name))
|
|
77
|
+
continue;
|
|
78
|
+
// Check if this fragment exists (either local or external)
|
|
79
|
+
const localDef = localFragmentMap.get(name);
|
|
80
|
+
const externalDef = externalFragmentMap.get(name);
|
|
81
|
+
const fragmentDef = localDef || externalDef;
|
|
82
|
+
if (fragmentDef) {
|
|
83
|
+
allNeeded.add(name);
|
|
84
|
+
// Get transitive dependencies
|
|
85
|
+
const transitiveDeps = getDirectlyReferencedFragments(fragmentDef);
|
|
86
|
+
collectTransitive(transitiveDeps);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Get direct references from the definition
|
|
91
|
+
const directlyNeeded = getDirectlyReferencedFragments(definition);
|
|
92
|
+
// Collect all transitive dependencies (includes direct ones)
|
|
93
|
+
collectTransitive(directlyNeeded);
|
|
94
|
+
return { allNeeded, directlyNeeded };
|
|
95
|
+
}
|
|
49
96
|
/**
|
|
50
97
|
* Creates fragment import declarations for local fragments.
|
|
51
98
|
* After splitting, these fragments will be in separate files, so we need to generate imports.
|
|
@@ -323,25 +370,72 @@ export const preset = {
|
|
|
323
370
|
* resolver marked MyFrag as "local" (same document).
|
|
324
371
|
*
|
|
325
372
|
* Solution: Treat local fragments as external after splitting.
|
|
373
|
+
* Only include fragments that are actually referenced by this definition.
|
|
326
374
|
*/
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
375
|
+
// Build a map of local fragment names -> definitions for lookup
|
|
376
|
+
const localFragmentMap = new Map();
|
|
377
|
+
for (const d of definitions) {
|
|
378
|
+
if (d.definition.kind === Kind.FRAGMENT_DEFINITION && d.name !== name) {
|
|
379
|
+
localFragmentMap.set(d.name, d.definition);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* ANALYZE FRAGMENT DEPENDENCIES
|
|
384
|
+
*
|
|
385
|
+
* We need two different sets of fragments:
|
|
386
|
+
* 1. allNeeded - ALL transitive deps for externalFragments (plugin needs these for type resolution)
|
|
387
|
+
* 2. directlyNeeded - Only DIRECT deps for fragmentImports (transitive deps handled by intermediate files)
|
|
388
|
+
*
|
|
389
|
+
* Example: Query → FragA → FragB
|
|
390
|
+
* - externalFragments: [FragA, FragB] (plugin needs both for types)
|
|
391
|
+
* - fragmentImports: [FragA] (only direct; FragA.ts already imports FragB)
|
|
392
|
+
*/
|
|
393
|
+
const { allNeeded, directlyNeeded } = analyzeFragmentDependencies(definition, source.externalFragments, localFragmentMap);
|
|
394
|
+
// Filter local fragments to only those in the transitive dependency set
|
|
395
|
+
// (for externalFragments - plugin needs all of them)
|
|
396
|
+
const allLocalFragments = Array.from(localFragmentMap.entries())
|
|
397
|
+
.filter(([fragName]) => allNeeded.has(fragName))
|
|
398
|
+
.map(([, fragDef]) => fragDef);
|
|
399
|
+
const localFragmentNodes = createLoadedFragments(allLocalFragments);
|
|
400
|
+
// Filter external fragments to only those actually needed by this definition
|
|
401
|
+
const filteredExternalFragments = source.externalFragments.filter(frag => allNeeded.has(frag.name));
|
|
402
|
+
// Combine filtered external fragments + local fragments (all transitive deps)
|
|
330
403
|
const allExternalFragments = [
|
|
331
|
-
...
|
|
404
|
+
...filteredExternalFragments,
|
|
332
405
|
...localFragmentNodes,
|
|
333
406
|
];
|
|
334
407
|
/**
|
|
335
408
|
* UPDATE FRAGMENT IMPORTS
|
|
336
409
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
410
|
+
* Only import DIRECT dependencies, not transitive ones.
|
|
411
|
+
* Transitive dependencies are imported by the intermediate fragment files.
|
|
412
|
+
*
|
|
413
|
+
* 1. External fragments: Filter to only DIRECTLY needed ones, update outputPath
|
|
414
|
+
* 2. Local fragments: Generate imports only for DIRECTLY referenced fragments
|
|
339
415
|
*/
|
|
340
|
-
const updatedFragmentImports = source.fragmentImports
|
|
416
|
+
const updatedFragmentImports = source.fragmentImports
|
|
417
|
+
// Filter to only include imports for fragments DIRECTLY referenced
|
|
418
|
+
.filter(fragmentImport => {
|
|
419
|
+
// Check if any of the import's identifiers are for directly needed fragments
|
|
420
|
+
return fragmentImport.importSource.identifiers.some(id => {
|
|
421
|
+
// Check if this identifier's name matches a directly needed fragment
|
|
422
|
+
for (const neededName of directlyNeeded) {
|
|
423
|
+
if (id.name === neededName || id.name.startsWith(neededName)) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
});
|
|
429
|
+
})
|
|
430
|
+
.map(fragmentImport => ({
|
|
341
431
|
...fragmentImport,
|
|
342
432
|
outputPath: filename,
|
|
343
433
|
}));
|
|
344
|
-
|
|
434
|
+
// Only create imports for DIRECTLY referenced local fragments
|
|
435
|
+
const directlyNeededLocalFragments = Array.from(localFragmentMap.entries())
|
|
436
|
+
.filter(([fragName]) => directlyNeeded.has(fragName))
|
|
437
|
+
.map(([, fragDef]) => fragDef);
|
|
438
|
+
const localFragmentImports = createLocalFragmentImports(directlyNeededLocalFragments, fragmentRegistry, source.documents[0].location, filename, folder, extension, {
|
|
345
439
|
baseDir,
|
|
346
440
|
baseOutputDir: options.baseOutputDir,
|
|
347
441
|
emitLegacyCommonJSImports: options.config.emitLegacyCommonJSImports,
|
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.10",
|
|
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"
|