separateOperations.js.flow 2.7 KB

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