separateOperations.js.flow 2.8 KB

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