@theguild/federation-composition 0.6.2 → 0.7.0-alpha-20240104152841-c814a1f

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 (40) hide show
  1. package/README.md +1 -0
  2. package/cjs/graphql/sort-sdl.js +1 -0
  3. package/cjs/subgraph/helpers.js +14 -6
  4. package/cjs/subgraph/validation/rules/elements/key.js +31 -11
  5. package/cjs/subgraph/validation/rules/elements/provides.js +5 -5
  6. package/cjs/subgraph/validation/rules/elements/requires.js +3 -1
  7. package/cjs/subgraph/validation/rules/known-directives-rule.js +3 -1
  8. package/cjs/subgraph/validation/rules/provided-arguments-on-directives-rule.js +6 -0
  9. package/cjs/subgraph/validation/validate-state.js +24 -1
  10. package/cjs/subgraph/validation/validate-subgraph.js +44 -28
  11. package/cjs/supergraph/composition/ast.js +24 -1
  12. package/cjs/supergraph/composition/object-type.js +150 -34
  13. package/cjs/supergraph/state.js +1 -0
  14. package/cjs/supergraph/validation/rules/enum-values-rule.js +3 -1
  15. package/cjs/supergraph/validation/rules/fields-of-the-same-type-rule.js +39 -16
  16. package/cjs/supergraph/validation/rules/satisfiablity-rule.js +318 -5
  17. package/esm/graphql/sort-sdl.js +1 -0
  18. package/esm/subgraph/helpers.js +14 -6
  19. package/esm/subgraph/validation/rules/elements/key.js +32 -12
  20. package/esm/subgraph/validation/rules/elements/provides.js +5 -5
  21. package/esm/subgraph/validation/rules/elements/requires.js +3 -1
  22. package/esm/subgraph/validation/rules/known-directives-rule.js +3 -1
  23. package/esm/subgraph/validation/rules/provided-arguments-on-directives-rule.js +6 -0
  24. package/esm/subgraph/validation/validate-state.js +22 -0
  25. package/esm/subgraph/validation/validate-subgraph.js +45 -29
  26. package/esm/supergraph/composition/ast.js +24 -1
  27. package/esm/supergraph/composition/object-type.js +150 -34
  28. package/esm/supergraph/state.js +1 -0
  29. package/esm/supergraph/validation/rules/enum-values-rule.js +3 -1
  30. package/esm/supergraph/validation/rules/fields-of-the-same-type-rule.js +39 -16
  31. package/esm/supergraph/validation/rules/satisfiablity-rule.js +318 -5
  32. package/package.json +1 -1
  33. package/typings/subgraph/validation/validate-state.d.cts +2 -1
  34. package/typings/subgraph/validation/validate-state.d.ts +2 -1
  35. package/typings/supergraph/composition/ast.d.cts +1 -0
  36. package/typings/supergraph/composition/ast.d.ts +1 -0
  37. package/typings/supergraph/composition/common.d.cts +2 -0
  38. package/typings/supergraph/composition/common.d.ts +2 -0
  39. package/typings/supergraph/composition/object-type.d.cts +2 -0
  40. package/typings/supergraph/composition/object-type.d.ts +2 -0
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.objectTypeBuilder = exports.isRealExtension = void 0;
4
+ const helpers_js_1 = require("../../utils/helpers.js");
4
5
  const ast_js_1 = require("./ast.js");
5
6
  const common_js_1 = require("./common.js");
6
7
  function isRealExtension(meta, version) {
@@ -50,6 +51,9 @@ function objectTypeBuilder() {
50
51
  });
51
52
  }
52
53
  type.interfaces.forEach(interfaceName => objectTypeState.interfaces.add(interfaceName));
54
+ if (type.keys.length) {
55
+ objectTypeState.isEntity = true;
56
+ }
53
57
  objectTypeState.byGraph.set(graph.id, {
54
58
  hasDefinition: isDefinition,
55
59
  extension: type.extension,
@@ -74,8 +78,7 @@ function objectTypeBuilder() {
74
78
  const shouldForceType = !usedAsKey && !isExternal && !fieldState.internal.seenNonExternal;
75
79
  const shouldChangeType = shouldForceType ||
76
80
  (!isExternal &&
77
- !field.type.endsWith('!') &&
78
- fieldState.type.endsWith('!'));
81
+ fieldState.type.lastIndexOf('!') > field.type.lastIndexOf('!'));
79
82
  if (shouldChangeType) {
80
83
  fieldState.type = field.type;
81
84
  }
@@ -94,12 +97,12 @@ function objectTypeBuilder() {
94
97
  if (field.scopes) {
95
98
  fieldState.scopes.push(...field.scopes);
96
99
  }
97
- if (field.override) {
98
- fieldState.override = field.override;
99
- }
100
100
  if (field.description && !fieldState.description) {
101
101
  fieldState.description = field.description;
102
102
  }
103
+ if (field.override) {
104
+ fieldState.override = field.override;
105
+ }
103
106
  if (field.deprecated && !fieldState.deprecated) {
104
107
  fieldState.deprecated = field.deprecated;
105
108
  }
@@ -110,6 +113,7 @@ function objectTypeBuilder() {
110
113
  type: field.type,
111
114
  external: field.external,
112
115
  inaccessible: field.inaccessible,
116
+ description: field.description ?? null,
113
117
  override: field.override,
114
118
  provides: field.provides,
115
119
  requires: field.requires,
@@ -148,7 +152,7 @@ function objectTypeBuilder() {
148
152
  }
149
153
  }
150
154
  },
151
- composeSupergraphNode(objectType, graphs, { graphNameToId }) {
155
+ composeSupergraphNode(objectType, graphs, { graphNameToId, supergraphState }) {
152
156
  const isQuery = objectType.name === 'Query';
153
157
  const joinTypes = isQuery
154
158
  ?
@@ -173,17 +177,91 @@ function objectTypeBuilder() {
173
177
  ];
174
178
  })
175
179
  .flat(1);
180
+ const fieldNamesOfImplementedInterfaces = {};
181
+ for (const interfaceName of objectType.interfaces) {
182
+ const interfaceState = supergraphState.interfaceTypes.get(interfaceName);
183
+ if (!interfaceState) {
184
+ throw new Error(`Interface "${interfaceName}" not found in Supergraph state`);
185
+ }
186
+ for (const [interfaceFieldName, interfaceField] of interfaceState.fields) {
187
+ const found = fieldNamesOfImplementedInterfaces[interfaceFieldName];
188
+ if (found) {
189
+ for (const graphId of interfaceField.byGraph.keys()) {
190
+ found.add(graphId);
191
+ }
192
+ }
193
+ else {
194
+ fieldNamesOfImplementedInterfaces[interfaceFieldName] = new Set(Array.from(interfaceField.byGraph.keys()));
195
+ }
196
+ }
197
+ }
198
+ if (objectType.isEntity) {
199
+ for (const [_, field] of objectType.fields) {
200
+ if (field.description) {
201
+ if (field.override) {
202
+ for (const [_, fieldInGraph] of field.byGraph) {
203
+ if (fieldInGraph.override && !fieldInGraph.shareable) {
204
+ field.description = fieldInGraph.description ?? undefined;
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ function shouldSetExternalOnJoinField(fieldStateInGraph, graphId, fieldState) {
212
+ if (!fieldStateInGraph.external) {
213
+ return false;
214
+ }
215
+ if (fieldStateInGraph.provided) {
216
+ return true;
217
+ }
218
+ if (fieldState.usedAsKey && objectType.byGraph.get(graphId).extension === true) {
219
+ return false;
220
+ }
221
+ return true;
222
+ }
223
+ function createJoinFields(fieldInGraphs, field, { hasDifferentOutputType, overridesMap, }) {
224
+ return fieldInGraphs
225
+ .map(([graphId, meta]) => {
226
+ const type = hasDifferentOutputType ? meta.type : undefined;
227
+ const override = meta.override ?? undefined;
228
+ const usedOverridden = provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId);
229
+ const external = shouldSetExternalOnJoinField(meta, graphId, field);
230
+ const provides = meta.provides ?? undefined;
231
+ const requires = meta.requires ?? undefined;
232
+ const definesSomething = !!type || !!override || !!provides || !!requires || !!usedOverridden;
233
+ const isRequiredOrProvided = meta.provided || meta.required;
234
+ if (external &&
235
+ objectType.byGraph.get(graphId).extension === true &&
236
+ !definesSomething &&
237
+ !isRequiredOrProvided) {
238
+ return null;
239
+ }
240
+ return {
241
+ graph: graphId,
242
+ type,
243
+ override,
244
+ usedOverridden,
245
+ external,
246
+ provides,
247
+ requires,
248
+ };
249
+ })
250
+ .filter(helpers_js_1.isDefined);
251
+ }
176
252
  return (0, ast_js_1.createObjectTypeNode)({
177
253
  name: objectType.name,
178
254
  ast: {
179
255
  directives: (0, common_js_1.convertToConst)(objectType.ast.directives),
180
256
  },
181
257
  description: objectType.description,
182
- fields: Array.from(objectType.fields.values()).map(field => {
258
+ fields: Array.from(objectType.fields.values())
259
+ .map(field => {
183
260
  const fieldInGraphs = Array.from(field.byGraph.entries());
184
261
  const hasDifferentOutputType = fieldInGraphs.some(([_, meta]) => meta.type !== field.type);
185
262
  const isDefinedEverywhere = field.byGraph.size === (isQuery ? graphs.size : objectType.byGraph.size);
186
263
  let joinFields = [];
264
+ const overridesMap = {};
187
265
  const differencesBetweenGraphs = {
188
266
  override: false,
189
267
  type: false,
@@ -199,6 +277,10 @@ function objectTypeBuilder() {
199
277
  }
200
278
  if (meta.override !== null) {
201
279
  differencesBetweenGraphs.override = true;
280
+ const originalGraphId = graphNameToId(meta.override);
281
+ if (originalGraphId) {
282
+ overridesMap[originalGraphId] = graphId;
283
+ }
202
284
  }
203
285
  if (meta.provides !== null) {
204
286
  differencesBetweenGraphs.provides = true;
@@ -210,12 +292,30 @@ function objectTypeBuilder() {
210
292
  differencesBetweenGraphs.type = true;
211
293
  }
212
294
  }
295
+ if (!isQuery && field.byGraph.size === 1) {
296
+ const graphId = field.byGraph.keys().next().value;
297
+ const fieldInGraph = field.byGraph.get(graphId);
298
+ if (fieldInGraph.external &&
299
+ !fieldInGraph.usedAsKey &&
300
+ !fieldInGraph.required &&
301
+ !fieldInGraph.provided &&
302
+ !provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId) &&
303
+ graphs.get(graphId).version === 'v1.0') {
304
+ return null;
305
+ }
306
+ }
213
307
  if (isQuery) {
214
308
  if (differencesBetweenGraphs.override) {
215
- const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null);
309
+ const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null &&
310
+ (objectType.byGraph.size > 1
311
+ ?
312
+ true
313
+ :
314
+ typeof graphNameToId(meta.override) === 'string'));
216
315
  joinFields = graphsWithOverride.map(([graphId, meta]) => ({
217
316
  graph: graphId,
218
317
  override: meta.override ?? undefined,
318
+ usedOverridden: provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
219
319
  type: differencesBetweenGraphs.type ? meta.type : undefined,
220
320
  external: meta.external ?? undefined,
221
321
  provides: meta.provides ?? undefined,
@@ -237,16 +337,23 @@ function objectTypeBuilder() {
237
337
  else if (isDefinedEverywhere) {
238
338
  const hasDifferencesBetweenGraphs = Object.values(differencesBetweenGraphs).some(value => value === true);
239
339
  if (differencesBetweenGraphs.override) {
240
- const mutedGraphs = fieldInGraphs
340
+ const overriddenGraphs = fieldInGraphs
241
341
  .map(([_, meta]) => (meta.override ? graphNameToId(meta.override) : null))
242
342
  .filter((graphId) => typeof graphId === 'string');
243
- const graphsToEmit = fieldInGraphs.filter(([graphId, f]) => f.external || !mutedGraphs.includes(graphId));
343
+ const graphsToEmit = fieldInGraphs.filter(([graphId, f]) => {
344
+ const isExternal = f.external === true;
345
+ const isOverridden = overriddenGraphs.includes(graphId);
346
+ const needsToPrintUsedOverridden = provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId);
347
+ const isRequired = f.required === true;
348
+ return (isExternal && isRequired) || needsToPrintUsedOverridden || !isOverridden;
349
+ });
244
350
  if (!(graphsToEmit.length === 1 &&
245
351
  joinTypes.length === 1 &&
246
352
  joinTypes[0].graph === graphsToEmit[0][0])) {
247
353
  joinFields = graphsToEmit.map(([graphId, meta]) => ({
248
354
  graph: graphId,
249
355
  override: meta.override ?? undefined,
356
+ usedOverridden: provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
250
357
  type: differencesBetweenGraphs.type ? meta.type : undefined,
251
358
  external: meta.external ?? undefined,
252
359
  provides: meta.provides ?? undefined,
@@ -255,27 +362,25 @@ function objectTypeBuilder() {
255
362
  }
256
363
  }
257
364
  else if (hasDifferencesBetweenGraphs) {
258
- joinFields = fieldInGraphs.map(([graphId, meta]) => ({
259
- graph: graphId,
260
- type: differencesBetweenGraphs.type ? meta.type : undefined,
261
- override: meta.override ?? undefined,
262
- external: meta.external
263
- ?
264
- field.usedAsKey && objectType.byGraph.get(graphId).extension === true
265
- ? false
266
- : true
267
- : undefined,
268
- provides: meta.provides ?? undefined,
269
- requires: meta.requires ?? undefined,
270
- }));
365
+ joinFields = createJoinFields(fieldInGraphs, field, {
366
+ hasDifferentOutputType,
367
+ overridesMap,
368
+ });
271
369
  }
272
370
  }
273
371
  else {
274
372
  if (differencesBetweenGraphs.override) {
275
- const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null);
276
- joinFields = graphsWithOverride.map(([graphId, meta]) => ({
373
+ const overriddenGraphs = fieldInGraphs
374
+ .map(([_, meta]) => (meta.override ? graphNameToId(meta.override) : null))
375
+ .filter((graphId) => typeof graphId === 'string');
376
+ const graphsToPrintJoinField = fieldInGraphs.filter(([graphId, meta]) => meta.override !== null ||
377
+ meta.external === true ||
378
+ (meta.shareable && !overriddenGraphs.includes(graphId)) ||
379
+ provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId));
380
+ joinFields = graphsToPrintJoinField.map(([graphId, meta]) => ({
277
381
  graph: graphId,
278
382
  override: meta.override ?? undefined,
383
+ usedOverridden: provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
279
384
  type: differencesBetweenGraphs.type ? meta.type : undefined,
280
385
  external: meta.external ?? undefined,
281
386
  provides: meta.provides ?? undefined,
@@ -283,14 +388,10 @@ function objectTypeBuilder() {
283
388
  }));
284
389
  }
285
390
  else {
286
- joinFields = fieldInGraphs.map(([graphId, meta]) => ({
287
- graph: graphId,
288
- type: hasDifferentOutputType ? meta.type : undefined,
289
- override: meta.override ?? undefined,
290
- external: meta.external ?? undefined,
291
- provides: meta.provides ?? undefined,
292
- requires: meta.requires ?? undefined,
293
- }));
391
+ joinFields = createJoinFields(fieldInGraphs, field, {
392
+ hasDifferentOutputType,
393
+ overridesMap,
394
+ });
294
395
  }
295
396
  }
296
397
  return {
@@ -313,6 +414,7 @@ function objectTypeBuilder() {
313
414
  !joinFields[0].override &&
314
415
  !joinFields[0].provides &&
315
416
  !joinFields[0].requires &&
417
+ !joinFields[0].usedOverridden &&
316
418
  !joinFields[0].type
317
419
  ? []
318
420
  : joinFields,
@@ -339,7 +441,8 @@ function objectTypeBuilder() {
339
441
  };
340
442
  }),
341
443
  };
342
- }),
444
+ })
445
+ .filter(helpers_js_1.isDefined),
343
446
  interfaces: Array.from(objectType.interfaces),
344
447
  tags: Array.from(objectType.tags),
345
448
  inaccessible: objectType.inaccessible,
@@ -367,6 +470,18 @@ function objectTypeBuilder() {
367
470
  };
368
471
  }
369
472
  exports.objectTypeBuilder = objectTypeBuilder;
473
+ function provideUsedOverriddenValue(fieldName, overridesMap, fieldNamesOfImplementedInterfaces, graphId) {
474
+ const inGraphs = fieldNamesOfImplementedInterfaces[fieldName];
475
+ const hasMatchingInterfaceFieldInGraph = inGraphs && inGraphs.has(graphId);
476
+ if (!hasMatchingInterfaceFieldInGraph) {
477
+ return false;
478
+ }
479
+ const hasOverride = typeof overridesMap[graphId] === 'string';
480
+ if (!hasOverride) {
481
+ return false;
482
+ }
483
+ return true;
484
+ }
370
485
  function getOrCreateObjectType(state, typeName) {
371
486
  const existing = state.get(typeName);
372
487
  if (existing) {
@@ -376,6 +491,7 @@ function getOrCreateObjectType(state, typeName) {
376
491
  name: typeName,
377
492
  tags: new Set(),
378
493
  hasDefinition: false,
494
+ isEntity: false,
379
495
  inaccessible: false,
380
496
  authenticated: false,
381
497
  policies: [],
@@ -122,6 +122,7 @@ function createSupergraphStateBuilder() {
122
122
  graphNameToId(graphName) {
123
123
  return graphNameToIdMap[graphName] ?? null;
124
124
  },
125
+ supergraphState: state,
125
126
  };
126
127
  const numberOfExpectedNodes = state.directives.size +
127
128
  state.scalarTypes.size +
@@ -10,7 +10,9 @@ function EnumValuesRule(context) {
10
10
  const total = enumTypeState.byGraph.size;
11
11
  for (const [valueName, valueState] of enumTypeState.values) {
12
12
  if (valueState.byGraph.size !== total) {
13
- missingValues.push(valueName);
13
+ if (!valueState.inaccessible) {
14
+ missingValues.push(valueName);
15
+ }
14
16
  }
15
17
  }
16
18
  if (missingValues.length === 0) {
@@ -2,31 +2,54 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FieldsOfTheSameTypeRule = void 0;
4
4
  const graphql_1 = require("graphql");
5
+ function stripNonNull(type) {
6
+ return type.replace(/!$/, '');
7
+ }
8
+ function stripList(type) {
9
+ const startsAt = type.indexOf('[');
10
+ const endsAt = type.lastIndexOf(']');
11
+ return type.slice(startsAt + 1, endsAt);
12
+ }
13
+ function normalizeOutputTypeStrings({ superType, localType, }) {
14
+ let superTypeNormalized = superType;
15
+ let localTypeNormalized = localType;
16
+ if (!superTypeNormalized.endsWith('!')) {
17
+ localTypeNormalized = stripNonNull(localTypeNormalized);
18
+ }
19
+ if (superTypeNormalized.startsWith('[') && localTypeNormalized.startsWith('[')) {
20
+ const innerSuper = stripList(superTypeNormalized);
21
+ let innerLocal = stripList(localTypeNormalized);
22
+ if (!innerSuper.endsWith('!')) {
23
+ innerLocal = stripNonNull(innerLocal);
24
+ }
25
+ const superSign = superTypeNormalized.endsWith('!') ? '!' : '';
26
+ const localSign = localTypeNormalized.endsWith('!') ? '!' : '';
27
+ superTypeNormalized = `[${innerSuper}]${superSign}`;
28
+ localTypeNormalized = `[${innerLocal}]${localSign}`;
29
+ }
30
+ return {
31
+ superType: superTypeNormalized,
32
+ localType: localTypeNormalized,
33
+ };
34
+ }
5
35
  function FieldsOfTheSameTypeRule(context) {
6
36
  return {
7
37
  ObjectTypeField(objectTypeState, fieldState) {
8
38
  const typeToGraphs = new Map();
9
39
  fieldState.byGraph.forEach((field, graphName) => {
10
- const isNullable = !field.type.endsWith('!');
11
- const isNullableInSupergraph = !fieldState.type.endsWith('!');
12
- const isMatchingNonNullablePart = fieldState.type.replace(/!$/, '') === field.type.replace(/!$/, '');
13
- let normalizedOutputType;
14
- if (isMatchingNonNullablePart) {
15
- normalizedOutputType = isNullableInSupergraph
16
- ? isNullable
17
- ? field.type
18
- : field.type.replace(/!$/, '')
19
- : field.type;
20
- }
21
- else {
22
- normalizedOutputType = field.type;
23
- }
24
- const existing = typeToGraphs.get(normalizedOutputType);
40
+ const normalizedOutputTypes = normalizeOutputTypeStrings({
41
+ superType: fieldState.type,
42
+ localType: field.type,
43
+ });
44
+ const fieldOutputType = normalizedOutputTypes.superType === normalizedOutputTypes.localType
45
+ ? normalizedOutputTypes.localType
46
+ : field.type;
47
+ const existing = typeToGraphs.get(fieldOutputType);
25
48
  if (existing) {
26
49
  existing.push(graphName);
27
50
  }
28
51
  else {
29
- typeToGraphs.set(normalizedOutputType, [graphName]);
52
+ typeToGraphs.set(fieldOutputType, [graphName]);
30
53
  }
31
54
  });
32
55
  if (typeToGraphs.size > 1) {