@apollo/federation-internals 2.0.0-alpha.6 → 2.0.0-preview.2

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 (105) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/buildSchema.d.ts +7 -3
  3. package/dist/buildSchema.d.ts.map +1 -1
  4. package/dist/buildSchema.js +41 -22
  5. package/dist/buildSchema.js.map +1 -1
  6. package/dist/coreSpec.d.ts +26 -4
  7. package/dist/coreSpec.d.ts.map +1 -1
  8. package/dist/coreSpec.js +86 -25
  9. package/dist/coreSpec.js.map +1 -1
  10. package/dist/definitions.d.ts +50 -43
  11. package/dist/definitions.d.ts.map +1 -1
  12. package/dist/definitions.js +201 -217
  13. package/dist/definitions.js.map +1 -1
  14. package/dist/directiveAndTypeSpecification.d.ts +38 -0
  15. package/dist/directiveAndTypeSpecification.d.ts.map +1 -0
  16. package/dist/directiveAndTypeSpecification.js +196 -0
  17. package/dist/directiveAndTypeSpecification.js.map +1 -0
  18. package/dist/error.d.ts +9 -1
  19. package/dist/error.d.ts.map +1 -1
  20. package/dist/error.js +20 -2
  21. package/dist/error.js.map +1 -1
  22. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  23. package/dist/extractSubgraphsFromSupergraph.js +28 -93
  24. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  25. package/dist/federation.d.ts +88 -46
  26. package/dist/federation.d.ts.map +1 -1
  27. package/dist/federation.js +706 -228
  28. package/dist/federation.js.map +1 -1
  29. package/dist/federationSpec.d.ts +19 -0
  30. package/dist/federationSpec.d.ts.map +1 -0
  31. package/dist/federationSpec.js +91 -0
  32. package/dist/federationSpec.js.map +1 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +7 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/joinSpec.d.ts +1 -0
  38. package/dist/joinSpec.d.ts.map +1 -1
  39. package/dist/joinSpec.js +1 -0
  40. package/dist/joinSpec.js.map +1 -1
  41. package/dist/operations.d.ts +8 -1
  42. package/dist/operations.d.ts.map +1 -1
  43. package/dist/operations.js +11 -4
  44. package/dist/operations.js.map +1 -1
  45. package/dist/print.d.ts +11 -9
  46. package/dist/print.d.ts.map +1 -1
  47. package/dist/print.js +21 -11
  48. package/dist/print.js.map +1 -1
  49. package/dist/schemaUpgrader.d.ts +108 -0
  50. package/dist/schemaUpgrader.d.ts.map +1 -0
  51. package/dist/schemaUpgrader.js +498 -0
  52. package/dist/schemaUpgrader.js.map +1 -0
  53. package/dist/sharing.d.ts +3 -0
  54. package/dist/sharing.d.ts.map +1 -0
  55. package/dist/sharing.js +51 -0
  56. package/dist/sharing.js.map +1 -0
  57. package/dist/supergraphs.d.ts.map +1 -1
  58. package/dist/supergraphs.js +2 -3
  59. package/dist/supergraphs.js.map +1 -1
  60. package/dist/tagSpec.d.ts.map +1 -1
  61. package/dist/tagSpec.js +1 -3
  62. package/dist/tagSpec.js.map +1 -1
  63. package/dist/utils.d.ts +7 -0
  64. package/dist/utils.d.ts.map +1 -1
  65. package/dist/utils.js +34 -1
  66. package/dist/utils.js.map +1 -1
  67. package/dist/validate.d.ts.map +1 -1
  68. package/dist/validate.js +9 -4
  69. package/dist/validate.js.map +1 -1
  70. package/dist/validation/KnownTypeNamesInFederationRule.d.ts.map +1 -1
  71. package/dist/validation/KnownTypeNamesInFederationRule.js +1 -2
  72. package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -1
  73. package/dist/values.d.ts +1 -0
  74. package/dist/values.d.ts.map +1 -1
  75. package/dist/values.js +3 -2
  76. package/dist/values.js.map +1 -1
  77. package/package.json +3 -3
  78. package/src/__tests__/definitions.test.ts +19 -17
  79. package/src/__tests__/federation.test.ts +31 -0
  80. package/src/__tests__/operations.test.ts +2 -3
  81. package/src/__tests__/schemaUpgrader.test.ts +168 -0
  82. package/src/__tests__/subgraphValidation.test.ts +5 -19
  83. package/src/__tests__/values.test.ts +2 -4
  84. package/src/buildSchema.ts +55 -36
  85. package/src/coreSpec.ts +112 -31
  86. package/src/definitions.ts +247 -260
  87. package/src/directiveAndTypeSpecification.ts +276 -0
  88. package/src/error.ts +55 -5
  89. package/src/extractSubgraphsFromSupergraph.ts +34 -118
  90. package/src/federation.ts +912 -295
  91. package/src/federationSpec.ts +113 -0
  92. package/src/index.ts +2 -0
  93. package/src/joinSpec.ts +2 -1
  94. package/src/operations.ts +22 -7
  95. package/src/print.ts +51 -38
  96. package/src/schemaUpgrader.ts +657 -0
  97. package/src/sharing.ts +68 -0
  98. package/src/supergraphs.ts +3 -3
  99. package/src/tagSpec.ts +1 -3
  100. package/src/utils.ts +63 -0
  101. package/src/validate.ts +13 -7
  102. package/src/validation/KnownTypeNamesInFederationRule.ts +1 -7
  103. package/src/values.ts +7 -3
  104. package/tsconfig.test.tsbuildinfo +1 -1
  105. package/tsconfig.tsbuildinfo +1 -1
@@ -1,48 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ExternalTester = exports.addSubgraphToError = exports.addSubgraphToASTNode = exports.Subgraph = exports.Subgraphs = exports.subgraphsFromServiceList = exports.parseFieldSetArgument = exports.buildSubgraph = exports.isEntityType = exports.isFederationDirective = exports.isFederationField = exports.isFederationTypeName = exports.isFederationType = exports.isFederationSubgraphSchema = exports.federationBuiltIns = exports.FederationBuiltIns = exports.FEDERATION_RESERVED_SUBGRAPH_NAME = exports.entitiesFieldName = exports.serviceFieldName = exports.tagDirectiveName = exports.providesDirectiveName = exports.requiresDirectiveName = exports.externalDirectiveName = exports.extendsDirectiveName = exports.keyDirectiveName = exports.fieldSetTypeName = exports.anyTypeName = exports.serviceTypeName = exports.entityTypeName = void 0;
3
+ exports.removeInactiveProvidesAndRequires = exports.addSubgraphToError = exports.addSubgraphToASTNode = exports.Subgraph = exports.FEDERATION_OPERATION_FIELDS = exports.entitiesFieldName = exports.serviceFieldName = exports.FEDERATION_OPERATION_TYPES = exports.entityTypeSpec = exports.serviceTypeSpec = exports.anyTypeSpec = exports.Subgraphs = exports.subgraphsFromServiceList = exports.collectTargetFields = exports.parseFieldSetArgument = exports.newEmptyFederation2Schema = exports.buildSubgraph = exports.isEntityType = exports.isFederationField = exports.isFederationSubgraphSchema = exports.federationMetadata = exports.printSubgraphNames = exports.asFed2SubgraphDocument = exports.setSchemaAsFed2Subgraph = exports.FederationBlueprint = exports.FederationMetadata = exports.collectUsedExternalFieldsCoordinates = exports.FEDERATION_RESERVED_SUBGRAPH_NAME = void 0;
4
4
  const definitions_1 = require("./definitions");
5
5
  const utils_1 = require("./utils");
6
6
  const specifiedRules_1 = require("graphql/validation/specifiedRules");
7
7
  const graphql_1 = require("graphql");
8
- const print_1 = require("./print");
9
8
  const KnownTypeNamesInFederationRule_1 = require("./validation/KnownTypeNamesInFederationRule");
10
9
  const buildSchema_1 = require("./buildSchema");
11
10
  const operations_1 = require("./operations");
12
11
  const tagSpec_1 = require("./tagSpec");
13
12
  const error_1 = require("./error");
14
- exports.entityTypeName = '_Entity';
15
- exports.serviceTypeName = '_Service';
16
- exports.anyTypeName = '_Any';
17
- exports.fieldSetTypeName = '_FieldSet';
18
- exports.keyDirectiveName = 'key';
19
- exports.extendsDirectiveName = 'extends';
20
- exports.externalDirectiveName = 'external';
21
- exports.requiresDirectiveName = 'requires';
22
- exports.providesDirectiveName = 'provides';
23
- exports.tagDirectiveName = 'tag';
24
- exports.serviceFieldName = '_service';
25
- exports.entitiesFieldName = '_entities';
13
+ const sharing_1 = require("./sharing");
14
+ const coreSpec_1 = require("./coreSpec");
15
+ const federationSpec_1 = require("./federationSpec");
16
+ const print_1 = require("./print");
17
+ const directiveAndTypeSpecification_1 = require("./directiveAndTypeSpecification");
18
+ const linkSpec = coreSpec_1.LINK_VERSIONS.latest();
26
19
  const tagSpec = tagSpec_1.TAG_VERSIONS.latest();
20
+ const federationSpec = federationSpec_1.FEDERATION_VERSIONS.latest();
27
21
  exports.FEDERATION_RESERVED_SUBGRAPH_NAME = '_';
28
- const FEDERATION_TYPES = [
29
- exports.entityTypeName,
30
- exports.serviceTypeName,
31
- exports.anyTypeName,
32
- exports.fieldSetTypeName
33
- ];
34
- const FEDERATION_DIRECTIVES = [
35
- exports.keyDirectiveName,
36
- exports.extendsDirectiveName,
37
- exports.externalDirectiveName,
38
- exports.requiresDirectiveName,
39
- exports.providesDirectiveName,
40
- exports.tagDirectiveName
41
- ];
42
- const FEDERATION_ROOT_FIELDS = [
43
- exports.serviceFieldName,
44
- exports.entitiesFieldName
45
- ];
46
22
  const FEDERATION_OMITTED_VALIDATION_RULES = [
47
23
  graphql_1.PossibleTypeExtensionsRule,
48
24
  graphql_1.KnownTypeNamesRule
@@ -51,14 +27,11 @@ const FEDERATION_SPECIFIC_VALIDATION_RULES = [
51
27
  KnownTypeNamesInFederationRule_1.KnownTypeNamesInFederationRule
52
28
  ];
53
29
  const FEDERATION_VALIDATION_RULES = specifiedRules_1.specifiedSDLRules.filter(rule => !FEDERATION_OMITTED_VALIDATION_RULES.includes(rule)).concat(FEDERATION_SPECIFIC_VALIDATION_RULES);
54
- function validateFieldSetSelections(directiveName, selectionSet, hasExternalInParents, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields) {
30
+ function validateFieldSetSelections(directiveName, selectionSet, hasExternalInParents, federationMetadata, allowOnNonExternalLeafFields) {
55
31
  for (const selection of selectionSet.selections()) {
56
32
  if (selection.kind === 'FieldSelection') {
57
33
  const field = selection.element().definition;
58
- const isExternal = externalTester.isExternal(field);
59
- if (isExternal) {
60
- externalFieldCoordinatesCollector.push(field.coordinate);
61
- }
34
+ const isExternal = federationMetadata.isFieldExternal(field);
62
35
  if (field.hasArguments()) {
63
36
  throw error_1.ERROR_CATEGORIES.FIELDS_HAS_ARGS.get(directiveName).err({
64
37
  message: `field ${field.coordinate} cannot be included because it has arguments (fields with argument are not allowed in @${directiveName})`,
@@ -68,16 +41,16 @@ function validateFieldSetSelections(directiveName, selectionSet, hasExternalInPa
68
41
  const mustBeExternal = !selection.selectionSet && !allowOnNonExternalLeafFields && !hasExternalInParents;
69
42
  if (!isExternal && mustBeExternal) {
70
43
  const errorCode = error_1.ERROR_CATEGORIES.DIRECTIVE_FIELDS_MISSING_EXTERNAL.get(directiveName);
71
- if (externalTester.isFakeExternal(field)) {
44
+ if (federationMetadata.isFieldFakeExternal(field)) {
72
45
  throw errorCode.err({
73
46
  message: `field "${field.coordinate}" should not be part of a @${directiveName} since it is already "effectively" provided by this subgraph `
74
- + `(while it is marked @${exports.externalDirectiveName}, it is a @${exports.keyDirectiveName} field of an extension type, which are not internally considered external for historical/backward compatibility reasons)`,
47
+ + `(while it is marked @${federationSpec_1.externalDirectiveSpec.name}, it is a @${federationSpec_1.keyDirectiveSpec.name} field of an extension type, which are not internally considered external for historical/backward compatibility reasons)`,
75
48
  nodes: field.sourceAST
76
49
  });
77
50
  }
78
51
  else {
79
52
  throw errorCode.err({
80
- message: `field "${field.coordinate}" should not be part of a @${directiveName} since it is already provided by this subgraph (it is not marked @${exports.externalDirectiveName})`,
53
+ message: `field "${field.coordinate}" should not be part of a @${directiveName} since it is already provided by this subgraph (it is not marked @${federationSpec_1.externalDirectiveSpec.name})`,
81
54
  nodes: field.sourceAST
82
55
  });
83
56
  }
@@ -88,24 +61,24 @@ function validateFieldSetSelections(directiveName, selectionSet, hasExternalInPa
88
61
  if (!newHasExternalInParents && (0, definitions_1.isInterfaceType)(parentType)) {
89
62
  for (const implem of parentType.possibleRuntimeTypes()) {
90
63
  const fieldInImplem = implem.field(field.name);
91
- if (fieldInImplem && externalTester.isExternal(fieldInImplem)) {
64
+ if (fieldInImplem && federationMetadata.isFieldExternal(fieldInImplem)) {
92
65
  newHasExternalInParents = true;
93
66
  break;
94
67
  }
95
68
  }
96
69
  }
97
- validateFieldSetSelections(directiveName, selection.selectionSet, newHasExternalInParents, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields);
70
+ validateFieldSetSelections(directiveName, selection.selectionSet, newHasExternalInParents, federationMetadata, allowOnNonExternalLeafFields);
98
71
  }
99
72
  }
100
73
  else {
101
- validateFieldSetSelections(directiveName, selection.selectionSet, hasExternalInParents, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields);
74
+ validateFieldSetSelections(directiveName, selection.selectionSet, hasExternalInParents, federationMetadata, allowOnNonExternalLeafFields);
102
75
  }
103
76
  }
104
77
  }
105
- function validateFieldSet(type, directive, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields, onFields) {
78
+ function validateFieldSet(type, directive, federationMetadata, allowOnNonExternalLeafFields, onFields) {
106
79
  var _a;
107
80
  try {
108
- const fieldAcessor = onFields
81
+ const fieldAccessor = onFields
109
82
  ? (type, fieldName) => {
110
83
  const field = type.field(fieldName);
111
84
  if (field) {
@@ -114,9 +87,9 @@ function validateFieldSet(type, directive, externalTester, externalFieldCoordina
114
87
  return field;
115
88
  }
116
89
  : undefined;
117
- const selectionSet = parseFieldSetArgument(type, directive, fieldAcessor);
90
+ const selectionSet = parseFieldSetArgument({ parentType: type, directive, fieldAccessor });
118
91
  try {
119
- validateFieldSetSelections(directive.name, selectionSet, false, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields);
92
+ validateFieldSetSelections(directive.name, selectionSet, false, federationMetadata, allowOnNonExternalLeafFields);
120
93
  return undefined;
121
94
  }
122
95
  catch (e) {
@@ -155,7 +128,7 @@ function fieldSetTargetDescription(directive) {
155
128
  const targetKind = directive.parent instanceof definitions_1.FieldDefinition ? "field" : "type";
156
129
  return `${targetKind} "${(_a = directive.parent) === null || _a === void 0 ? void 0 : _a.coordinate}"`;
157
130
  }
158
- function validateAllFieldSet(definition, targetTypeExtractor, errorCollector, externalTester, externalFieldCoordinatesCollector, isOnParentType, allowOnNonExternalLeafFields, onFields) {
131
+ function validateAllFieldSet(definition, targetTypeExtractor, errorCollector, federationMetadata, isOnParentType, allowOnNonExternalLeafFields, onFields) {
159
132
  for (const application of definition.applications()) {
160
133
  const elt = application.parent;
161
134
  const type = targetTypeExtractor(elt);
@@ -169,19 +142,57 @@ function validateAllFieldSet(definition, targetTypeExtractor, errorCollector, ex
169
142
  nodes: (0, definitions_1.sourceASTs)(application).concat(isOnParentType ? [] : (0, definitions_1.sourceASTs)(type)),
170
143
  }));
171
144
  }
172
- const error = validateFieldSet(type, application, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields, onFields);
145
+ const error = validateFieldSet(type, application, federationMetadata, allowOnNonExternalLeafFields, onFields);
173
146
  if (error) {
174
147
  errorCollector.push(error);
175
148
  }
176
149
  }
177
150
  }
178
- function validateAllExternalFieldsUsed(schema, externalTester, allExternalFieldsUsedInFederationDirectivesCoordinates, errorCollector) {
179
- for (const type of schema.types()) {
151
+ function collectUsedExternalFieldsCoordinates(metadata) {
152
+ const usedExternalCoordinates = new Set();
153
+ collectUsedExternaFieldsForDirective(metadata, metadata.keyDirective(), type => type, usedExternalCoordinates);
154
+ collectUsedExternaFieldsForDirective(metadata, metadata.requiresDirective(), field => field.parent, usedExternalCoordinates);
155
+ collectUsedExternaFieldsForDirective(metadata, metadata.providesDirective(), field => {
156
+ const type = (0, definitions_1.baseType)(field.type);
157
+ return (0, definitions_1.isCompositeType)(type) ? type : undefined;
158
+ }, usedExternalCoordinates);
159
+ for (const itfType of metadata.schema.types('InterfaceType')) {
160
+ const runtimeTypes = itfType.possibleRuntimeTypes();
161
+ for (const field of itfType.fields()) {
162
+ for (const runtimeType of runtimeTypes) {
163
+ const implemField = runtimeType.field(field.name);
164
+ if (implemField && metadata.isFieldExternal(implemField)) {
165
+ usedExternalCoordinates.add(implemField.coordinate);
166
+ }
167
+ }
168
+ }
169
+ }
170
+ return usedExternalCoordinates;
171
+ }
172
+ exports.collectUsedExternalFieldsCoordinates = collectUsedExternalFieldsCoordinates;
173
+ function collectUsedExternaFieldsForDirective(metadata, definition, targetTypeExtractor, usedExternalCoordinates) {
174
+ for (const application of definition.applications()) {
175
+ const type = targetTypeExtractor(application.parent);
176
+ if (!type) {
177
+ continue;
178
+ }
179
+ collectTargetFields({
180
+ parentType: type,
181
+ directive: application,
182
+ includeInterfaceFieldsImplementations: true,
183
+ validate: false,
184
+ }).filter((field) => metadata.isFieldExternal(field))
185
+ .forEach((field) => usedExternalCoordinates.add(field.coordinate));
186
+ }
187
+ }
188
+ function validateAllExternalFieldsUsed(metadata, errorCollector) {
189
+ const allUsedExternals = collectUsedExternalFieldsCoordinates(metadata);
190
+ for (const type of metadata.schema.types()) {
180
191
  if (!(0, definitions_1.isObjectType)(type) && !(0, definitions_1.isInterfaceType)(type)) {
181
192
  continue;
182
193
  }
183
194
  for (const field of type.fields()) {
184
- if (!externalTester.isExternal(field) || allExternalFieldsUsedInFederationDirectivesCoordinates.includes(field.coordinate)) {
195
+ if (!metadata.isFieldExternal(field) || allUsedExternals.has(field.coordinate)) {
185
196
  continue;
186
197
  }
187
198
  if (!isFieldSatisfyingInterface(field)) {
@@ -194,10 +205,25 @@ function validateAllExternalFieldsUsed(schema, externalTester, allExternalFields
194
205
  }
195
206
  }
196
207
  }
208
+ function validateNoExternalOnInterfaceFields(metadata, errorCollector) {
209
+ for (const itf of metadata.schema.types('InterfaceType')) {
210
+ for (const field of itf.fields()) {
211
+ if (metadata.isFieldExternal(field)) {
212
+ errorCollector.push(error_1.ERRORS.EXTERNAL_ON_INTERFACE.err({
213
+ message: `Interface type field "${field.coordinate}" is marked @external but @external is not allowed on interface fields (it is nonsensical).`,
214
+ nodes: field.sourceAST,
215
+ }));
216
+ }
217
+ }
218
+ }
219
+ }
197
220
  function isFieldSatisfyingInterface(field) {
198
221
  return field.parent.interfaces().some(itf => itf.field(field.name));
199
222
  }
200
- function validateInterfaceRuntimeImplementationFieldsTypes(itf, externalTester, errorCollector) {
223
+ function validateInterfaceRuntimeImplementationFieldsTypes(itf, metadata, errorCollector) {
224
+ var _a;
225
+ const requiresDirective = (_a = federationMetadata(itf.schema())) === null || _a === void 0 ? void 0 : _a.requiresDirective();
226
+ (0, utils_1.assert)(requiresDirective, 'Schema should be a federation subgraph, but @requires directive not found');
201
227
  const runtimeTypes = itf.possibleRuntimeTypes();
202
228
  for (const field of itf.fields()) {
203
229
  const withExternalOrRequires = [];
@@ -210,7 +236,7 @@ function validateInterfaceRuntimeImplementationFieldsTypes(itf, externalTester,
210
236
  if (implemField.sourceAST) {
211
237
  nodes.push(implemField.sourceAST);
212
238
  }
213
- if (externalTester.isExternal(implemField) || implemField.hasAppliedDirective(exports.requiresDirectiveName)) {
239
+ if (metadata.isFieldExternal(implemField) || implemField.hasAppliedDirective(requiresDirective)) {
214
240
  withExternalOrRequires.push(implemField);
215
241
  }
216
242
  const returnType = implemField.type;
@@ -229,77 +255,193 @@ const printFieldCoordinate = (f) => `"${f.coordinate}"`;
229
255
  function formatFieldsToReturnType([type, implems]) {
230
256
  return `${(0, utils_1.joinStrings)(implems.map(printFieldCoordinate))} ${implems.length == 1 ? 'has' : 'have'} type "${type}"`;
231
257
  }
232
- class FederationBuiltIns extends definitions_1.BuiltIns {
233
- addBuiltInTypes(schema) {
234
- super.addBuiltInTypes(schema);
235
- this.addBuiltInUnion(schema, exports.entityTypeName);
236
- this.addBuiltInObject(schema, exports.serviceTypeName).addField('sdl', schema.stringType());
237
- this.addBuiltInScalar(schema, exports.anyTypeName);
238
- this.addBuiltInScalar(schema, exports.fieldSetTypeName);
239
- }
240
- addBuiltInDirectives(schema) {
241
- super.addBuiltInDirectives(schema);
242
- const fieldSetType = new definitions_1.NonNullType(schema.type(exports.fieldSetTypeName));
243
- const keyDirective = this.addBuiltInDirective(schema, exports.keyDirectiveName)
244
- .addLocations(graphql_1.DirectiveLocation.OBJECT, graphql_1.DirectiveLocation.INTERFACE);
245
- keyDirective.repeatable = true;
246
- keyDirective.addArgument('fields', fieldSetType);
247
- this.addBuiltInDirective(schema, exports.extendsDirectiveName)
248
- .addLocations(graphql_1.DirectiveLocation.OBJECT, graphql_1.DirectiveLocation.INTERFACE);
249
- this.addBuiltInDirective(schema, exports.externalDirectiveName)
250
- .addLocations(graphql_1.DirectiveLocation.OBJECT, graphql_1.DirectiveLocation.FIELD_DEFINITION);
251
- for (const name of [exports.requiresDirectiveName, exports.providesDirectiveName]) {
252
- this.addBuiltInDirective(schema, name)
253
- .addLocations(graphql_1.DirectiveLocation.FIELD_DEFINITION)
254
- .addArgument('fields', fieldSetType);
255
- }
256
- const directive = this.addBuiltInDirective(schema, 'tag').addLocations(...tagSpec_1.tagLocations);
257
- directive.addArgument("name", new definitions_1.NonNullType(schema.stringType()));
258
- }
259
- prepareValidation(schema) {
260
- super.prepareValidation(schema);
261
- let entityType = schema.type(exports.entityTypeName);
262
- if (!entityType.isBuiltIn) {
263
- if (entityType.membersCount() === 0) {
264
- entityType.remove();
265
- }
266
- entityType = schema.builtInTypes('UnionType', true).find(u => u.name === exports.entityTypeName);
267
- }
268
- entityType.clearTypes();
269
- for (const objectType of schema.types("ObjectType")) {
270
- if (isEntityType(objectType)) {
271
- entityType.addType(objectType);
272
- }
273
- }
274
- const hasEntities = entityType.membersCount() > 0;
275
- if (!hasEntities) {
276
- entityType.remove();
277
- }
278
- const queryRoot = schema.schemaDefinition.root("query");
279
- const queryType = queryRoot ? queryRoot.type : schema.addType(new definitions_1.ObjectType("Query"));
280
- const entityField = queryType.field(exports.entitiesFieldName);
281
- if (hasEntities) {
282
- const anyType = schema.type(exports.anyTypeName);
283
- (0, utils_1.assert)(anyType, `The schema should have the _Any type`);
284
- const entityFieldType = new definitions_1.NonNullType(new definitions_1.ListType(entityType));
285
- if (!entityField) {
286
- this.addBuiltInField(queryType, exports.entitiesFieldName, entityFieldType)
287
- .addArgument('representations', new definitions_1.NonNullType(new definitions_1.ListType(new definitions_1.NonNullType(anyType))));
288
- }
289
- else if (!entityField.type) {
290
- entityField.type = entityType;
258
+ function checkIfFed2Schema(schema) {
259
+ const core = schema.coreFeatures;
260
+ if (!core) {
261
+ return false;
262
+ }
263
+ const federationFeature = core.getByIdentity(federationSpec.identity);
264
+ return !!federationFeature && federationFeature.url.version.satisfies(new coreSpec_1.FeatureVersion(2, 0));
265
+ }
266
+ class FederationMetadata {
267
+ constructor(schema) {
268
+ this.schema = schema;
269
+ }
270
+ onInvalidate() {
271
+ this._externalTester = undefined;
272
+ this._sharingPredicate = undefined;
273
+ this._isFed2Schema = undefined;
274
+ }
275
+ isFed2Schema() {
276
+ if (!this._isFed2Schema) {
277
+ this._isFed2Schema = checkIfFed2Schema(this.schema);
278
+ }
279
+ return this._isFed2Schema;
280
+ }
281
+ externalTester() {
282
+ if (!this._externalTester) {
283
+ this._externalTester = new ExternalTester(this.schema);
284
+ }
285
+ return this._externalTester;
286
+ }
287
+ sharingPredicate() {
288
+ if (!this._sharingPredicate) {
289
+ this._sharingPredicate = (0, sharing_1.computeShareables)(this.schema);
290
+ }
291
+ return this._sharingPredicate;
292
+ }
293
+ isFieldExternal(field) {
294
+ return this.externalTester().isExternal(field);
295
+ }
296
+ isFieldPartiallyExternal(field) {
297
+ return this.externalTester().isPartiallyExternal(field);
298
+ }
299
+ isFieldFullyExternal(field) {
300
+ return this.externalTester().isFullyExternal(field);
301
+ }
302
+ isFieldFakeExternal(field) {
303
+ return this.externalTester().isFakeExternal(field);
304
+ }
305
+ selectionSelectsAnyExternalField(selectionSet) {
306
+ return this.externalTester().selectsAnyExternalField(selectionSet);
307
+ }
308
+ isFieldShareable(field) {
309
+ return this.sharingPredicate()(field);
310
+ }
311
+ federationDirectiveNameInSchema(name) {
312
+ if (this.isFed2Schema()) {
313
+ const coreFeatures = this.schema.coreFeatures;
314
+ (0, utils_1.assert)(coreFeatures, 'Schema should be a core schema');
315
+ const federationFeature = coreFeatures.getByIdentity(federationSpec.identity);
316
+ (0, utils_1.assert)(federationFeature, 'Schema should have the federation feature');
317
+ return federationFeature.directiveNameInSchema(name);
318
+ }
319
+ else {
320
+ return name;
321
+ }
322
+ }
323
+ federationTypeNameInSchema(name) {
324
+ if (name.charAt(0) === '_') {
325
+ return name;
326
+ }
327
+ if (this.isFed2Schema()) {
328
+ const coreFeatures = this.schema.coreFeatures;
329
+ (0, utils_1.assert)(coreFeatures, 'Schema should be a core schema');
330
+ const federationFeature = coreFeatures.getByIdentity(federationSpec.identity);
331
+ (0, utils_1.assert)(federationFeature, 'Schema should have the federation feature');
332
+ return federationFeature.typeNameInSchema(name);
333
+ }
334
+ else {
335
+ return '_' + name;
336
+ }
337
+ }
338
+ getFederationDirective(name) {
339
+ const directive = this.schema.directive(this.federationDirectiveNameInSchema(name));
340
+ (0, utils_1.assert)(directive, `The provided schema does not have federation directive @${name}`);
341
+ return directive;
342
+ }
343
+ keyDirective() {
344
+ return this.getFederationDirective(federationSpec_1.keyDirectiveSpec.name);
345
+ }
346
+ extendsDirective() {
347
+ return this.getFederationDirective(federationSpec_1.extendsDirectiveSpec.name);
348
+ }
349
+ externalDirective() {
350
+ return this.getFederationDirective(federationSpec_1.externalDirectiveSpec.name);
351
+ }
352
+ requiresDirective() {
353
+ return this.getFederationDirective(federationSpec_1.requiresDirectiveSpec.name);
354
+ }
355
+ providesDirective() {
356
+ return this.getFederationDirective(federationSpec_1.providesDirectiveSpec.name);
357
+ }
358
+ shareableDirective() {
359
+ return this.getFederationDirective(federationSpec_1.shareableDirectiveSpec.name);
360
+ }
361
+ tagDirective() {
362
+ return this.getFederationDirective(federationSpec_1.tagDirectiveSpec.name);
363
+ }
364
+ allFederationDirectives() {
365
+ const baseDirectives = [
366
+ this.keyDirective(),
367
+ this.externalDirective(),
368
+ this.requiresDirective(),
369
+ this.providesDirective(),
370
+ this.tagDirective(),
371
+ this.extendsDirective(),
372
+ ];
373
+ return this.isFed2Schema()
374
+ ? baseDirectives.concat(this.shareableDirective())
375
+ : baseDirectives;
376
+ }
377
+ entityType() {
378
+ return this.schema.type(this.federationTypeNameInSchema(exports.entityTypeSpec.name));
379
+ }
380
+ anyType() {
381
+ return this.schema.type(this.federationTypeNameInSchema(exports.anyTypeSpec.name));
382
+ }
383
+ serviceType() {
384
+ return this.schema.type(this.federationTypeNameInSchema(exports.serviceTypeSpec.name));
385
+ }
386
+ fieldSetType() {
387
+ return this.schema.type(this.federationTypeNameInSchema(federationSpec_1.fieldSetTypeSpec.name));
388
+ }
389
+ allFederationTypes() {
390
+ const baseTypes = [
391
+ this.anyType(),
392
+ this.serviceType(),
393
+ this.fieldSetType(),
394
+ ];
395
+ const entityType = this.entityType();
396
+ if (entityType) {
397
+ baseTypes.push(entityType);
398
+ }
399
+ return baseTypes;
400
+ }
401
+ }
402
+ exports.FederationMetadata = FederationMetadata;
403
+ class FederationBlueprint extends definitions_1.SchemaBlueprint {
404
+ onAddedCoreFeature(schema, feature) {
405
+ super.onAddedCoreFeature(schema, feature);
406
+ if (feature.url.identity === federationSpec_1.federationIdentity) {
407
+ const spec = federationSpec_1.FEDERATION_VERSIONS.find(feature.url.version);
408
+ if (spec) {
409
+ spec.addElementsToSchema(schema);
291
410
  }
292
411
  }
293
- else if (entityField) {
294
- entityField.remove();
412
+ }
413
+ onMissingDirectiveDefinition(schema, name) {
414
+ if (name === coreSpec_1.linkDirectiveDefaultName) {
415
+ linkSpec.addToSchema(schema);
416
+ return schema.directive(name);
295
417
  }
296
- if (!queryType.field(exports.serviceFieldName)) {
297
- this.addBuiltInField(queryType, exports.serviceFieldName, schema.type(exports.serviceTypeName));
418
+ return super.onMissingDirectiveDefinition(schema, name);
419
+ }
420
+ ignoreParsedField(type, fieldName) {
421
+ if (!exports.FEDERATION_OPERATION_FIELDS.includes(fieldName)) {
422
+ return false;
423
+ }
424
+ const metadata = federationMetadata(type.schema());
425
+ return !!metadata && !metadata.isFed2Schema();
426
+ }
427
+ onConstructed(schema) {
428
+ const existing = federationMetadata(schema);
429
+ if (!existing) {
430
+ schema['_federationMetadata'] = new FederationMetadata(schema);
298
431
  }
299
432
  }
433
+ onDirectiveDefinitionAndSchemaParsed(schema) {
434
+ completeSubgraphSchema(schema);
435
+ }
436
+ onInvalidation(schema) {
437
+ super.onInvalidation(schema);
438
+ const metadata = federationMetadata(schema);
439
+ (0, utils_1.assert)(metadata, 'Federation schema should have had its metadata set on construction');
440
+ FederationMetadata.prototype['onInvalidate'].call(metadata);
441
+ }
300
442
  onValidation(schema) {
301
443
  var _a;
302
- const errors = super.onValidation(schema, [exports.tagDirectiveName]);
444
+ const errors = super.onValidation(schema);
303
445
  for (const k of definitions_1.allSchemaRootKinds) {
304
446
  const type = (_a = schema.schemaDefinition.root(k)) === null || _a === void 0 ? void 0 : _a.type;
305
447
  const defaultName = (0, definitions_1.defaultRootName)(k);
@@ -316,21 +458,25 @@ class FederationBuiltIns extends definitions_1.BuiltIns {
316
458
  type.rename(defaultName);
317
459
  }
318
460
  }
319
- const externalTester = new ExternalTester(schema);
320
- const externalFieldsInFedDirectivesCoordinates = [];
321
- const keyDirective = this.keyDirective(schema);
322
- validateAllFieldSet(keyDirective, type => type, errors, externalTester, externalFieldsInFedDirectivesCoordinates, true, true, field => {
323
- if ((0, definitions_1.isListType)(field.type) || (0, definitions_1.isUnionType)(field.type) || (0, definitions_1.isInterfaceType)(field.type)) {
324
- let kind = field.type.kind;
461
+ const metadata = federationMetadata(schema);
462
+ (0, utils_1.assert)(metadata, 'Federation schema should have had its metadata set on construction');
463
+ if (!metadata.isFed2Schema()) {
464
+ return errors;
465
+ }
466
+ const keyDirective = metadata.keyDirective();
467
+ validateAllFieldSet(keyDirective, type => type, errors, metadata, true, true, field => {
468
+ const type = (0, definitions_1.baseType)(field.type);
469
+ if ((0, definitions_1.isUnionType)(type) || (0, definitions_1.isInterfaceType)(type)) {
470
+ let kind = type.kind;
325
471
  kind = kind.slice(0, kind.length - 'Type'.length);
326
472
  throw error_1.ERRORS.KEY_FIELDS_SELECT_INVALID_TYPE.err({
327
473
  message: `field "${field.coordinate}" is a ${kind} type which is not allowed in @key`
328
474
  });
329
475
  }
330
476
  });
331
- validateAllFieldSet(this.requiresDirective(schema), field => field.parent, errors, externalTester, externalFieldsInFedDirectivesCoordinates, false, false);
332
- validateAllFieldSet(this.providesDirective(schema), field => {
333
- if (externalTester.isExternal(field)) {
477
+ validateAllFieldSet(metadata.requiresDirective(), field => field.parent, errors, metadata, false, false);
478
+ validateAllFieldSet(metadata.providesDirective(), field => {
479
+ if (metadata.isFieldExternal(field)) {
334
480
  throw new graphql_1.GraphQLError(`Cannot have both @provides and @external on field "${field.coordinate}"`, field.sourceAST);
335
481
  }
336
482
  const type = (0, definitions_1.baseType)(field.type);
@@ -341,93 +487,129 @@ class FederationBuiltIns extends definitions_1.BuiltIns {
341
487
  });
342
488
  }
343
489
  return type;
344
- }, errors, externalTester, externalFieldsInFedDirectivesCoordinates, false, false);
345
- validateAllExternalFieldsUsed(schema, externalTester, externalFieldsInFedDirectivesCoordinates, errors);
346
- const tagDirective = this.tagDirective(schema);
347
- if (!tagDirective.isBuiltIn) {
490
+ }, errors, metadata, false, false);
491
+ validateNoExternalOnInterfaceFields(metadata, errors);
492
+ validateAllExternalFieldsUsed(metadata, errors);
493
+ const tagDirective = metadata.tagDirective();
494
+ if (tagDirective) {
348
495
  const error = tagSpec.checkCompatibleDirective(tagDirective);
349
496
  if (error) {
350
497
  errors.push(error);
351
498
  }
352
499
  }
353
500
  for (const itf of schema.types('InterfaceType')) {
354
- validateInterfaceRuntimeImplementationFieldsTypes(itf, externalTester, errors);
501
+ validateInterfaceRuntimeImplementationFieldsTypes(itf, metadata, errors);
355
502
  }
356
503
  return errors;
357
504
  }
358
505
  validationRules() {
359
506
  return FEDERATION_VALIDATION_RULES;
360
507
  }
361
- keyDirective(schema) {
362
- return this.getTypedDirective(schema, exports.keyDirectiveName);
363
- }
364
- extendsDirective(schema) {
365
- return this.getTypedDirective(schema, exports.extendsDirectiveName);
366
- }
367
- externalDirective(schema) {
368
- return this.getTypedDirective(schema, exports.externalDirectiveName);
369
- }
370
- requiresDirective(schema) {
371
- return this.getTypedDirective(schema, exports.requiresDirectiveName);
508
+ }
509
+ exports.FederationBlueprint = FederationBlueprint;
510
+ const federationBlueprint = new FederationBlueprint();
511
+ function findUnusedNamedForLinkDirective(schema) {
512
+ if (!schema.directive(linkSpec.url.name)) {
513
+ return undefined;
372
514
  }
373
- providesDirective(schema) {
374
- return this.getTypedDirective(schema, exports.providesDirectiveName);
515
+ const baseName = linkSpec.url.name;
516
+ let n = 1;
517
+ for (;;) {
518
+ const candidate = baseName + n;
519
+ if (!schema.directive(candidate)) {
520
+ return candidate;
521
+ }
375
522
  }
376
- tagDirective(schema) {
377
- return this.getTypedDirective(schema, exports.tagDirectiveName);
523
+ }
524
+ function setSchemaAsFed2Subgraph(schema) {
525
+ let core = schema.coreFeatures;
526
+ let spec;
527
+ if (core) {
528
+ spec = core.coreDefinition;
529
+ (0, utils_1.assert)(spec.url.version.satisfies(linkSpec.version), `Fed2 schema must use @link with version >= 1.0, but schema uses ${spec.url}`);
378
530
  }
379
- maybeUpdateSubgraphDocument(schema, document) {
380
- document = super.maybeUpdateSubgraphDocument(schema, document);
381
- const definitions = document.definitions.concat();
382
- for (const directiveName of FEDERATION_DIRECTIVES) {
383
- const directive = schema.directive(directiveName);
384
- (0, utils_1.assert)(directive, 'This method should only have been called on a schema with federation built-ins');
385
- if (directive.isBuiltIn) {
386
- definitions.push((0, graphql_1.parse)((0, print_1.printDirectiveDefinition)(directive, print_1.defaultPrintOptions)).definitions[0]);
387
- }
388
- }
389
- return {
390
- kind: graphql_1.Kind.DOCUMENT,
391
- loc: document.loc,
392
- definitions
393
- };
531
+ else {
532
+ const alias = findUnusedNamedForLinkDirective(schema);
533
+ linkSpec.addToSchema(schema, alias);
534
+ spec = linkSpec;
535
+ core = schema.coreFeatures;
536
+ (0, utils_1.assert)(core, 'Schema should now be a core schema');
394
537
  }
538
+ (0, utils_1.assert)(!core.getByIdentity(federationSpec.identity), 'Schema already set as a federation subgraph');
539
+ schema.schemaDefinition.applyDirective(core.coreItself.nameInSchema, {
540
+ url: federationSpec.url.toString(),
541
+ import: federationSpec_1.FEDERATION2_SPEC_DIRECTIVES.map((spec) => `@${spec.name}`),
542
+ });
543
+ completeSubgraphSchema(schema);
395
544
  }
396
- exports.FederationBuiltIns = FederationBuiltIns;
397
- exports.federationBuiltIns = new FederationBuiltIns();
398
- function isFederationSubgraphSchema(schema) {
399
- return schema.builtIns instanceof FederationBuiltIns;
545
+ exports.setSchemaAsFed2Subgraph = setSchemaAsFed2Subgraph;
546
+ function asFed2SubgraphDocument(document) {
547
+ const fed2LinkExtension = {
548
+ kind: graphql_1.Kind.SCHEMA_EXTENSION,
549
+ directives: [{
550
+ kind: graphql_1.Kind.DIRECTIVE,
551
+ name: { kind: graphql_1.Kind.NAME, value: coreSpec_1.linkDirectiveDefaultName },
552
+ arguments: [{
553
+ kind: graphql_1.Kind.ARGUMENT,
554
+ name: { kind: graphql_1.Kind.NAME, value: 'url' },
555
+ value: { kind: graphql_1.Kind.STRING, value: federationSpec.url.toString() }
556
+ },
557
+ {
558
+ kind: graphql_1.Kind.ARGUMENT,
559
+ name: { kind: graphql_1.Kind.NAME, value: 'import' },
560
+ value: { kind: graphql_1.Kind.LIST, values: federationSpec_1.FEDERATION2_SPEC_DIRECTIVES.map((spec) => ({ kind: graphql_1.Kind.STRING, value: `@${spec.name}` })) }
561
+ }]
562
+ }]
563
+ };
564
+ return {
565
+ kind: graphql_1.Kind.DOCUMENT,
566
+ loc: document.loc,
567
+ definitions: document.definitions.concat(fed2LinkExtension)
568
+ };
400
569
  }
401
- exports.isFederationSubgraphSchema = isFederationSubgraphSchema;
402
- function isFederationType(type) {
403
- return isFederationTypeName(type.name);
570
+ exports.asFed2SubgraphDocument = asFed2SubgraphDocument;
571
+ function printSubgraphNames(names) {
572
+ return (0, utils_1.printHumanReadableList)(names.map(n => `"${n}"`), {
573
+ prefix: 'subgraph',
574
+ prefixPlural: 'subgraphs',
575
+ });
404
576
  }
405
- exports.isFederationType = isFederationType;
406
- function isFederationTypeName(typeName) {
407
- return FEDERATION_TYPES.includes(typeName);
577
+ exports.printSubgraphNames = printSubgraphNames;
578
+ function federationMetadata(schema) {
579
+ return schema['_federationMetadata'];
408
580
  }
409
- exports.isFederationTypeName = isFederationTypeName;
581
+ exports.federationMetadata = federationMetadata;
582
+ function isFederationSubgraphSchema(schema) {
583
+ return !!federationMetadata(schema);
584
+ }
585
+ exports.isFederationSubgraphSchema = isFederationSubgraphSchema;
410
586
  function isFederationField(field) {
411
587
  var _a;
412
588
  if (field.parent === ((_a = field.schema().schemaDefinition.root("query")) === null || _a === void 0 ? void 0 : _a.type)) {
413
- return FEDERATION_ROOT_FIELDS.includes(field.name);
589
+ return exports.FEDERATION_OPERATION_FIELDS.includes(field.name);
414
590
  }
415
591
  return false;
416
592
  }
417
593
  exports.isFederationField = isFederationField;
418
- function isFederationDirective(directive) {
419
- return FEDERATION_DIRECTIVES.includes(directive.name);
420
- }
421
- exports.isFederationDirective = isFederationDirective;
422
594
  function isEntityType(type) {
423
- return type.kind == "ObjectType" && type.hasAppliedDirective(exports.keyDirectiveName);
595
+ if (type.kind !== "ObjectType") {
596
+ return false;
597
+ }
598
+ const metadata = federationMetadata(type.schema());
599
+ return !!metadata && type.hasAppliedDirective(metadata.keyDirective());
424
600
  }
425
601
  exports.isEntityType = isEntityType;
426
- function buildSubgraph(name, source) {
602
+ function buildSubgraph(name, url, source) {
603
+ const buildOptions = {
604
+ blueprint: federationBlueprint,
605
+ validate: false,
606
+ };
607
+ let subgraph;
427
608
  try {
428
- return typeof source === 'string'
429
- ? (0, buildSchema_1.buildSchema)(new graphql_1.Source(source, name), exports.federationBuiltIns)
430
- : (0, buildSchema_1.buildSchemaFromAST)(source, exports.federationBuiltIns);
609
+ const schema = typeof source === 'string'
610
+ ? (0, buildSchema_1.buildSchema)(new graphql_1.Source(source, name), buildOptions)
611
+ : (0, buildSchema_1.buildSchemaFromAST)(source, buildOptions);
612
+ subgraph = new Subgraph(name, url, schema);
431
613
  }
432
614
  catch (e) {
433
615
  if (e instanceof graphql_1.GraphQLError) {
@@ -437,14 +619,73 @@ function buildSubgraph(name, source) {
437
619
  throw e;
438
620
  }
439
621
  }
622
+ return subgraph.validate();
440
623
  }
441
624
  exports.buildSubgraph = buildSubgraph;
442
- function parseFieldSetArgument(parentType, directive, fieldAccessor = (type, name) => type.field(name)) {
625
+ function newEmptyFederation2Schema() {
626
+ const schema = new definitions_1.Schema(federationBlueprint);
627
+ setSchemaAsFed2Subgraph(schema);
628
+ return schema;
629
+ }
630
+ exports.newEmptyFederation2Schema = newEmptyFederation2Schema;
631
+ function completeSubgraphSchema(schema) {
632
+ const coreFeatures = schema.coreFeatures;
633
+ if (coreFeatures) {
634
+ const fedFeature = coreFeatures.getByIdentity(federationSpec_1.federationIdentity);
635
+ if (fedFeature) {
636
+ completeFed2SubgraphSchema(schema);
637
+ }
638
+ else {
639
+ completeFed1SubgraphSchema(schema);
640
+ }
641
+ }
642
+ else {
643
+ const fedLink = schema.schemaDefinition.appliedDirectivesOf(coreSpec_1.linkDirectiveDefaultName).find(isFedSpecLinkDirective);
644
+ if (fedLink) {
645
+ linkSpec.addToSchema(schema);
646
+ completeFed2SubgraphSchema(schema);
647
+ }
648
+ else {
649
+ completeFed1SubgraphSchema(schema);
650
+ }
651
+ }
652
+ }
653
+ function isFedSpecLinkDirective(directive) {
654
+ const args = directive.arguments();
655
+ return directive.name === coreSpec_1.linkDirectiveDefaultName && args['url'] && args['url'].startsWith(federationSpec_1.federationIdentity);
656
+ }
657
+ function completeFed1SubgraphSchema(schema) {
658
+ federationSpec_1.fieldSetTypeSpec.checkOrAdd(schema, '_' + federationSpec_1.fieldSetTypeSpec.name);
659
+ federationSpec_1.keyDirectiveSpec.checkOrAdd(schema);
660
+ federationSpec_1.requiresDirectiveSpec.checkOrAdd(schema);
661
+ federationSpec_1.providesDirectiveSpec.checkOrAdd(schema);
662
+ federationSpec_1.extendsDirectiveSpec.checkOrAdd(schema);
663
+ federationSpec_1.externalDirectiveSpec.checkOrAdd(schema);
664
+ federationSpec_1.tagDirectiveSpec.checkOrAdd(schema);
665
+ }
666
+ function completeFed2SubgraphSchema(schema) {
667
+ const coreFeatures = schema.coreFeatures;
668
+ (0, utils_1.assert)(coreFeatures, 'This method should not have been called on a non-core schema');
669
+ const fedFeature = coreFeatures.getByIdentity(federationSpec_1.federationIdentity);
670
+ (0, utils_1.assert)(fedFeature, 'This method should not have been called on a schema with no @link for federation');
671
+ const spec = federationSpec_1.FEDERATION_VERSIONS.find(fedFeature.url.version);
672
+ if (!spec) {
673
+ throw error_1.ERRORS.UNKNOWN_FEDERATION_LINK_VERSION.err({
674
+ message: `Invalid version ${fedFeature.url.version} for the federation feature in @link direction on schema`,
675
+ nodes: fedFeature.directive.sourceAST
676
+ });
677
+ }
678
+ spec.addElementsToSchema(schema);
679
+ }
680
+ function parseFieldSetArgument({ parentType, directive, fieldAccessor, validate, }) {
443
681
  var _a;
444
682
  try {
445
- const selectionSet = (0, operations_1.parseSelectionSet)(parentType, validateFieldSetValue(directive), new definitions_1.VariableDefinitions(), undefined, fieldAccessor);
446
- selectionSet.validate();
447
- return selectionSet;
683
+ return (0, operations_1.parseSelectionSet)({
684
+ parentType,
685
+ source: validateFieldSetValue(directive),
686
+ fieldAccessor,
687
+ validate,
688
+ });
448
689
  }
449
690
  catch (e) {
450
691
  if (!(e instanceof graphql_1.GraphQLError)) {
@@ -459,7 +700,7 @@ function parseFieldSetArgument(parentType, directive, fieldAccessor = (type, nam
459
700
  if (msg.endsWith('.')) {
460
701
  msg = msg.slice(0, msg.length - 1);
461
702
  }
462
- if (directive.name === exports.keyDirectiveName) {
703
+ if (directive.name === federationSpec_1.keyDirectiveSpec.name) {
463
704
  msg = msg + ' (the field should be either be added to this subgraph or, if it should not be resolved by this subgraph, you need to add it to this subgraph with @external).';
464
705
  }
465
706
  else {
@@ -475,6 +716,39 @@ function parseFieldSetArgument(parentType, directive, fieldAccessor = (type, nam
475
716
  }
476
717
  }
477
718
  exports.parseFieldSetArgument = parseFieldSetArgument;
719
+ function collectTargetFields({ parentType, directive, includeInterfaceFieldsImplementations, validate = true, }) {
720
+ const fields = [];
721
+ try {
722
+ parseFieldSetArgument({
723
+ parentType,
724
+ directive,
725
+ fieldAccessor: (t, f) => {
726
+ const field = t.field(f);
727
+ if (field) {
728
+ fields.push(field);
729
+ if (includeInterfaceFieldsImplementations && (0, definitions_1.isInterfaceType)(t)) {
730
+ for (const implType of t.possibleRuntimeTypes()) {
731
+ const implField = implType.field(f);
732
+ if (implField) {
733
+ fields.push(implField);
734
+ }
735
+ }
736
+ }
737
+ }
738
+ return field;
739
+ },
740
+ validate,
741
+ });
742
+ }
743
+ catch (e) {
744
+ const isGraphQLError = (0, definitions_1.errorCauses)(e) !== undefined;
745
+ if (!isGraphQLError || validate) {
746
+ throw e;
747
+ }
748
+ }
749
+ return fields;
750
+ }
751
+ exports.collectTargetFields = collectTargetFields;
478
752
  function validateFieldSetValue(directive) {
479
753
  var _a;
480
754
  const fields = directive.arguments().fields;
@@ -506,7 +780,7 @@ function subgraphsFromServiceList(serviceList) {
506
780
  const subgraphs = new Subgraphs();
507
781
  for (const service of serviceList) {
508
782
  try {
509
- subgraphs.add(service.name, (_a = service.url) !== null && _a !== void 0 ? _a : '', service.typeDefs);
783
+ subgraphs.add(buildSubgraph(service.name, (_a = service.url) !== null && _a !== void 0 ? _a : '', service.typeDefs));
510
784
  }
511
785
  catch (e) {
512
786
  const causes = (0, definitions_1.errorCauses)(e);
@@ -525,18 +799,12 @@ class Subgraphs {
525
799
  constructor() {
526
800
  this.subgraphs = new utils_1.OrderedMap();
527
801
  }
528
- add(subgraphOrName, url, schema) {
529
- const toAdd = typeof subgraphOrName === 'string'
530
- ? new Subgraph(subgraphOrName, url, schema instanceof definitions_1.Schema ? schema : buildSubgraph(subgraphOrName, schema))
531
- : subgraphOrName;
532
- if (toAdd.name === exports.FEDERATION_RESERVED_SUBGRAPH_NAME) {
533
- throw error_1.ERRORS.INVALID_SUBGRAPH_NAME.err({ message: `Invalid name ${exports.FEDERATION_RESERVED_SUBGRAPH_NAME} for a subgraph: this name is reserved` });
534
- }
535
- if (this.subgraphs.has(toAdd.name)) {
536
- throw new Error(`A subgraph named ${toAdd.name} already exists` + (toAdd.url ? ` (with url '${toAdd.url}')` : ''));
802
+ add(subgraph) {
803
+ if (this.subgraphs.has(subgraph.name)) {
804
+ throw new Error(`A subgraph named ${subgraph.name} already exists` + (subgraph.url ? ` (with url '${subgraph.url}')` : ''));
537
805
  }
538
- this.subgraphs.add(toAdd.name, toAdd);
539
- return toAdd;
806
+ this.subgraphs.add(subgraph.name, subgraph);
807
+ return subgraph;
540
808
  }
541
809
  get(name) {
542
810
  return this.subgraphs.get(name);
@@ -555,22 +823,123 @@ class Subgraphs {
555
823
  yield subgraph;
556
824
  }
557
825
  }
826
+ validate() {
827
+ let errors = [];
828
+ for (const subgraph of this.values()) {
829
+ try {
830
+ subgraph.validate();
831
+ }
832
+ catch (e) {
833
+ const causes = (0, definitions_1.errorCauses)(e);
834
+ if (!causes) {
835
+ throw e;
836
+ }
837
+ errors = errors.concat(causes);
838
+ }
839
+ }
840
+ return errors.length === 0 ? undefined : errors;
841
+ }
558
842
  toString() {
559
843
  return '[' + this.subgraphs.keys().join(', ') + ']';
560
844
  }
561
845
  }
562
846
  exports.Subgraphs = Subgraphs;
847
+ exports.anyTypeSpec = (0, directiveAndTypeSpecification_1.createScalarTypeSpecification)({ name: '_Any' });
848
+ exports.serviceTypeSpec = (0, directiveAndTypeSpecification_1.createObjectTypeSpecification)({
849
+ name: '_Service',
850
+ fieldsFct: (schema) => [{ name: 'sdl', type: schema.stringType() }],
851
+ });
852
+ exports.entityTypeSpec = (0, directiveAndTypeSpecification_1.createUnionTypeSpecification)({
853
+ name: '_Entity',
854
+ membersFct: (schema) => {
855
+ return schema.types("ObjectType").filter(isEntityType).map((t) => t.name);
856
+ },
857
+ });
858
+ exports.FEDERATION_OPERATION_TYPES = [exports.anyTypeSpec, exports.serviceTypeSpec, exports.entityTypeSpec];
859
+ exports.serviceFieldName = '_service';
860
+ exports.entitiesFieldName = '_entities';
861
+ exports.FEDERATION_OPERATION_FIELDS = [exports.serviceFieldName, exports.entitiesFieldName];
563
862
  class Subgraph {
564
- constructor(name, url, schema, validateSchema = true) {
863
+ constructor(name, url, schema) {
565
864
  this.name = name;
566
865
  this.url = url;
567
866
  this.schema = schema;
568
- if (validateSchema) {
569
- schema.validate();
867
+ if (name === exports.FEDERATION_RESERVED_SUBGRAPH_NAME) {
868
+ throw error_1.ERRORS.INVALID_SUBGRAPH_NAME.err({ message: `Invalid name ${exports.FEDERATION_RESERVED_SUBGRAPH_NAME} for a subgraph: this name is reserved` });
570
869
  }
571
870
  }
572
- toString() {
573
- return `${this.name} (${this.url})`;
871
+ metadata() {
872
+ const metadata = federationMetadata(this.schema);
873
+ (0, utils_1.assert)(metadata, 'The subgraph schema should have built with the federation built-ins.');
874
+ return metadata;
875
+ }
876
+ isFed2Subgraph() {
877
+ return this.metadata().isFed2Schema();
878
+ }
879
+ addFederationOperations() {
880
+ const metadata = this.metadata();
881
+ for (const type of exports.FEDERATION_OPERATION_TYPES) {
882
+ type.checkOrAdd(this.schema);
883
+ }
884
+ const queryRoot = this.schema.schemaDefinition.root("query");
885
+ const queryType = queryRoot ? queryRoot.type : this.schema.addType(new definitions_1.ObjectType("Query"));
886
+ const entityField = queryType.field(exports.entitiesFieldName);
887
+ const entityType = metadata.entityType();
888
+ if (entityType) {
889
+ const entityFieldType = new definitions_1.NonNullType(new definitions_1.ListType(entityType));
890
+ if (!entityField) {
891
+ queryType.addField(exports.entitiesFieldName, entityFieldType)
892
+ .addArgument('representations', new definitions_1.NonNullType(new definitions_1.ListType(new definitions_1.NonNullType(metadata.anyType()))));
893
+ }
894
+ else if (!entityField.type) {
895
+ entityField.type = entityType;
896
+ }
897
+ }
898
+ else if (entityField) {
899
+ entityField.remove();
900
+ }
901
+ if (!queryType.field(exports.serviceFieldName)) {
902
+ queryType.addField(exports.serviceFieldName, metadata.serviceType());
903
+ }
904
+ }
905
+ validate() {
906
+ try {
907
+ this.addFederationOperations();
908
+ this.schema.validate();
909
+ return this;
910
+ }
911
+ catch (e) {
912
+ if (e instanceof graphql_1.GraphQLError) {
913
+ throw addSubgraphToError(e, this.name, error_1.ERRORS.INVALID_GRAPHQL);
914
+ }
915
+ else {
916
+ throw e;
917
+ }
918
+ }
919
+ }
920
+ isPrintedDirective(d) {
921
+ var _a;
922
+ if (this.metadata().allFederationDirectives().includes(d)) {
923
+ return false;
924
+ }
925
+ const core = this.schema.coreFeatures;
926
+ return !core || ((_a = core.sourceFeature(d)) === null || _a === void 0 ? void 0 : _a.url.identity) !== coreSpec_1.linkIdentity;
927
+ }
928
+ isPrintedType(t) {
929
+ var _a;
930
+ if (this.metadata().allFederationTypes().includes(t)) {
931
+ return false;
932
+ }
933
+ const core = this.schema.coreFeatures;
934
+ return !core || ((_a = core.sourceFeature(t)) === null || _a === void 0 ? void 0 : _a.url.identity) !== coreSpec_1.linkIdentity;
935
+ }
936
+ toString(basePrintOptions = print_1.defaultPrintOptions) {
937
+ return (0, print_1.printSchema)(this.schema, {
938
+ ...basePrintOptions,
939
+ directiveDefinitionFilter: (d) => this.isPrintedDirective(d),
940
+ typeFilter: (t) => this.isPrintedType(t),
941
+ fieldFilter: (f) => !isFederationField(f),
942
+ });
574
943
  }
575
944
  }
576
945
  exports.Subgraph = Subgraph;
@@ -596,48 +965,61 @@ function addSubgraphToError(e, subgraphName, errorCode) {
596
965
  source: cause.source,
597
966
  positions: cause.positions,
598
967
  path: cause.path,
599
- originalError: cause.originalError,
968
+ originalError: cause,
600
969
  extensions: cause.extensions,
601
970
  });
602
971
  }
603
972
  else {
604
- return new graphql_1.GraphQLError(message, nodes, cause.source, cause.positions, cause.path, cause.originalError, cause.extensions);
973
+ return new graphql_1.GraphQLError(message, nodes, cause.source, cause.positions, cause.path, cause, cause.extensions);
605
974
  }
606
975
  });
607
- return (0, definitions_1.ErrGraphQLValidationFailed)(updatedCauses);
976
+ return updatedCauses.length === 1 ? updatedCauses[0] : (0, definitions_1.ErrGraphQLValidationFailed)(updatedCauses);
608
977
  }
609
978
  exports.addSubgraphToError = addSubgraphToError;
610
979
  class ExternalTester {
611
980
  constructor(schema) {
612
981
  this.schema = schema;
613
982
  this.fakeExternalFields = new Set();
983
+ this.providedFields = new Set();
984
+ this.externalDirective = this.metadata().externalDirective();
614
985
  this.collectFakeExternals();
986
+ this.collectProvidedFields();
987
+ }
988
+ metadata() {
989
+ const metadata = federationMetadata(this.schema);
990
+ (0, utils_1.assert)(metadata, 'Schema should be a subgraphs schema');
991
+ return metadata;
615
992
  }
616
993
  collectFakeExternals() {
617
- const keyDirective = exports.federationBuiltIns.keyDirective(this.schema);
618
- if (!keyDirective) {
619
- return;
620
- }
621
- for (const key of keyDirective.applications()) {
622
- const parent = key.parent;
623
- if (!(key.ofExtension() || parent.hasAppliedDirective(exports.extendsDirectiveName))) {
994
+ const metadata = this.metadata();
995
+ const extendsDirective = metadata.extendsDirective();
996
+ for (const key of metadata.keyDirective().applications()) {
997
+ const parentType = key.parent;
998
+ if (!(key.ofExtension() || parentType.hasAppliedDirective(extendsDirective))) {
624
999
  continue;
625
1000
  }
626
- try {
627
- parseFieldSetArgument(parent, key, (parentType, fieldName) => {
628
- const field = parentType.field(fieldName);
629
- if (field && field.hasAppliedDirective(exports.externalDirectiveName)) {
630
- this.fakeExternalFields.add(field.coordinate);
631
- }
632
- return field;
633
- });
634
- }
635
- catch (e) {
636
- }
1001
+ collectTargetFields({
1002
+ parentType,
1003
+ directive: key,
1004
+ includeInterfaceFieldsImplementations: false,
1005
+ validate: false,
1006
+ }).filter((field) => field.hasAppliedDirective(this.externalDirective))
1007
+ .forEach((field) => this.fakeExternalFields.add(field.coordinate));
1008
+ }
1009
+ }
1010
+ collectProvidedFields() {
1011
+ for (const provides of this.metadata().providesDirective().applications()) {
1012
+ const parent = provides.parent;
1013
+ collectTargetFields({
1014
+ parentType: (0, definitions_1.baseType)(parent.type),
1015
+ directive: provides,
1016
+ includeInterfaceFieldsImplementations: true,
1017
+ validate: false,
1018
+ }).forEach((f) => this.providedFields.add(f.coordinate));
637
1019
  }
638
1020
  }
639
1021
  isExternal(field) {
640
- return field.hasAppliedDirective(exports.externalDirectiveName) && !this.isFakeExternal(field);
1022
+ return field.hasAppliedDirective(this.externalDirective) && !this.isFakeExternal(field);
641
1023
  }
642
1024
  isFakeExternal(field) {
643
1025
  return this.fakeExternalFields.has(field.coordinate);
@@ -655,6 +1037,102 @@ class ExternalTester {
655
1037
  }
656
1038
  return false;
657
1039
  }
1040
+ isPartiallyExternal(field) {
1041
+ return this.isExternal(field) && this.providedFields.has(field.coordinate);
1042
+ }
1043
+ isFullyExternal(field) {
1044
+ return this.isExternal(field) && !this.providedFields.has(field.coordinate);
1045
+ }
1046
+ }
1047
+ function removeInactiveProvidesAndRequires(schema, onModified = () => { }) {
1048
+ const metadata = federationMetadata(schema);
1049
+ if (!metadata) {
1050
+ return;
1051
+ }
1052
+ const providesDirective = metadata.providesDirective();
1053
+ const requiresDirective = metadata.requiresDirective();
1054
+ for (const type of schema.types()) {
1055
+ if (!(0, definitions_1.isObjectType)(type) && !(0, definitions_1.isInterfaceType)(type)) {
1056
+ continue;
1057
+ }
1058
+ for (const field of type.fields()) {
1059
+ const fieldBaseType = (0, definitions_1.baseType)(field.type);
1060
+ removeInactiveApplications(providesDirective, field, fieldBaseType, onModified);
1061
+ removeInactiveApplications(requiresDirective, field, type, onModified);
1062
+ }
1063
+ }
1064
+ }
1065
+ exports.removeInactiveProvidesAndRequires = removeInactiveProvidesAndRequires;
1066
+ function removeInactiveApplications(directiveDefinition, field, parentType, onModified) {
1067
+ for (const application of field.appliedDirectivesOf(directiveDefinition)) {
1068
+ let selection;
1069
+ try {
1070
+ selection = parseFieldSetArgument({ parentType, directive: application });
1071
+ }
1072
+ catch (e) {
1073
+ continue;
1074
+ }
1075
+ if (selectsNonExternalLeafField(selection)) {
1076
+ application.remove();
1077
+ const updated = withoutNonExternalLeafFields(selection);
1078
+ if (!updated.isEmpty()) {
1079
+ const updatedDirective = field.applyDirective(directiveDefinition, { fields: updated.toString(true, false) });
1080
+ onModified(field, application, updatedDirective);
1081
+ }
1082
+ else {
1083
+ onModified(field, application);
1084
+ }
1085
+ }
1086
+ }
1087
+ }
1088
+ function isExternalOrHasExternalImplementations(field) {
1089
+ const metadata = federationMetadata(field.schema());
1090
+ if (!metadata) {
1091
+ return false;
1092
+ }
1093
+ if (field.hasAppliedDirective(metadata.externalDirective())) {
1094
+ return true;
1095
+ }
1096
+ const parentType = field.parent;
1097
+ if ((0, definitions_1.isInterfaceType)(parentType)) {
1098
+ for (const implem of parentType.possibleRuntimeTypes()) {
1099
+ const fieldInImplem = implem.field(field.name);
1100
+ if (fieldInImplem && fieldInImplem.hasAppliedDirective(metadata.externalDirective())) {
1101
+ return true;
1102
+ }
1103
+ }
1104
+ }
1105
+ return false;
1106
+ }
1107
+ function selectsNonExternalLeafField(selection) {
1108
+ return selection.selections().some(s => {
1109
+ if (s.kind === 'FieldSelection') {
1110
+ if (isExternalOrHasExternalImplementations(s.field.definition)) {
1111
+ return false;
1112
+ }
1113
+ return !s.selectionSet || selectsNonExternalLeafField(s.selectionSet);
1114
+ }
1115
+ else {
1116
+ return selectsNonExternalLeafField(s.selectionSet);
1117
+ }
1118
+ });
1119
+ }
1120
+ function withoutNonExternalLeafFields(selectionSet) {
1121
+ const newSelectionSet = new operations_1.SelectionSet(selectionSet.parentType);
1122
+ for (const selection of selectionSet.selections()) {
1123
+ if (selection.kind === 'FieldSelection') {
1124
+ if (isExternalOrHasExternalImplementations(selection.field.definition)) {
1125
+ newSelectionSet.add(selection);
1126
+ continue;
1127
+ }
1128
+ }
1129
+ if (selection.selectionSet) {
1130
+ const updated = withoutNonExternalLeafFields(selection.selectionSet);
1131
+ if (!updated.isEmpty()) {
1132
+ newSelectionSet.add((0, operations_1.selectionOfElement)(selection.element(), updated));
1133
+ }
1134
+ }
1135
+ }
1136
+ return newSelectionSet;
658
1137
  }
659
- exports.ExternalTester = ExternalTester;
660
1138
  //# sourceMappingURL=federation.js.map