separateOperations.mjs 2.5 KB

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