@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.
- package/README.md +2 -1
- package/cjs/specifications/link.js +40 -5
- package/cjs/subgraph/helpers.js +2 -2
- package/cjs/subgraph/state.js +8 -0
- package/cjs/subgraph/validation/rules/elements/provides.js +8 -6
- package/cjs/subgraph/validation/rules/elements/requires.js +9 -7
- package/cjs/subgraph/validation/validate-state.js +9 -3
- package/cjs/subgraph/validation/validate-subgraph.js +4 -2
- package/cjs/subgraph/validation/validation-context.js +10 -0
- package/cjs/supergraph/composition/directive.js +3 -0
- package/cjs/supergraph/composition/enum-type.js +3 -0
- package/cjs/supergraph/composition/input-object-type.js +3 -0
- package/cjs/supergraph/composition/interface-type.js +4 -0
- package/cjs/supergraph/composition/object-type.js +18 -20
- package/cjs/supergraph/composition/scalar-type.js +2 -0
- package/cjs/supergraph/composition/union-type.js +2 -0
- package/cjs/supergraph/state.js +24 -26
- package/cjs/supergraph/validation/rules/fields-of-the-same-type-rule.js +35 -0
- package/cjs/supergraph/validation/rules/invalid-field-sharing-rule.js +4 -1
- package/cjs/supergraph/validation/rules/satisfiablity/constants.js +4 -0
- package/cjs/supergraph/validation/rules/satisfiablity/edge.js +64 -0
- package/cjs/supergraph/validation/rules/satisfiablity/errors.js +44 -0
- package/cjs/supergraph/validation/rules/satisfiablity/fields.js +147 -0
- package/cjs/supergraph/validation/rules/satisfiablity/finder.js +267 -0
- package/cjs/supergraph/validation/rules/satisfiablity/graph.js +675 -0
- package/cjs/supergraph/validation/rules/satisfiablity/helpers.js +41 -0
- package/cjs/supergraph/validation/rules/satisfiablity/move-validator.js +337 -0
- package/cjs/supergraph/validation/rules/satisfiablity/moves.js +52 -0
- package/cjs/supergraph/validation/rules/satisfiablity/node.js +89 -0
- package/cjs/supergraph/validation/rules/satisfiablity/operation-path.js +70 -0
- package/cjs/supergraph/validation/rules/satisfiablity/supergraph.js +37 -0
- package/cjs/supergraph/validation/rules/satisfiablity/walker.js +306 -0
- package/cjs/supergraph/validation/rules/satisfiablity-rule.js +45 -1081
- package/cjs/supergraph/validation/validate-supergraph.js +1 -1
- package/cjs/utils/logger.js +127 -0
- package/esm/specifications/link.js +40 -5
- package/esm/subgraph/helpers.js +2 -2
- package/esm/subgraph/state.js +8 -0
- package/esm/subgraph/validation/rules/elements/provides.js +8 -6
- package/esm/subgraph/validation/rules/elements/requires.js +9 -7
- package/esm/subgraph/validation/validate-state.js +9 -3
- package/esm/subgraph/validation/validate-subgraph.js +4 -2
- package/esm/subgraph/validation/validation-context.js +11 -1
- package/esm/supergraph/composition/directive.js +3 -0
- package/esm/supergraph/composition/enum-type.js +3 -0
- package/esm/supergraph/composition/input-object-type.js +3 -0
- package/esm/supergraph/composition/interface-type.js +4 -0
- package/esm/supergraph/composition/object-type.js +18 -20
- package/esm/supergraph/composition/scalar-type.js +2 -0
- package/esm/supergraph/composition/union-type.js +2 -0
- package/esm/supergraph/state.js +24 -26
- package/esm/supergraph/validation/rules/fields-of-the-same-type-rule.js +35 -0
- package/esm/supergraph/validation/rules/invalid-field-sharing-rule.js +4 -1
- package/esm/supergraph/validation/rules/satisfiablity/constants.js +1 -0
- package/esm/supergraph/validation/rules/satisfiablity/edge.js +54 -0
- package/esm/supergraph/validation/rules/satisfiablity/errors.js +40 -0
- package/esm/supergraph/validation/rules/satisfiablity/fields.js +142 -0
- package/esm/supergraph/validation/rules/satisfiablity/finder.js +261 -0
- package/esm/supergraph/validation/rules/satisfiablity/graph.js +671 -0
- package/esm/supergraph/validation/rules/satisfiablity/helpers.js +35 -0
- package/esm/supergraph/validation/rules/satisfiablity/move-validator.js +333 -0
- package/esm/supergraph/validation/rules/satisfiablity/moves.js +46 -0
- package/esm/supergraph/validation/rules/satisfiablity/node.js +85 -0
- package/esm/supergraph/validation/rules/satisfiablity/operation-path.js +66 -0
- package/esm/supergraph/validation/rules/satisfiablity/supergraph.js +33 -0
- package/esm/supergraph/validation/rules/satisfiablity/walker.js +301 -0
- package/esm/supergraph/validation/rules/satisfiablity-rule.js +40 -1076
- package/esm/supergraph/validation/validate-supergraph.js +1 -1
- package/esm/utils/logger.js +119 -0
- package/package.json +2 -1
- package/typings/subgraph/state.d.cts +2 -0
- package/typings/subgraph/state.d.ts +2 -0
- package/typings/subgraph/validation/validate-state.d.cts +2 -1
- package/typings/subgraph/validation/validate-state.d.ts +2 -1
- package/typings/subgraph/validation/validation-context.d.cts +3 -0
- package/typings/subgraph/validation/validation-context.d.ts +3 -0
- package/typings/supergraph/composition/common.d.cts +2 -1
- package/typings/supergraph/composition/common.d.ts +2 -1
- package/typings/supergraph/composition/directive.d.cts +4 -0
- package/typings/supergraph/composition/directive.d.ts +4 -0
- package/typings/supergraph/composition/enum-type.d.cts +4 -0
- package/typings/supergraph/composition/enum-type.d.ts +4 -0
- package/typings/supergraph/composition/input-object-type.d.cts +4 -0
- package/typings/supergraph/composition/input-object-type.d.ts +4 -0
- package/typings/supergraph/composition/interface-type.d.cts +5 -0
- package/typings/supergraph/composition/interface-type.d.ts +5 -0
- package/typings/supergraph/composition/object-type.d.cts +5 -0
- package/typings/supergraph/composition/object-type.d.ts +5 -0
- package/typings/supergraph/composition/scalar-type.d.cts +3 -0
- package/typings/supergraph/composition/scalar-type.d.ts +3 -0
- package/typings/supergraph/composition/union-type.d.cts +3 -0
- package/typings/supergraph/composition/union-type.d.ts +3 -0
- package/typings/supergraph/state.d.cts +4 -4
- package/typings/supergraph/state.d.ts +4 -4
- package/typings/supergraph/validation/rules/satisfiablity/constants.d.cts +2 -0
- package/typings/supergraph/validation/rules/satisfiablity/constants.d.ts +2 -0
- package/typings/supergraph/validation/rules/satisfiablity/edge.d.cts +31 -0
- package/typings/supergraph/validation/rules/satisfiablity/edge.d.ts +31 -0
- package/typings/supergraph/validation/rules/satisfiablity/errors.d.cts +17 -0
- package/typings/supergraph/validation/rules/satisfiablity/errors.d.ts +17 -0
- package/typings/supergraph/validation/rules/satisfiablity/fields.d.cts +33 -0
- package/typings/supergraph/validation/rules/satisfiablity/fields.d.ts +33 -0
- package/typings/supergraph/validation/rules/satisfiablity/finder.d.cts +28 -0
- package/typings/supergraph/validation/rules/satisfiablity/finder.d.ts +28 -0
- package/typings/supergraph/validation/rules/satisfiablity/graph.d.cts +63 -0
- package/typings/supergraph/validation/rules/satisfiablity/graph.d.ts +63 -0
- package/typings/supergraph/validation/rules/satisfiablity/helpers.d.cts +7 -0
- package/typings/supergraph/validation/rules/satisfiablity/helpers.d.ts +7 -0
- package/typings/supergraph/validation/rules/satisfiablity/move-validator.d.cts +25 -0
- package/typings/supergraph/validation/rules/satisfiablity/move-validator.d.ts +25 -0
- package/typings/supergraph/validation/rules/satisfiablity/moves.d.cts +24 -0
- package/typings/supergraph/validation/rules/satisfiablity/moves.d.ts +24 -0
- package/typings/supergraph/validation/rules/satisfiablity/node.d.cts +31 -0
- package/typings/supergraph/validation/rules/satisfiablity/node.d.ts +31 -0
- package/typings/supergraph/validation/rules/satisfiablity/operation-path.d.cts +29 -0
- package/typings/supergraph/validation/rules/satisfiablity/operation-path.d.ts +29 -0
- package/typings/supergraph/validation/rules/satisfiablity/supergraph.d.cts +14 -0
- package/typings/supergraph/validation/rules/satisfiablity/supergraph.d.ts +14 -0
- package/typings/supergraph/validation/rules/satisfiablity/walker.d.cts +35 -0
- package/typings/supergraph/validation/rules/satisfiablity/walker.d.ts +35 -0
- package/typings/utils/logger.d.cts +33 -0
- package/typings/utils/logger.d.ts +33 -0
- package/cjs/utils/dependency-graph.js +0 -227
- package/esm/utils/dependency-graph.js +0 -222
- package/typings/utils/dependency-graph.d.cts +0 -31
- package/typings/utils/dependency-graph.d.ts +0 -31
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function occurrences(str, subString) {
|
|
2
|
+
if (subString.length <= 0) {
|
|
3
|
+
return str.length + 1;
|
|
4
|
+
}
|
|
5
|
+
let n = 0, pos = 0, step = subString.length;
|
|
6
|
+
while (true) {
|
|
7
|
+
pos = str.indexOf(subString, pos);
|
|
8
|
+
if (pos >= 0) {
|
|
9
|
+
++n;
|
|
10
|
+
pos += step;
|
|
11
|
+
}
|
|
12
|
+
else
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
return n;
|
|
16
|
+
}
|
|
17
|
+
export function scoreKeyFields(keyFields) {
|
|
18
|
+
const fields = occurrences(keyFields, ' ') + 1;
|
|
19
|
+
const innerSelectionSets = occurrences(keyFields, '{') * 3;
|
|
20
|
+
return fields + innerSelectionSets;
|
|
21
|
+
}
|
|
22
|
+
export function lazy(factory) {
|
|
23
|
+
let value;
|
|
24
|
+
return {
|
|
25
|
+
get() {
|
|
26
|
+
if (value === undefined) {
|
|
27
|
+
value = factory();
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
},
|
|
31
|
+
invalidate() {
|
|
32
|
+
value = undefined;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { isEntityEdge, isFieldEdge } from './edge';
|
|
2
|
+
import { SatisfiabilityError } from './errors';
|
|
3
|
+
import { concatIfNotExistsFields, concatIfNotExistsString, PathFinder } from './finder';
|
|
4
|
+
export class MoveValidator {
|
|
5
|
+
supergraph;
|
|
6
|
+
cache = new Map();
|
|
7
|
+
logger;
|
|
8
|
+
pathFinder;
|
|
9
|
+
constructor(logger, supergraph) {
|
|
10
|
+
this.supergraph = supergraph;
|
|
11
|
+
this.logger = logger.create('MoveValidator');
|
|
12
|
+
this.pathFinder = new PathFinder(this.logger, supergraph, this);
|
|
13
|
+
}
|
|
14
|
+
canResolveFields(fields, path, visitedEdges, visitedGraphs, visitedFields) {
|
|
15
|
+
const cacheKey = JSON.stringify(fields) +
|
|
16
|
+
' | ' +
|
|
17
|
+
visitedGraphs.join(',') +
|
|
18
|
+
visitedFields.join(',') +
|
|
19
|
+
' | ' +
|
|
20
|
+
visitedEdges
|
|
21
|
+
.map(e => e.toString())
|
|
22
|
+
.sort()
|
|
23
|
+
.join(',');
|
|
24
|
+
const cached = this.cache.get(cacheKey);
|
|
25
|
+
if (cached) {
|
|
26
|
+
return cached;
|
|
27
|
+
}
|
|
28
|
+
const requirements = [];
|
|
29
|
+
for (const field of fields) {
|
|
30
|
+
requirements.unshift({
|
|
31
|
+
field,
|
|
32
|
+
paths: [path.clone()],
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
while (requirements.length > 0) {
|
|
36
|
+
const requirement = requirements.pop();
|
|
37
|
+
if (!requirement) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
const result = this.validateRequirement(requirement, visitedEdges, visitedGraphs, visitedFields);
|
|
41
|
+
if (result.success === false) {
|
|
42
|
+
this.cache.set(cacheKey, result);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
for (const innerRequirement of result.requirements) {
|
|
46
|
+
requirements.unshift(innerRequirement);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
this.cache.set(cacheKey, {
|
|
50
|
+
success: true,
|
|
51
|
+
errors: undefined,
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
errors: undefined,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
validateRequirement(requirement, visitedEdges, visitedGraphs, visitedFields) {
|
|
59
|
+
const nextPaths = [];
|
|
60
|
+
const errors = [];
|
|
61
|
+
if ('type' in requirement) {
|
|
62
|
+
for (const path of requirement.paths) {
|
|
63
|
+
const directPathsResult = this.pathFinder.findDirectPaths(path, requirement.type.childTypeName, null, visitedEdges);
|
|
64
|
+
if (directPathsResult.success) {
|
|
65
|
+
if (this.logger.isEnabled) {
|
|
66
|
+
this.logger.log(() => 'Possible direct paths:');
|
|
67
|
+
for (const path of directPathsResult.paths) {
|
|
68
|
+
this.logger.log(() => ' ' + path.toString());
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
nextPaths.push(...directPathsResult.paths);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
errors.push(...directPathsResult.errors);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const path of requirement.paths) {
|
|
78
|
+
const indirectPathsResult = this.pathFinder.findIndirectPaths(path, requirement.type.childTypeName, null, visitedEdges, visitedGraphs, visitedFields);
|
|
79
|
+
if (indirectPathsResult.success) {
|
|
80
|
+
if (this.logger.isEnabled) {
|
|
81
|
+
this.logger.log(() => 'Possible indirect paths:');
|
|
82
|
+
for (const path of indirectPathsResult.paths) {
|
|
83
|
+
this.logger.log(() => ' ' + path.toString());
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
nextPaths.push(...indirectPathsResult.paths);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
errors.push(...indirectPathsResult.errors);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (nextPaths.length === 0) {
|
|
93
|
+
if (this.logger.isEnabled) {
|
|
94
|
+
this.logger.log(() => 'Could not resolve from:');
|
|
95
|
+
for (const path of requirement.paths) {
|
|
96
|
+
this.logger.log(() => ' ' + path.toString());
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
errors,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (!requirement.type.field) {
|
|
105
|
+
return {
|
|
106
|
+
success: true,
|
|
107
|
+
requirements: [],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
success: true,
|
|
112
|
+
requirements: [
|
|
113
|
+
{
|
|
114
|
+
field: requirement.type.field,
|
|
115
|
+
paths: nextPaths.slice(),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const possibleTypes = this.supergraph.possibleTypesOf(requirement.field.typeName);
|
|
121
|
+
const needsAbstractMove = !possibleTypes.includes(requirement.field.typeName);
|
|
122
|
+
if (needsAbstractMove) {
|
|
123
|
+
const requirements = [];
|
|
124
|
+
for (const possibleType of possibleTypes) {
|
|
125
|
+
const abstractMoveRequirement = {
|
|
126
|
+
type: {
|
|
127
|
+
parentTypeName: requirement.field.typeName,
|
|
128
|
+
childTypeName: possibleType,
|
|
129
|
+
field: {
|
|
130
|
+
...requirement.field,
|
|
131
|
+
typeName: possibleType,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
paths: requirement.paths,
|
|
135
|
+
};
|
|
136
|
+
requirements.push(abstractMoveRequirement);
|
|
137
|
+
}
|
|
138
|
+
this.logger.log(() => 'Abstract move');
|
|
139
|
+
return {
|
|
140
|
+
success: true,
|
|
141
|
+
requirements,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
for (const path of requirement.paths) {
|
|
145
|
+
const directPathsResult = this.pathFinder.findDirectPaths(path, requirement.field.typeName, requirement.field.fieldName, visitedEdges);
|
|
146
|
+
if (directPathsResult.success) {
|
|
147
|
+
if (this.logger.isEnabled) {
|
|
148
|
+
this.logger.log(() => 'Possible direct paths:');
|
|
149
|
+
for (const path of directPathsResult.paths) {
|
|
150
|
+
this.logger.log(() => ' ' + path.toString());
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
nextPaths.push(...directPathsResult.paths);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
errors.push(...directPathsResult.errors);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const path of requirement.paths) {
|
|
160
|
+
const indirectPathsResult = this.pathFinder.findIndirectPaths(path, requirement.field.typeName, requirement.field.fieldName, visitedEdges, visitedGraphs, visitedFields);
|
|
161
|
+
if (indirectPathsResult.success) {
|
|
162
|
+
if (this.logger.isEnabled) {
|
|
163
|
+
this.logger.log(() => 'Possible indirect paths:');
|
|
164
|
+
for (const path of indirectPathsResult.paths) {
|
|
165
|
+
this.logger.log(() => ' ' + path.toString());
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
nextPaths.push(...indirectPathsResult.paths);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
errors.push(...indirectPathsResult.errors);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (nextPaths.length === 0) {
|
|
175
|
+
this.logger.log(() => `Failed to resolve field ${requirement.field.typeName}.${requirement.field.fieldName} from:`);
|
|
176
|
+
if (this.logger.isEnabled) {
|
|
177
|
+
for (const path of requirement.paths) {
|
|
178
|
+
this.logger.log(() => ` ` + path);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
errors: errors.filter(e => e.isMatchingField(requirement.field.typeName, requirement.field.fieldName)),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (!requirement.field.selectionSet || requirement.field.selectionSet.length === 0) {
|
|
187
|
+
return {
|
|
188
|
+
success: true,
|
|
189
|
+
requirements: [],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
requirements: requirement.field.selectionSet.map(field => ({
|
|
195
|
+
field,
|
|
196
|
+
paths: nextPaths.slice(),
|
|
197
|
+
})),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
isExternal(edge) {
|
|
201
|
+
if (!isFieldEdge(edge)) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
if (edge.move.provided) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
if (!edge.head.typeState) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
if (edge.head.typeState.kind !== 'object') {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
const fieldState = edge.head.typeState.fields.get(edge.move.fieldName);
|
|
214
|
+
if (!fieldState) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
const objectTypeStateInGraph = edge.head.typeState.byGraph.get(edge.head.graphId);
|
|
218
|
+
if (!objectTypeStateInGraph) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
const fieldStateInGraph = fieldState.byGraph.get(edge.head.graphId);
|
|
222
|
+
if (!fieldStateInGraph) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const external = fieldState.byGraph.get(edge.head.graphId)?.external ?? false;
|
|
226
|
+
if (!external) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
const isFedV1 = fieldStateInGraph.version === 'v1.0';
|
|
230
|
+
if (isFedV1 && objectTypeStateInGraph.extension && fieldState.usedAsKey) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
if (!fieldStateInGraph.usedAsKey) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
let hasNonExternalFields = false;
|
|
237
|
+
if (isFedV1) {
|
|
238
|
+
for (const [fieldName, fieldState] of edge.head.typeState.fields) {
|
|
239
|
+
if (fieldName === edge.move.fieldName) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const fieldStateInGraph = fieldState.byGraph.get(edge.head.graphId);
|
|
243
|
+
if (!fieldStateInGraph) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (!fieldStateInGraph.external) {
|
|
247
|
+
hasNonExternalFields = true;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (hasNonExternalFields) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
if (objectTypeStateInGraph.extension) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
isOverridden(edge) {
|
|
261
|
+
if (!isFieldEdge(edge)) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
if (!edge.head.typeState) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (!edge.head.typeState || edge.head.typeState.kind !== 'object') {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
const fieldState = edge.head.typeState.fields.get(edge.move.fieldName);
|
|
271
|
+
if (!fieldState) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
if (!fieldState.override) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
const overriddenGraphId = this.supergraph.graphNameToId(fieldState.override);
|
|
278
|
+
if (!overriddenGraphId) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
return edge.head.graphId === overriddenGraphId;
|
|
282
|
+
}
|
|
283
|
+
isEdgeResolvable(edge, path, visitedEdges, visitedGraphs, visitedFields) {
|
|
284
|
+
this.logger.group(() => 'Checking resolvability of ' + edge);
|
|
285
|
+
this.logger.log(() => 'Visited graphs: ' + visitedGraphs.join(','));
|
|
286
|
+
const resolvability = edge.getResolvability(concatIfNotExistsString(visitedGraphs, edge.tail.graphName));
|
|
287
|
+
if (resolvability) {
|
|
288
|
+
this.logger.groupEnd(() => resolvability.success
|
|
289
|
+
? `Can move to ${edge}`
|
|
290
|
+
: `Cannot move to ${edge} (already visited: ${resolvability.error.kind})`);
|
|
291
|
+
return resolvability;
|
|
292
|
+
}
|
|
293
|
+
if (isFieldEdge(edge)) {
|
|
294
|
+
if (this.isOverridden(edge)) {
|
|
295
|
+
this.logger.groupEnd(() => 'Cannot move to ' + edge + ' because it is overridden');
|
|
296
|
+
return edge.setResolvable(false, visitedGraphs, SatisfiabilityError.forMissingField(edge.tail.graphName, edge.move.typeName, edge.move.fieldName));
|
|
297
|
+
}
|
|
298
|
+
if (edge.move.requires) {
|
|
299
|
+
this.logger.log(() => 'Detected @requires');
|
|
300
|
+
const newVisitedGraphs = concatIfNotExistsString(visitedGraphs, edge.tail.graphName);
|
|
301
|
+
const newVisitedFields = concatIfNotExistsFields(visitedFields, edge.move.requires);
|
|
302
|
+
this.logger.log(() => 'Visited graphs: ' + newVisitedGraphs.join(','));
|
|
303
|
+
if (this.canResolveFields(edge.move.requires.fields, path, visitedEdges.concat(edge), newVisitedGraphs, newVisitedFields).success) {
|
|
304
|
+
this.logger.groupEnd(() => 'Can move to ' + edge);
|
|
305
|
+
return edge.setResolvable(true, newVisitedGraphs);
|
|
306
|
+
}
|
|
307
|
+
this.logger.groupEnd(() => 'Cannot move to ' + edge + ' because @require is not resolvable');
|
|
308
|
+
return {
|
|
309
|
+
success: false,
|
|
310
|
+
error: SatisfiabilityError.forRequire(edge.head.graphName, edge.move.typeName, edge.move.fieldName),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
else if (this.isExternal(edge)) {
|
|
314
|
+
this.logger.groupEnd(() => 'Cannot move to ' + edge + ' because it is external and cross-graph');
|
|
315
|
+
return edge.setResolvable(false, visitedGraphs, SatisfiabilityError.forExternal(edge.head.graphName, edge.move.typeName, edge.move.fieldName));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else if (isEntityEdge(edge)) {
|
|
319
|
+
this.logger.log(() => 'Detected @key');
|
|
320
|
+
const newVisitedGraphs = concatIfNotExistsString(visitedGraphs, edge.tail.graphName);
|
|
321
|
+
const newVisitedFields = concatIfNotExistsFields(visitedFields, edge.move.keyFields);
|
|
322
|
+
this.logger.log(() => 'Visited graphs: ' + newVisitedGraphs.join(','));
|
|
323
|
+
if (this.canResolveFields(edge.move.keyFields.fields, path, visitedEdges.concat(edge), newVisitedGraphs, newVisitedFields).success) {
|
|
324
|
+
this.logger.groupEnd(() => 'Can move to ' + edge);
|
|
325
|
+
return edge.setResolvable(true, newVisitedGraphs);
|
|
326
|
+
}
|
|
327
|
+
this.logger.groupEnd(() => 'Cannot move to ' + edge + ' because key fields are not resolvable');
|
|
328
|
+
return edge.setResolvable(false, newVisitedGraphs, SatisfiabilityError.forKey(edge.head.graphName, edge.tail.graphName, edge.head.typeName, edge.move.keyFields.toString()));
|
|
329
|
+
}
|
|
330
|
+
this.logger.groupEnd(() => 'Can move to ' + edge);
|
|
331
|
+
return edge.setResolvable(true, visitedGraphs);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { lazy } from './helpers';
|
|
2
|
+
export class FieldMove {
|
|
3
|
+
typeName;
|
|
4
|
+
fieldName;
|
|
5
|
+
requires;
|
|
6
|
+
provides;
|
|
7
|
+
provided;
|
|
8
|
+
_toString = lazy(() => {
|
|
9
|
+
let str = this.fieldName;
|
|
10
|
+
if (this.requires) {
|
|
11
|
+
str += ` @require(${this.requires})`;
|
|
12
|
+
}
|
|
13
|
+
if (this.provides) {
|
|
14
|
+
str += ` @provides(${this.provides})`;
|
|
15
|
+
}
|
|
16
|
+
if (this.provided) {
|
|
17
|
+
str += ' @provided';
|
|
18
|
+
}
|
|
19
|
+
return str;
|
|
20
|
+
});
|
|
21
|
+
constructor(typeName, fieldName, requires = null, provides = null, provided = false) {
|
|
22
|
+
this.typeName = typeName;
|
|
23
|
+
this.fieldName = fieldName;
|
|
24
|
+
this.requires = requires;
|
|
25
|
+
this.provides = provides;
|
|
26
|
+
this.provided = provided;
|
|
27
|
+
}
|
|
28
|
+
toString() {
|
|
29
|
+
return this._toString.get();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class AbstractMove {
|
|
33
|
+
toString() {
|
|
34
|
+
return `🔮`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class EntityMove {
|
|
38
|
+
keyFields;
|
|
39
|
+
_toString = lazy(() => `🔑 ${this.keyFields}`);
|
|
40
|
+
constructor(keyFields) {
|
|
41
|
+
this.keyFields = keyFields;
|
|
42
|
+
}
|
|
43
|
+
toString() {
|
|
44
|
+
return this._toString.get();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { lazy } from './helpers';
|
|
2
|
+
export class Node {
|
|
3
|
+
index;
|
|
4
|
+
typeName;
|
|
5
|
+
typeState;
|
|
6
|
+
graphId;
|
|
7
|
+
graphName;
|
|
8
|
+
_toString = lazy(() => `${this.typeName}/${this.graphName}`);
|
|
9
|
+
isLeaf = false;
|
|
10
|
+
childrenIndex = new Map();
|
|
11
|
+
visitedGraphCombos = [];
|
|
12
|
+
constructor(index, typeName, typeState, graphId, graphName) {
|
|
13
|
+
this.index = index;
|
|
14
|
+
this.typeName = typeName;
|
|
15
|
+
this.typeState = typeState;
|
|
16
|
+
this.graphId = graphId;
|
|
17
|
+
this.graphName = graphName;
|
|
18
|
+
if (this.typeState === undefined) {
|
|
19
|
+
throw new Error(`Expected typeState to be defined for ${typeName} in subgraph ${graphName}`);
|
|
20
|
+
}
|
|
21
|
+
if (this.typeState === null ||
|
|
22
|
+
this.typeState.kind === 'scalar' ||
|
|
23
|
+
this.typeState.kind === 'enum') {
|
|
24
|
+
this.isLeaf = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
withoutState() {
|
|
28
|
+
this.childrenIndex = new Map();
|
|
29
|
+
this.visitedGraphCombos = [];
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
addFieldEdge(fieldName, edgeAt) {
|
|
33
|
+
const id = `field__${fieldName}`;
|
|
34
|
+
const indexes = this.childrenIndex.get(id);
|
|
35
|
+
if (indexes) {
|
|
36
|
+
if (!indexes.includes(edgeAt)) {
|
|
37
|
+
indexes.push(edgeAt);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
this.childrenIndex.set(id, [edgeAt]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
getFieldEdgeIndexes(fieldName) {
|
|
45
|
+
return this.childrenIndex.get(`field__${fieldName}`);
|
|
46
|
+
}
|
|
47
|
+
addEntityEdge(typeName, edgeAt) {
|
|
48
|
+
this.pushToChildrenIndex(`entity__${typeName}`, edgeAt);
|
|
49
|
+
}
|
|
50
|
+
getEntityEdgeIndexes(typeName) {
|
|
51
|
+
return this.childrenIndex.get(`entity__${typeName}`);
|
|
52
|
+
}
|
|
53
|
+
addAbstractEdge(typeName, edgeAt) {
|
|
54
|
+
this.pushToChildrenIndex(`abstract__${typeName}`, edgeAt);
|
|
55
|
+
}
|
|
56
|
+
getAbstractEdgeIndexes(typeName) {
|
|
57
|
+
return this.childrenIndex.get(`abstract__${typeName}`);
|
|
58
|
+
}
|
|
59
|
+
addCrossGraphEdge(typeName, edgeAt) {
|
|
60
|
+
this.pushToChildrenIndex(`cross-graph__${typeName}`, edgeAt);
|
|
61
|
+
}
|
|
62
|
+
getCrossGraphEdgeIndexes(typeName) {
|
|
63
|
+
return this.childrenIndex.get(`cross-graph__${typeName}`);
|
|
64
|
+
}
|
|
65
|
+
pushToChildrenIndex(id, edgeAt) {
|
|
66
|
+
const indexes = this.childrenIndex.get(id);
|
|
67
|
+
if (indexes) {
|
|
68
|
+
if (!indexes.includes(edgeAt)) {
|
|
69
|
+
indexes.push(edgeAt);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.childrenIndex.set(id, [edgeAt]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
isGraphComboVisited(graphNameProvidesCombos) {
|
|
77
|
+
return this.visitedGraphCombos.some(visitedGraphs => visitedGraphs.every(g => graphNameProvidesCombos.includes(g)));
|
|
78
|
+
}
|
|
79
|
+
setGraphComboAsVisited(graphNames) {
|
|
80
|
+
this.visitedGraphCombos.push(graphNames);
|
|
81
|
+
}
|
|
82
|
+
toString() {
|
|
83
|
+
return this._toString.get();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { isFieldEdge } from './edge';
|
|
2
|
+
import { lazy } from './helpers';
|
|
3
|
+
export class OperationPath {
|
|
4
|
+
_rootNode;
|
|
5
|
+
_toString = lazy(() => {
|
|
6
|
+
let str = this._rootNode.toString();
|
|
7
|
+
for (let i = 0; i < this.previousEdges.length; i++) {
|
|
8
|
+
const edge = this.previousEdges[i];
|
|
9
|
+
if (edge) {
|
|
10
|
+
str += ` -(${edge.move})-> ${edge.tail}`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return str;
|
|
14
|
+
});
|
|
15
|
+
previousNodes = [];
|
|
16
|
+
previousEdges = [];
|
|
17
|
+
previousSteps = [];
|
|
18
|
+
constructor(_rootNode) {
|
|
19
|
+
this._rootNode = _rootNode;
|
|
20
|
+
}
|
|
21
|
+
move(edge) {
|
|
22
|
+
this._toString.invalidate();
|
|
23
|
+
this.advance(edge);
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
clone() {
|
|
27
|
+
const newPath = new OperationPath(this._rootNode);
|
|
28
|
+
newPath.previousNodes = this.previousNodes.slice();
|
|
29
|
+
newPath.previousEdges = this.previousEdges.slice();
|
|
30
|
+
newPath.previousSteps = this.previousSteps.slice();
|
|
31
|
+
return newPath;
|
|
32
|
+
}
|
|
33
|
+
depth() {
|
|
34
|
+
return this.previousEdges.length;
|
|
35
|
+
}
|
|
36
|
+
edge() {
|
|
37
|
+
return this.previousEdges[this.previousEdges.length - 1];
|
|
38
|
+
}
|
|
39
|
+
steps() {
|
|
40
|
+
return this.previousSteps;
|
|
41
|
+
}
|
|
42
|
+
tail() {
|
|
43
|
+
return this.edge()?.tail;
|
|
44
|
+
}
|
|
45
|
+
rootNode() {
|
|
46
|
+
return this._rootNode;
|
|
47
|
+
}
|
|
48
|
+
isVisitedEdge(edge) {
|
|
49
|
+
return this.previousEdges.includes(edge);
|
|
50
|
+
}
|
|
51
|
+
toString() {
|
|
52
|
+
return this._toString.get();
|
|
53
|
+
}
|
|
54
|
+
advance(edge) {
|
|
55
|
+
this.previousEdges.push(edge);
|
|
56
|
+
this.previousNodes.push(edge.head);
|
|
57
|
+
this.previousSteps.push(isFieldEdge(edge)
|
|
58
|
+
? {
|
|
59
|
+
typeName: edge.move.typeName,
|
|
60
|
+
fieldName: edge.move.fieldName,
|
|
61
|
+
}
|
|
62
|
+
: {
|
|
63
|
+
typeName: edge.tail.typeName,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Logger, LoggerContext } from '../../../../utils/logger';
|
|
2
|
+
import { SUPERGRAPH_ID } from './constants';
|
|
3
|
+
import { FieldsResolver } from './fields';
|
|
4
|
+
import { Graph } from './graph';
|
|
5
|
+
import { MoveValidator } from './move-validator';
|
|
6
|
+
import { Walker } from './walker';
|
|
7
|
+
export class Supergraph {
|
|
8
|
+
supergraph;
|
|
9
|
+
mergedGraph;
|
|
10
|
+
fieldsResolver;
|
|
11
|
+
moveRequirementChecker;
|
|
12
|
+
logger = new Logger('Supergraph', new LoggerContext());
|
|
13
|
+
constructor(supergraphState) {
|
|
14
|
+
this.fieldsResolver = new FieldsResolver(supergraphState);
|
|
15
|
+
this.supergraph = new Graph(this.logger, SUPERGRAPH_ID, 'supergraph', supergraphState, this.fieldsResolver, true);
|
|
16
|
+
this.mergedGraph = new Graph(this.logger, SUPERGRAPH_ID, 'merged', supergraphState, this.fieldsResolver);
|
|
17
|
+
for (const [id, subgraphState] of supergraphState.subgraphs) {
|
|
18
|
+
this.mergedGraph.addSubgraph(new Graph(this.logger, id, subgraphState.graph.name, supergraphState, this.fieldsResolver, false)
|
|
19
|
+
.addFromRoots()
|
|
20
|
+
.addFromEntities()
|
|
21
|
+
.addUnreachableTypes());
|
|
22
|
+
}
|
|
23
|
+
this.mergedGraph.joinSubgraphs();
|
|
24
|
+
this.supergraph.addFromRoots();
|
|
25
|
+
this.moveRequirementChecker = new MoveValidator(this.logger, this.mergedGraph);
|
|
26
|
+
}
|
|
27
|
+
validate() {
|
|
28
|
+
return new Walker(this.logger, this.moveRequirementChecker, this.supergraph, this.mergedGraph).walk('dfs');
|
|
29
|
+
}
|
|
30
|
+
validateOperation(operation, steps) {
|
|
31
|
+
return new Walker(this.logger, this.moveRequirementChecker, this.supergraph, this.mergedGraph).walkTrail(operation, steps);
|
|
32
|
+
}
|
|
33
|
+
}
|