separateOperations.js.flow 2.9 KB

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