UNPKG

2.61 kBJavaScriptView Raw
1import { Kind } from '../language/kinds.mjs';
2import { visit } from '../language/visitor.mjs';
3/**
4 * separateOperations accepts a single AST document which may contain many
5 * operations and fragments and returns a collection of AST documents each of
6 * which contains a single operation as well the fragment definitions it
7 * refers to.
8 */
9
10export function separateOperations(documentAST) {
11 const operations = [];
12 const depGraph = Object.create(null); // Populate metadata and build a dependency graph.
13
14 for (const definitionNode of documentAST.definitions) {
15 switch (definitionNode.kind) {
16 case Kind.OPERATION_DEFINITION:
17 operations.push(definitionNode);
18 break;
19
20 case Kind.FRAGMENT_DEFINITION:
21 depGraph[definitionNode.name.value] = collectDependencies(
22 definitionNode.selectionSet,
23 );
24 break;
25
26 default: // ignore non-executable definitions
27 }
28 } // For each operation, produce a new synthesized AST which includes only what
29 // is necessary for completing that operation.
30
31 const separatedDocumentASTs = Object.create(null);
32
33 for (const operation of operations) {
34 const dependencies = new Set();
35
36 for (const fragmentName of collectDependencies(operation.selectionSet)) {
37 collectTransitiveDependencies(dependencies, depGraph, fragmentName);
38 } // Provides the empty string for anonymous operations.
39
40 const operationName = operation.name ? operation.name.value : ''; // The list of definition nodes to be included for this operation, sorted
41 // to retain the same order as the original document.
42
43 separatedDocumentASTs[operationName] = {
44 kind: Kind.DOCUMENT,
45 definitions: documentAST.definitions.filter(
46 (node) =>
47 node === operation ||
48 (node.kind === Kind.FRAGMENT_DEFINITION &&
49 dependencies.has(node.name.value)),
50 ),
51 };
52 }
53
54 return separatedDocumentASTs;
55}
56
57// From a dependency graph, collects a list of transitive dependencies by
58// recursing through a dependency graph.
59function collectTransitiveDependencies(collected, depGraph, fromName) {
60 if (!collected.has(fromName)) {
61 collected.add(fromName);
62 const immediateDeps = depGraph[fromName];
63
64 if (immediateDeps !== undefined) {
65 for (const toName of immediateDeps) {
66 collectTransitiveDependencies(collected, depGraph, toName);
67 }
68 }
69 }
70}
71
72function collectDependencies(selectionSet) {
73 const dependencies = [];
74 visit(selectionSet, {
75 FragmentSpread(node) {
76 dependencies.push(node.name.value);
77 },
78 });
79 return dependencies;
80}