@theguild/federation-composition 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +2 -1
  2. package/cjs/specifications/link.js +40 -5
  3. package/cjs/subgraph/helpers.js +2 -2
  4. package/cjs/subgraph/state.js +8 -0
  5. package/cjs/subgraph/validation/rules/elements/provides.js +8 -6
  6. package/cjs/subgraph/validation/rules/elements/requires.js +9 -7
  7. package/cjs/subgraph/validation/validate-state.js +9 -3
  8. package/cjs/subgraph/validation/validate-subgraph.js +4 -2
  9. package/cjs/subgraph/validation/validation-context.js +10 -0
  10. package/cjs/supergraph/composition/directive.js +3 -0
  11. package/cjs/supergraph/composition/enum-type.js +3 -0
  12. package/cjs/supergraph/composition/input-object-type.js +3 -0
  13. package/cjs/supergraph/composition/interface-type.js +4 -0
  14. package/cjs/supergraph/composition/object-type.js +18 -20
  15. package/cjs/supergraph/composition/scalar-type.js +2 -0
  16. package/cjs/supergraph/composition/union-type.js +2 -0
  17. package/cjs/supergraph/state.js +24 -26
  18. package/cjs/supergraph/validation/rules/fields-of-the-same-type-rule.js +35 -0
  19. package/cjs/supergraph/validation/rules/invalid-field-sharing-rule.js +4 -1
  20. package/cjs/supergraph/validation/rules/satisfiablity/constants.js +4 -0
  21. package/cjs/supergraph/validation/rules/satisfiablity/edge.js +64 -0
  22. package/cjs/supergraph/validation/rules/satisfiablity/errors.js +44 -0
  23. package/cjs/supergraph/validation/rules/satisfiablity/fields.js +147 -0
  24. package/cjs/supergraph/validation/rules/satisfiablity/finder.js +267 -0
  25. package/cjs/supergraph/validation/rules/satisfiablity/graph.js +675 -0
  26. package/cjs/supergraph/validation/rules/satisfiablity/helpers.js +41 -0
  27. package/cjs/supergraph/validation/rules/satisfiablity/move-validator.js +337 -0
  28. package/cjs/supergraph/validation/rules/satisfiablity/moves.js +52 -0
  29. package/cjs/supergraph/validation/rules/satisfiablity/node.js +89 -0
  30. package/cjs/supergraph/validation/rules/satisfiablity/operation-path.js +70 -0
  31. package/cjs/supergraph/validation/rules/satisfiablity/supergraph.js +37 -0
  32. package/cjs/supergraph/validation/rules/satisfiablity/walker.js +306 -0
  33. package/cjs/supergraph/validation/rules/satisfiablity-rule.js +45 -1081
  34. package/cjs/supergraph/validation/validate-supergraph.js +1 -1
  35. package/cjs/utils/logger.js +127 -0
  36. package/esm/specifications/link.js +40 -5
  37. package/esm/subgraph/helpers.js +2 -2
  38. package/esm/subgraph/state.js +8 -0
  39. package/esm/subgraph/validation/rules/elements/provides.js +8 -6
  40. package/esm/subgraph/validation/rules/elements/requires.js +9 -7
  41. package/esm/subgraph/validation/validate-state.js +9 -3
  42. package/esm/subgraph/validation/validate-subgraph.js +4 -2
  43. package/esm/subgraph/validation/validation-context.js +11 -1
  44. package/esm/supergraph/composition/directive.js +3 -0
  45. package/esm/supergraph/composition/enum-type.js +3 -0
  46. package/esm/supergraph/composition/input-object-type.js +3 -0
  47. package/esm/supergraph/composition/interface-type.js +4 -0
  48. package/esm/supergraph/composition/object-type.js +18 -20
  49. package/esm/supergraph/composition/scalar-type.js +2 -0
  50. package/esm/supergraph/composition/union-type.js +2 -0
  51. package/esm/supergraph/state.js +24 -26
  52. package/esm/supergraph/validation/rules/fields-of-the-same-type-rule.js +35 -0
  53. package/esm/supergraph/validation/rules/invalid-field-sharing-rule.js +4 -1
  54. package/esm/supergraph/validation/rules/satisfiablity/constants.js +1 -0
  55. package/esm/supergraph/validation/rules/satisfiablity/edge.js +54 -0
  56. package/esm/supergraph/validation/rules/satisfiablity/errors.js +40 -0
  57. package/esm/supergraph/validation/rules/satisfiablity/fields.js +142 -0
  58. package/esm/supergraph/validation/rules/satisfiablity/finder.js +261 -0
  59. package/esm/supergraph/validation/rules/satisfiablity/graph.js +671 -0
  60. package/esm/supergraph/validation/rules/satisfiablity/helpers.js +35 -0
  61. package/esm/supergraph/validation/rules/satisfiablity/move-validator.js +333 -0
  62. package/esm/supergraph/validation/rules/satisfiablity/moves.js +46 -0
  63. package/esm/supergraph/validation/rules/satisfiablity/node.js +85 -0
  64. package/esm/supergraph/validation/rules/satisfiablity/operation-path.js +66 -0
  65. package/esm/supergraph/validation/rules/satisfiablity/supergraph.js +33 -0
  66. package/esm/supergraph/validation/rules/satisfiablity/walker.js +301 -0
  67. package/esm/supergraph/validation/rules/satisfiablity-rule.js +40 -1076
  68. package/esm/supergraph/validation/validate-supergraph.js +1 -1
  69. package/esm/utils/logger.js +119 -0
  70. package/package.json +2 -1
  71. package/typings/subgraph/state.d.cts +2 -0
  72. package/typings/subgraph/state.d.ts +2 -0
  73. package/typings/subgraph/validation/validate-state.d.cts +2 -1
  74. package/typings/subgraph/validation/validate-state.d.ts +2 -1
  75. package/typings/subgraph/validation/validation-context.d.cts +3 -0
  76. package/typings/subgraph/validation/validation-context.d.ts +3 -0
  77. package/typings/supergraph/composition/common.d.cts +2 -1
  78. package/typings/supergraph/composition/common.d.ts +2 -1
  79. package/typings/supergraph/composition/directive.d.cts +4 -0
  80. package/typings/supergraph/composition/directive.d.ts +4 -0
  81. package/typings/supergraph/composition/enum-type.d.cts +4 -0
  82. package/typings/supergraph/composition/enum-type.d.ts +4 -0
  83. package/typings/supergraph/composition/input-object-type.d.cts +4 -0
  84. package/typings/supergraph/composition/input-object-type.d.ts +4 -0
  85. package/typings/supergraph/composition/interface-type.d.cts +5 -0
  86. package/typings/supergraph/composition/interface-type.d.ts +5 -0
  87. package/typings/supergraph/composition/object-type.d.cts +5 -0
  88. package/typings/supergraph/composition/object-type.d.ts +5 -0
  89. package/typings/supergraph/composition/scalar-type.d.cts +3 -0
  90. package/typings/supergraph/composition/scalar-type.d.ts +3 -0
  91. package/typings/supergraph/composition/union-type.d.cts +3 -0
  92. package/typings/supergraph/composition/union-type.d.ts +3 -0
  93. package/typings/supergraph/state.d.cts +4 -4
  94. package/typings/supergraph/state.d.ts +4 -4
  95. package/typings/supergraph/validation/rules/satisfiablity/constants.d.cts +2 -0
  96. package/typings/supergraph/validation/rules/satisfiablity/constants.d.ts +2 -0
  97. package/typings/supergraph/validation/rules/satisfiablity/edge.d.cts +31 -0
  98. package/typings/supergraph/validation/rules/satisfiablity/edge.d.ts +31 -0
  99. package/typings/supergraph/validation/rules/satisfiablity/errors.d.cts +17 -0
  100. package/typings/supergraph/validation/rules/satisfiablity/errors.d.ts +17 -0
  101. package/typings/supergraph/validation/rules/satisfiablity/fields.d.cts +33 -0
  102. package/typings/supergraph/validation/rules/satisfiablity/fields.d.ts +33 -0
  103. package/typings/supergraph/validation/rules/satisfiablity/finder.d.cts +28 -0
  104. package/typings/supergraph/validation/rules/satisfiablity/finder.d.ts +28 -0
  105. package/typings/supergraph/validation/rules/satisfiablity/graph.d.cts +63 -0
  106. package/typings/supergraph/validation/rules/satisfiablity/graph.d.ts +63 -0
  107. package/typings/supergraph/validation/rules/satisfiablity/helpers.d.cts +7 -0
  108. package/typings/supergraph/validation/rules/satisfiablity/helpers.d.ts +7 -0
  109. package/typings/supergraph/validation/rules/satisfiablity/move-validator.d.cts +25 -0
  110. package/typings/supergraph/validation/rules/satisfiablity/move-validator.d.ts +25 -0
  111. package/typings/supergraph/validation/rules/satisfiablity/moves.d.cts +24 -0
  112. package/typings/supergraph/validation/rules/satisfiablity/moves.d.ts +24 -0
  113. package/typings/supergraph/validation/rules/satisfiablity/node.d.cts +31 -0
  114. package/typings/supergraph/validation/rules/satisfiablity/node.d.ts +31 -0
  115. package/typings/supergraph/validation/rules/satisfiablity/operation-path.d.cts +29 -0
  116. package/typings/supergraph/validation/rules/satisfiablity/operation-path.d.ts +29 -0
  117. package/typings/supergraph/validation/rules/satisfiablity/supergraph.d.cts +14 -0
  118. package/typings/supergraph/validation/rules/satisfiablity/supergraph.d.ts +14 -0
  119. package/typings/supergraph/validation/rules/satisfiablity/walker.d.cts +35 -0
  120. package/typings/supergraph/validation/rules/satisfiablity/walker.d.ts +35 -0
  121. package/typings/utils/logger.d.cts +33 -0
  122. package/typings/utils/logger.d.ts +33 -0
  123. package/cjs/utils/dependency-graph.js +0 -227
  124. package/esm/utils/dependency-graph.js +0 -222
  125. package/typings/utils/dependency-graph.d.cts +0 -31
  126. package/typings/utils/dependency-graph.d.ts +0 -31
@@ -0,0 +1,301 @@
1
+ import { OperationTypeNode } from 'graphql';
2
+ import { isAbstractEdge, isFieldEdge } from './edge';
3
+ import { PathFinder } from './finder';
4
+ import { OperationPath } from './operation-path';
5
+ export class WalkTracker {
6
+ superPath;
7
+ paths;
8
+ errors = [];
9
+ constructor(superPath, paths) {
10
+ this.superPath = superPath;
11
+ this.paths = paths;
12
+ }
13
+ move(edge) {
14
+ if (isFieldEdge(edge) || isAbstractEdge(edge)) {
15
+ return new WalkTracker(this.superPath.clone().move(edge), []);
16
+ }
17
+ throw new Error('Expected edge to be FieldMove or AbstractMove');
18
+ }
19
+ addPath(path) {
20
+ this.paths.push(path);
21
+ this.errors = [];
22
+ }
23
+ addError(error) {
24
+ this.errors.push(error);
25
+ }
26
+ isPossible() {
27
+ return this.paths.length > 0;
28
+ }
29
+ givesEmptyResult() {
30
+ const lastEdge = this.superPath.edge();
31
+ return (this.paths.length === 0 && this.errors.length === 0 && !!lastEdge && isAbstractEdge(lastEdge));
32
+ }
33
+ isEdgeVisited(edge) {
34
+ return this.superPath.isVisitedEdge(edge);
35
+ }
36
+ listErrors() {
37
+ return this.errors
38
+ .filter((error, i, all) => all.findIndex(e => e.toString() === error.toString()) === i)
39
+ .filter(error => {
40
+ if (error.kind !== 'KEY') {
41
+ return true;
42
+ }
43
+ const steps = this.superPath.steps();
44
+ const lastStep = steps[steps.length - 1];
45
+ if (!lastStep || lastStep.typeName !== error.typeName) {
46
+ return true;
47
+ }
48
+ return true;
49
+ });
50
+ }
51
+ }
52
+ const defaultIsEdgeIgnored = () => false;
53
+ export class Walker {
54
+ moveChecker;
55
+ supergraph;
56
+ mergedGraph;
57
+ logger;
58
+ pathFinder;
59
+ constructor(logger, moveChecker, supergraph, mergedGraph) {
60
+ this.moveChecker = moveChecker;
61
+ this.supergraph = supergraph;
62
+ this.mergedGraph = mergedGraph;
63
+ this.logger = logger.create('Walker');
64
+ this.pathFinder = new PathFinder(this.logger, this.mergedGraph, this.moveChecker);
65
+ }
66
+ walkTrail(operationType, steps) {
67
+ if (steps.length === 0) {
68
+ throw new Error('Expected at least one step');
69
+ }
70
+ const rootNode = this.supergraph.nodeOf(operationType === OperationTypeNode.QUERY
71
+ ? 'Query'
72
+ : operationType === OperationTypeNode.MUTATION
73
+ ? 'Mutation'
74
+ : 'Subscription', false);
75
+ if (!rootNode) {
76
+ throw new Error(`Expected root node for operation type ${operationType}`);
77
+ }
78
+ let state = new WalkTracker(new OperationPath(rootNode), this.mergedGraph.nodesOf(rootNode.typeName, false).map(n => new OperationPath(n)));
79
+ for (const step of steps) {
80
+ const stepId = 'fieldName' in step && step.fieldName
81
+ ? `${step.typeName}.${step.fieldName}`
82
+ : step.typeName;
83
+ const isFieldStep = 'fieldName' in step;
84
+ const isEdgeIgnored = (edge) => {
85
+ if (isFieldStep) {
86
+ return !isFieldEdge(edge) || edge.move.fieldName !== step.fieldName;
87
+ }
88
+ return true;
89
+ };
90
+ let called = 0;
91
+ let unreachable = false;
92
+ let emptyObjectResult = false;
93
+ this.nextStep(state, state.superPath.tail() ?? state.superPath.rootNode(), (nextState, superEdge) => {
94
+ if (called++ > 1) {
95
+ throw new Error('Expected nextStep to be called only once');
96
+ }
97
+ state = nextState;
98
+ if (nextState.isPossible()) {
99
+ if (this.logger.isEnabled) {
100
+ for (const path of nextState.paths) {
101
+ this.logger.log(() => path.toString());
102
+ }
103
+ }
104
+ this.logger.groupEnd(() => 'Advanced to ' + superEdge + ' with ' + nextState.paths.length + ' paths');
105
+ }
106
+ else if (nextState.givesEmptyResult()) {
107
+ emptyObjectResult = true;
108
+ this.logger.groupEnd(() => 'Federation will resolve an empty object for ' + superEdge);
109
+ }
110
+ else {
111
+ unreachable = true;
112
+ this.logger.log(() => 'Dead end', '🚨 ');
113
+ if (this.logger.isEnabled) {
114
+ for (const path of state.paths) {
115
+ this.logger.log(() => path.toString());
116
+ }
117
+ }
118
+ this.logger.groupEnd(() => 'Unreachable path ' + nextState.superPath.toString());
119
+ }
120
+ }, isEdgeIgnored);
121
+ if (unreachable) {
122
+ break;
123
+ }
124
+ if (emptyObjectResult) {
125
+ break;
126
+ }
127
+ }
128
+ return state;
129
+ }
130
+ walk(method = 'bfs') {
131
+ if (method === 'dfs') {
132
+ return this.dfs();
133
+ }
134
+ return this.bfs();
135
+ }
136
+ nextStep(state, superTail, next, isEdgeIgnored = defaultIsEdgeIgnored) {
137
+ const graphsLeadingToNode = Array.from(new Set(state.paths.map(p => {
138
+ const edge = p.edge();
139
+ const tail = p.tail() ?? p.rootNode();
140
+ const tailGraphName = tail.graphName;
141
+ if (edge && isFieldEdge(edge) && edge.move.provides) {
142
+ return `${tailGraphName}#provides`;
143
+ }
144
+ return tailGraphName;
145
+ })));
146
+ if (superTail.isGraphComboVisited(graphsLeadingToNode)) {
147
+ this.logger.log(() => 'Node already visited: ' + superTail);
148
+ return;
149
+ }
150
+ superTail.setGraphComboAsVisited(graphsLeadingToNode);
151
+ const superEdges = this.supergraph.edgesOfHead(superTail);
152
+ for (const superEdge of superEdges) {
153
+ if (isEdgeIgnored(superEdge)) {
154
+ continue;
155
+ }
156
+ this.logger.group(() => 'Attempt to advance to ' + superEdge + ' (' + state.paths.length + ' paths)');
157
+ if (state.isEdgeVisited(superEdge)) {
158
+ this.logger.groupEnd(() => 'Edge already visited: ' + superEdge);
159
+ continue;
160
+ }
161
+ if (!(isFieldEdge(superEdge) || isAbstractEdge(superEdge))) {
162
+ throw new Error('Expected edge to have a FieldMove or AbstractMove');
163
+ }
164
+ const nextState = state.move(superEdge);
165
+ const shortestPathPerTail = new Map();
166
+ const isFieldMove = isFieldEdge(superEdge);
167
+ const id = isFieldMove
168
+ ? `${superEdge.move.typeName}.${superEdge.move.fieldName}`
169
+ : `... on ${superEdge.tail.typeName}`;
170
+ for (const path of state.paths) {
171
+ this.logger.group(() => 'Advance path: ' + path.toString());
172
+ const directPathsResult = this.pathFinder.findDirectPaths(path, isFieldMove ? superEdge.move.typeName : superEdge.tail.typeName, isFieldMove ? superEdge.move.fieldName : null, []);
173
+ if (directPathsResult.success && directPathsResult.paths.length === 0) {
174
+ this.logger.groupEnd(() => 'Abstract type');
175
+ continue;
176
+ }
177
+ if (directPathsResult.success) {
178
+ setShortestPath(shortestPathPerTail, directPathsResult.paths);
179
+ }
180
+ else {
181
+ for (const error of directPathsResult.errors) {
182
+ nextState.addError(error);
183
+ }
184
+ }
185
+ if (directPathsResult.success && superEdge.tail.isLeaf) {
186
+ this.logger.groupEnd(() => 'Reached leaf node, no need to find indirect paths');
187
+ continue;
188
+ }
189
+ const indirectPathsResult = this.pathFinder.findIndirectPaths(path, isFieldMove ? superEdge.move.typeName : superEdge.tail.typeName, isFieldMove ? superEdge.move.fieldName : null, [], [], []);
190
+ if (indirectPathsResult.success) {
191
+ setShortestPath(shortestPathPerTail, indirectPathsResult.paths);
192
+ }
193
+ else {
194
+ for (const error of indirectPathsResult.errors) {
195
+ nextState.addError(error);
196
+ }
197
+ }
198
+ this.logger.groupEnd(() => directPathsResult.success || indirectPathsResult.success
199
+ ? 'Can advance to ' + id
200
+ : 'Cannot advance to ' + id);
201
+ }
202
+ for (const shortestPathByTail of shortestPathPerTail.values()) {
203
+ nextState.addPath(shortestPathByTail);
204
+ }
205
+ next(nextState, superEdge);
206
+ }
207
+ }
208
+ dfs() {
209
+ const unreachable = [];
210
+ const rootNodes = ['Query', 'Mutation', 'Subscription']
211
+ .map(name => this.supergraph.nodeOf(name, false))
212
+ .filter((node) => !!node);
213
+ for (const rootNode of rootNodes) {
214
+ this._dfs(rootNode, new WalkTracker(new OperationPath(rootNode), this.mergedGraph.nodesOf(rootNode.typeName, false).map(n => new OperationPath(n))), unreachable);
215
+ }
216
+ return unreachable;
217
+ }
218
+ _dfs(superTail, state, unreachable) {
219
+ if (superTail.isLeaf) {
220
+ return;
221
+ }
222
+ this.nextStep(state, superTail, (nextState, superEdge) => {
223
+ if (nextState.isPossible()) {
224
+ if (this.logger.isEnabled) {
225
+ for (const path of nextState.paths) {
226
+ this.logger.log(() => path.toString());
227
+ }
228
+ }
229
+ this.logger.groupEnd(() => 'Advanced to ' + superEdge + ' with ' + nextState.paths.length + ' paths');
230
+ this._dfs(superEdge.tail, nextState, unreachable);
231
+ }
232
+ else if (nextState.givesEmptyResult()) {
233
+ this.logger.groupEnd(() => 'Federation will resolve an empty object for ' + superEdge);
234
+ }
235
+ else {
236
+ unreachable.push(nextState);
237
+ this.logger.log(() => 'Dead end', '🚨 ');
238
+ if (this.logger.isEnabled) {
239
+ for (const path of state.paths) {
240
+ this.logger.log(() => path.toString());
241
+ }
242
+ }
243
+ this.logger.groupEnd(() => 'Unreachable path ' + nextState.superPath.toString());
244
+ }
245
+ });
246
+ }
247
+ bfs() {
248
+ const unreachable = [];
249
+ const queue = [];
250
+ const rootNodes = ['Query', 'Mutation', 'Subscription']
251
+ .map(name => this.supergraph.nodeOf(name, false))
252
+ .filter((node) => !!node);
253
+ for (const rootNode of rootNodes) {
254
+ queue.push(new WalkTracker(new OperationPath(rootNode), this.mergedGraph.nodesOf(rootNode.typeName, false).map(n => new OperationPath(n))));
255
+ }
256
+ while (queue.length > 0) {
257
+ const state = queue.pop();
258
+ if (!state) {
259
+ throw new Error('Unexpected end of queue');
260
+ }
261
+ const superTail = state.superPath.tail() ?? state.superPath.rootNode();
262
+ if (superTail.isLeaf) {
263
+ continue;
264
+ }
265
+ this.nextStep(state, superTail, (nextState, superEdge) => {
266
+ if (nextState.isPossible()) {
267
+ if (this.logger.isEnabled) {
268
+ for (const path of nextState.paths) {
269
+ this.logger.log(() => path.toString());
270
+ }
271
+ }
272
+ this.logger.groupEnd(() => 'Advanced to ' + superEdge + ' with ' + nextState.paths.length + ' paths');
273
+ queue.push(nextState);
274
+ }
275
+ else if (nextState.givesEmptyResult()) {
276
+ this.logger.groupEnd(() => 'Federation will resolve an empty object for ' + superEdge);
277
+ }
278
+ else {
279
+ unreachable.push(nextState);
280
+ this.logger.log(() => 'Dead end', '🚨 ');
281
+ if (this.logger.isEnabled) {
282
+ for (const path of state.paths) {
283
+ this.logger.log(() => path.toString());
284
+ }
285
+ }
286
+ this.logger.groupEnd(() => 'Unreachable path ' + nextState.superPath.toString());
287
+ }
288
+ });
289
+ }
290
+ return unreachable;
291
+ }
292
+ }
293
+ function setShortestPath(shortestPathPerTail, paths) {
294
+ for (const path of paths) {
295
+ const tail = path.tail() ?? path.rootNode();
296
+ const shortestByTail = shortestPathPerTail.get(tail);
297
+ if (!shortestByTail || shortestByTail.depth() > path.depth()) {
298
+ shortestPathPerTail.set(tail, path);
299
+ }
300
+ }
301
+ }