@apollo/federation-internals 2.0.0-alpha.4 → 2.0.0-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +11 -1
  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 +10 -1
  19. package/dist/error.d.ts.map +1 -1
  20. package/dist/error.js +22 -2
  21. package/dist/error.js.map +1 -1
  22. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  23. package/dist/extractSubgraphsFromSupergraph.js +29 -94
  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 +745 -233
  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 +8 -0
  64. package/dist/utils.d.ts.map +1 -1
  65. package/dist/utils.js +49 -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/jest.config.js +5 -1
  78. package/package.json +4 -7
  79. package/src/__tests__/definitions.test.ts +19 -17
  80. package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +103 -0
  81. package/src/__tests__/federation.test.ts +31 -0
  82. package/src/__tests__/operations.test.ts +2 -3
  83. package/src/__tests__/schemaUpgrader.test.ts +168 -0
  84. package/src/__tests__/subgraphValidation.test.ts +33 -19
  85. package/src/__tests__/values.test.ts +2 -4
  86. package/src/buildSchema.ts +55 -36
  87. package/src/coreSpec.ts +112 -31
  88. package/src/definitions.ts +247 -260
  89. package/src/directiveAndTypeSpecification.ts +276 -0
  90. package/src/error.ts +61 -5
  91. package/src/extractSubgraphsFromSupergraph.ts +35 -119
  92. package/src/federation.ts +960 -293
  93. package/src/federationSpec.ts +113 -0
  94. package/src/index.ts +2 -0
  95. package/src/joinSpec.ts +2 -1
  96. package/src/operations.ts +22 -7
  97. package/src/print.ts +51 -38
  98. package/src/schemaUpgrader.ts +657 -0
  99. package/src/sharing.ts +68 -0
  100. package/src/supergraphs.ts +3 -3
  101. package/src/tagSpec.ts +1 -3
  102. package/src/utils.ts +85 -0
  103. package/src/validate.ts +13 -7
  104. package/src/validation/KnownTypeNamesInFederationRule.ts +1 -7
  105. package/src/values.ts +7 -3
  106. package/tsconfig.test.tsbuildinfo +1 -1
  107. package/tsconfig.tsbuildinfo +1 -1
@@ -1,49 +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
- const _1 = require(".");
15
- exports.entityTypeName = '_Entity';
16
- exports.serviceTypeName = '_Service';
17
- exports.anyTypeName = '_Any';
18
- exports.fieldSetTypeName = '_FieldSet';
19
- exports.keyDirectiveName = 'key';
20
- exports.extendsDirectiveName = 'extends';
21
- exports.externalDirectiveName = 'external';
22
- exports.requiresDirectiveName = 'requires';
23
- exports.providesDirectiveName = 'provides';
24
- exports.tagDirectiveName = 'tag';
25
- exports.serviceFieldName = '_service';
26
- 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();
27
19
  const tagSpec = tagSpec_1.TAG_VERSIONS.latest();
20
+ const federationSpec = federationSpec_1.FEDERATION_VERSIONS.latest();
28
21
  exports.FEDERATION_RESERVED_SUBGRAPH_NAME = '_';
29
- const FEDERATION_TYPES = [
30
- exports.entityTypeName,
31
- exports.serviceTypeName,
32
- exports.anyTypeName,
33
- exports.fieldSetTypeName
34
- ];
35
- const FEDERATION_DIRECTIVES = [
36
- exports.keyDirectiveName,
37
- exports.extendsDirectiveName,
38
- exports.externalDirectiveName,
39
- exports.requiresDirectiveName,
40
- exports.providesDirectiveName,
41
- exports.tagDirectiveName
42
- ];
43
- const FEDERATION_ROOT_FIELDS = [
44
- exports.serviceFieldName,
45
- exports.entitiesFieldName
46
- ];
47
22
  const FEDERATION_OMITTED_VALIDATION_RULES = [
48
23
  graphql_1.PossibleTypeExtensionsRule,
49
24
  graphql_1.KnownTypeNamesRule
@@ -52,33 +27,30 @@ const FEDERATION_SPECIFIC_VALIDATION_RULES = [
52
27
  KnownTypeNamesInFederationRule_1.KnownTypeNamesInFederationRule
53
28
  ];
54
29
  const FEDERATION_VALIDATION_RULES = specifiedRules_1.specifiedSDLRules.filter(rule => !FEDERATION_OMITTED_VALIDATION_RULES.includes(rule)).concat(FEDERATION_SPECIFIC_VALIDATION_RULES);
55
- function validateFieldSetSelections(directiveName, selectionSet, hasExternalInParents, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields) {
30
+ function validateFieldSetSelections(directiveName, selectionSet, hasExternalInParents, federationMetadata, allowOnNonExternalLeafFields) {
56
31
  for (const selection of selectionSet.selections()) {
57
32
  if (selection.kind === 'FieldSelection') {
58
33
  const field = selection.element().definition;
59
- const isExternal = externalTester.isExternal(field);
60
- if (isExternal) {
61
- externalFieldCoordinatesCollector.push(field.coordinate);
62
- }
34
+ const isExternal = federationMetadata.isFieldExternal(field);
63
35
  if (field.hasArguments()) {
64
- throw _1.ERROR_CATEGORIES.FIELDS_HAS_ARGS.get(directiveName).err({
36
+ throw error_1.ERROR_CATEGORIES.FIELDS_HAS_ARGS.get(directiveName).err({
65
37
  message: `field ${field.coordinate} cannot be included because it has arguments (fields with argument are not allowed in @${directiveName})`,
66
38
  nodes: field.sourceAST
67
39
  });
68
40
  }
69
41
  const mustBeExternal = !selection.selectionSet && !allowOnNonExternalLeafFields && !hasExternalInParents;
70
42
  if (!isExternal && mustBeExternal) {
71
- const errorCode = _1.ERROR_CATEGORIES.DIRECTIVE_FIELDS_MISSING_EXTERNAL.get(directiveName);
72
- if (externalTester.isFakeExternal(field)) {
43
+ const errorCode = error_1.ERROR_CATEGORIES.DIRECTIVE_FIELDS_MISSING_EXTERNAL.get(directiveName);
44
+ if (federationMetadata.isFieldFakeExternal(field)) {
73
45
  throw errorCode.err({
74
46
  message: `field "${field.coordinate}" should not be part of a @${directiveName} since it is already "effectively" provided by this subgraph `
75
- + `(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)`,
76
48
  nodes: field.sourceAST
77
49
  });
78
50
  }
79
51
  else {
80
52
  throw errorCode.err({
81
- 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})`,
82
54
  nodes: field.sourceAST
83
55
  });
84
56
  }
@@ -89,24 +61,24 @@ function validateFieldSetSelections(directiveName, selectionSet, hasExternalInPa
89
61
  if (!newHasExternalInParents && (0, definitions_1.isInterfaceType)(parentType)) {
90
62
  for (const implem of parentType.possibleRuntimeTypes()) {
91
63
  const fieldInImplem = implem.field(field.name);
92
- if (fieldInImplem && externalTester.isExternal(fieldInImplem)) {
64
+ if (fieldInImplem && federationMetadata.isFieldExternal(fieldInImplem)) {
93
65
  newHasExternalInParents = true;
94
66
  break;
95
67
  }
96
68
  }
97
69
  }
98
- validateFieldSetSelections(directiveName, selection.selectionSet, newHasExternalInParents, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields);
70
+ validateFieldSetSelections(directiveName, selection.selectionSet, newHasExternalInParents, federationMetadata, allowOnNonExternalLeafFields);
99
71
  }
100
72
  }
101
73
  else {
102
- validateFieldSetSelections(directiveName, selection.selectionSet, hasExternalInParents, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields);
74
+ validateFieldSetSelections(directiveName, selection.selectionSet, hasExternalInParents, federationMetadata, allowOnNonExternalLeafFields);
103
75
  }
104
76
  }
105
77
  }
106
- function validateFieldSet(type, directive, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields, onFields) {
78
+ function validateFieldSet(type, directive, federationMetadata, allowOnNonExternalLeafFields, onFields) {
107
79
  var _a;
108
80
  try {
109
- const fieldAcessor = onFields
81
+ const fieldAccessor = onFields
110
82
  ? (type, fieldName) => {
111
83
  const field = type.field(fieldName);
112
84
  if (field) {
@@ -115,9 +87,9 @@ function validateFieldSet(type, directive, externalTester, externalFieldCoordina
115
87
  return field;
116
88
  }
117
89
  : undefined;
118
- const selectionSet = parseFieldSetArgument(type, directive, fieldAcessor);
90
+ const selectionSet = parseFieldSetArgument({ parentType: type, directive, fieldAccessor });
119
91
  try {
120
- validateFieldSetSelections(directive.name, selectionSet, false, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields);
92
+ validateFieldSetSelections(directive.name, selectionSet, false, federationMetadata, allowOnNonExternalLeafFields);
121
93
  return undefined;
122
94
  }
123
95
  catch (e) {
@@ -128,7 +100,7 @@ function validateFieldSet(type, directive, externalTester, externalFieldCoordina
128
100
  if (e.nodes) {
129
101
  nodes.push(...e.nodes);
130
102
  }
131
- const codeDef = (_a = (0, error_1.errorCodeDef)(e)) !== null && _a !== void 0 ? _a : _1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS.get(directive.name);
103
+ const codeDef = (_a = (0, error_1.errorCodeDef)(e)) !== null && _a !== void 0 ? _a : error_1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS.get(directive.name);
132
104
  return codeDef.err({
133
105
  message: `${fieldSetErrorDescriptor(directive)}: ${e.message.trim()}`,
134
106
  nodes,
@@ -156,13 +128,13 @@ function fieldSetTargetDescription(directive) {
156
128
  const targetKind = directive.parent instanceof definitions_1.FieldDefinition ? "field" : "type";
157
129
  return `${targetKind} "${(_a = directive.parent) === null || _a === void 0 ? void 0 : _a.coordinate}"`;
158
130
  }
159
- function validateAllFieldSet(definition, targetTypeExtractor, errorCollector, externalTester, externalFieldCoordinatesCollector, isOnParentType, allowOnNonExternalLeafFields, onFields) {
131
+ function validateAllFieldSet(definition, targetTypeExtractor, errorCollector, federationMetadata, isOnParentType, allowOnNonExternalLeafFields, onFields) {
160
132
  for (const application of definition.applications()) {
161
133
  const elt = application.parent;
162
134
  const type = targetTypeExtractor(elt);
163
135
  const parentType = isOnParentType ? type : elt.parent;
164
136
  if ((0, definitions_1.isInterfaceType)(parentType)) {
165
- const code = _1.ERROR_CATEGORIES.DIRECTIVE_UNSUPPORTED_ON_INTERFACE.get(definition.name);
137
+ const code = error_1.ERROR_CATEGORIES.DIRECTIVE_UNSUPPORTED_ON_INTERFACE.get(definition.name);
166
138
  errorCollector.push(code.err({
167
139
  message: isOnParentType
168
140
  ? `Cannot use ${definition.coordinate} on interface "${parentType.coordinate}": ${definition.coordinate} is not yet supported on interfaces`
@@ -170,19 +142,57 @@ function validateAllFieldSet(definition, targetTypeExtractor, errorCollector, ex
170
142
  nodes: (0, definitions_1.sourceASTs)(application).concat(isOnParentType ? [] : (0, definitions_1.sourceASTs)(type)),
171
143
  }));
172
144
  }
173
- const error = validateFieldSet(type, application, externalTester, externalFieldCoordinatesCollector, allowOnNonExternalLeafFields, onFields);
145
+ const error = validateFieldSet(type, application, federationMetadata, allowOnNonExternalLeafFields, onFields);
174
146
  if (error) {
175
147
  errorCollector.push(error);
176
148
  }
177
149
  }
178
150
  }
179
- function validateAllExternalFieldsUsed(schema, externalTester, allExternalFieldsUsedInFederationDirectivesCoordinates, errorCollector) {
180
- 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()) {
181
191
  if (!(0, definitions_1.isObjectType)(type) && !(0, definitions_1.isInterfaceType)(type)) {
182
192
  continue;
183
193
  }
184
194
  for (const field of type.fields()) {
185
- if (!externalTester.isExternal(field) || allExternalFieldsUsedInFederationDirectivesCoordinates.includes(field.coordinate)) {
195
+ if (!metadata.isFieldExternal(field) || allUsedExternals.has(field.coordinate)) {
186
196
  continue;
187
197
  }
188
198
  if (!isFieldSatisfyingInterface(field)) {
@@ -195,87 +205,250 @@ function validateAllExternalFieldsUsed(schema, externalTester, allExternalFields
195
205
  }
196
206
  }
197
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
+ }
198
220
  function isFieldSatisfyingInterface(field) {
199
221
  return field.parent.interfaces().some(itf => itf.field(field.name));
200
222
  }
201
- class FederationBuiltIns extends definitions_1.BuiltIns {
202
- addBuiltInTypes(schema) {
203
- super.addBuiltInTypes(schema);
204
- this.addBuiltInUnion(schema, exports.entityTypeName);
205
- this.addBuiltInObject(schema, exports.serviceTypeName).addField('sdl', schema.stringType());
206
- this.addBuiltInScalar(schema, exports.anyTypeName);
207
- this.addBuiltInScalar(schema, exports.fieldSetTypeName);
208
- }
209
- addBuiltInDirectives(schema) {
210
- super.addBuiltInDirectives(schema);
211
- const fieldSetType = new definitions_1.NonNullType(schema.type(exports.fieldSetTypeName));
212
- const keyDirective = this.addBuiltInDirective(schema, exports.keyDirectiveName)
213
- .addLocations(graphql_1.DirectiveLocation.OBJECT, graphql_1.DirectiveLocation.INTERFACE);
214
- keyDirective.repeatable = true;
215
- keyDirective.addArgument('fields', fieldSetType);
216
- this.addBuiltInDirective(schema, exports.extendsDirectiveName)
217
- .addLocations(graphql_1.DirectiveLocation.OBJECT, graphql_1.DirectiveLocation.INTERFACE);
218
- this.addBuiltInDirective(schema, exports.externalDirectiveName)
219
- .addLocations(graphql_1.DirectiveLocation.OBJECT, graphql_1.DirectiveLocation.FIELD_DEFINITION);
220
- for (const name of [exports.requiresDirectiveName, exports.providesDirectiveName]) {
221
- this.addBuiltInDirective(schema, name)
222
- .addLocations(graphql_1.DirectiveLocation.FIELD_DEFINITION)
223
- .addArgument('fields', fieldSetType);
224
- }
225
- const directive = this.addBuiltInDirective(schema, 'tag').addLocations(...tagSpec_1.tagLocations);
226
- directive.addArgument("name", new definitions_1.NonNullType(schema.stringType()));
227
- }
228
- prepareValidation(schema) {
229
- super.prepareValidation(schema);
230
- let entityType = schema.type(exports.entityTypeName);
231
- if (!entityType.isBuiltIn) {
232
- if (entityType.membersCount() === 0) {
233
- entityType.remove();
234
- }
235
- entityType = schema.builtInTypes('UnionType', true).find(u => u.name === exports.entityTypeName);
236
- }
237
- entityType.clearTypes();
238
- for (const objectType of schema.types("ObjectType")) {
239
- if (isEntityType(objectType)) {
240
- entityType.addType(objectType);
241
- }
242
- }
243
- const hasEntities = entityType.membersCount() > 0;
244
- if (!hasEntities) {
245
- entityType.remove();
246
- }
247
- const queryRoot = schema.schemaDefinition.root("query");
248
- const queryType = queryRoot ? queryRoot.type : schema.addType(new definitions_1.ObjectType("Query"));
249
- const entityField = queryType.field(exports.entitiesFieldName);
250
- if (hasEntities) {
251
- const anyType = schema.type(exports.anyTypeName);
252
- (0, utils_1.assert)(anyType, `The schema should have the _Any type`);
253
- const entityFieldType = new definitions_1.NonNullType(new definitions_1.ListType(entityType));
254
- if (!entityField) {
255
- this.addBuiltInField(queryType, exports.entitiesFieldName, entityFieldType)
256
- .addArgument('representations', new definitions_1.NonNullType(new definitions_1.ListType(new definitions_1.NonNullType(anyType))));
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');
227
+ const runtimeTypes = itf.possibleRuntimeTypes();
228
+ for (const field of itf.fields()) {
229
+ const withExternalOrRequires = [];
230
+ const typeToImplems = new utils_1.MultiMap();
231
+ const nodes = [];
232
+ for (const type of runtimeTypes) {
233
+ const implemField = type.field(field.name);
234
+ if (!implemField)
235
+ continue;
236
+ if (implemField.sourceAST) {
237
+ nodes.push(implemField.sourceAST);
257
238
  }
258
- else if (!entityField.type) {
259
- entityField.type = entityType;
239
+ if (metadata.isFieldExternal(implemField) || implemField.hasAppliedDirective(requiresDirective)) {
240
+ withExternalOrRequires.push(implemField);
260
241
  }
242
+ const returnType = implemField.type;
243
+ typeToImplems.add(returnType.toString(), implemField);
261
244
  }
262
- else if (entityField) {
263
- entityField.remove();
245
+ if (withExternalOrRequires.length > 0 && typeToImplems.size > 1) {
246
+ const typeToImplemsArray = [...typeToImplems.entries()];
247
+ errorCollector.push(error_1.ERRORS.INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH.err({
248
+ message: `Some of the runtime implementations of interface field "${field.coordinate}" are marked @external or have a @require (${withExternalOrRequires.map(printFieldCoordinate)}) so all the implementations should use the same type (a current limitation of federation; see https://github.com/apollographql/federation/issues/1257), but ${formatFieldsToReturnType(typeToImplemsArray[0])} while ${(0, utils_1.joinStrings)(typeToImplemsArray.slice(1).map(formatFieldsToReturnType), ' and ')}.`,
249
+ nodes
250
+ }));
264
251
  }
265
- if (!queryType.field(exports.serviceFieldName)) {
266
- this.addBuiltInField(queryType, exports.serviceFieldName, schema.type(exports.serviceTypeName));
252
+ }
253
+ }
254
+ const printFieldCoordinate = (f) => `"${f.coordinate}"`;
255
+ function formatFieldsToReturnType([type, implems]) {
256
+ return `${(0, utils_1.joinStrings)(implems.map(printFieldCoordinate))} ${implems.length == 1 ? 'has' : 'have'} type "${type}"`;
257
+ }
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);
267
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);
410
+ }
411
+ }
412
+ }
413
+ onMissingDirectiveDefinition(schema, name) {
414
+ if (name === coreSpec_1.linkDirectiveDefaultName) {
415
+ linkSpec.addToSchema(schema);
416
+ return schema.directive(name);
417
+ }
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);
431
+ }
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);
268
441
  }
269
442
  onValidation(schema) {
270
443
  var _a;
271
- const errors = super.onValidation(schema, [exports.tagDirectiveName]);
444
+ const errors = super.onValidation(schema);
272
445
  for (const k of definitions_1.allSchemaRootKinds) {
273
446
  const type = (_a = schema.schemaDefinition.root(k)) === null || _a === void 0 ? void 0 : _a.type;
274
447
  const defaultName = (0, definitions_1.defaultRootName)(k);
275
448
  if (type && type.name !== defaultName) {
276
449
  const existing = schema.type(defaultName);
277
450
  if (existing) {
278
- errors.push(_1.ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err({
451
+ errors.push(error_1.ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err({
279
452
  message: `The schema has a type named "${defaultName}" but it is not set as the ${k} root type ("${type.name}" is instead): `
280
453
  + 'this is not supported by federation. '
281
454
  + 'If a root type does not use its default name, there should be no other type with that default name.',
@@ -285,21 +458,25 @@ class FederationBuiltIns extends definitions_1.BuiltIns {
285
458
  type.rename(defaultName);
286
459
  }
287
460
  }
288
- const externalTester = new ExternalTester(schema);
289
- const externalFieldsInFedDirectivesCoordinates = [];
290
- const keyDirective = this.keyDirective(schema);
291
- validateAllFieldSet(keyDirective, type => type, errors, externalTester, externalFieldsInFedDirectivesCoordinates, true, true, field => {
292
- if ((0, definitions_1.isListType)(field.type) || (0, definitions_1.isUnionType)(field.type) || (0, definitions_1.isInterfaceType)(field.type)) {
293
- 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;
294
471
  kind = kind.slice(0, kind.length - 'Type'.length);
295
472
  throw error_1.ERRORS.KEY_FIELDS_SELECT_INVALID_TYPE.err({
296
473
  message: `field "${field.coordinate}" is a ${kind} type which is not allowed in @key`
297
474
  });
298
475
  }
299
476
  });
300
- validateAllFieldSet(this.requiresDirective(schema), field => field.parent, errors, externalTester, externalFieldsInFedDirectivesCoordinates, false, false);
301
- validateAllFieldSet(this.providesDirective(schema), field => {
302
- 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)) {
303
480
  throw new graphql_1.GraphQLError(`Cannot have both @provides and @external on field "${field.coordinate}"`, field.sourceAST);
304
481
  }
305
482
  const type = (0, definitions_1.baseType)(field.type);
@@ -310,90 +487,129 @@ class FederationBuiltIns extends definitions_1.BuiltIns {
310
487
  });
311
488
  }
312
489
  return type;
313
- }, errors, externalTester, externalFieldsInFedDirectivesCoordinates, false, false);
314
- validateAllExternalFieldsUsed(schema, externalTester, externalFieldsInFedDirectivesCoordinates, errors);
315
- const tagDirective = this.tagDirective(schema);
316
- if (!tagDirective.isBuiltIn) {
490
+ }, errors, metadata, false, false);
491
+ validateNoExternalOnInterfaceFields(metadata, errors);
492
+ validateAllExternalFieldsUsed(metadata, errors);
493
+ const tagDirective = metadata.tagDirective();
494
+ if (tagDirective) {
317
495
  const error = tagSpec.checkCompatibleDirective(tagDirective);
318
496
  if (error) {
319
497
  errors.push(error);
320
498
  }
321
499
  }
500
+ for (const itf of schema.types('InterfaceType')) {
501
+ validateInterfaceRuntimeImplementationFieldsTypes(itf, metadata, errors);
502
+ }
322
503
  return errors;
323
504
  }
324
505
  validationRules() {
325
506
  return FEDERATION_VALIDATION_RULES;
326
507
  }
327
- keyDirective(schema) {
328
- return this.getTypedDirective(schema, exports.keyDirectiveName);
329
- }
330
- extendsDirective(schema) {
331
- return this.getTypedDirective(schema, exports.extendsDirectiveName);
332
- }
333
- externalDirective(schema) {
334
- return this.getTypedDirective(schema, exports.externalDirectiveName);
335
- }
336
- requiresDirective(schema) {
337
- 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;
338
514
  }
339
- providesDirective(schema) {
340
- 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
+ }
341
522
  }
342
- tagDirective(schema) {
343
- 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}`);
344
530
  }
345
- maybeUpdateSubgraphDocument(schema, document) {
346
- document = super.maybeUpdateSubgraphDocument(schema, document);
347
- const definitions = document.definitions.concat();
348
- for (const directiveName of FEDERATION_DIRECTIVES) {
349
- const directive = schema.directive(directiveName);
350
- (0, utils_1.assert)(directive, 'This method should only have been called on a schema with federation built-ins');
351
- if (directive.isBuiltIn) {
352
- definitions.push((0, graphql_1.parse)((0, print_1.printDirectiveDefinition)(directive, print_1.defaultPrintOptions)).definitions[0]);
353
- }
354
- }
355
- return {
356
- kind: graphql_1.Kind.DOCUMENT,
357
- loc: document.loc,
358
- definitions
359
- };
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');
360
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);
361
544
  }
362
- exports.FederationBuiltIns = FederationBuiltIns;
363
- exports.federationBuiltIns = new FederationBuiltIns();
364
- function isFederationSubgraphSchema(schema) {
365
- 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
+ };
366
569
  }
367
- exports.isFederationSubgraphSchema = isFederationSubgraphSchema;
368
- function isFederationType(type) {
369
- 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
+ });
370
576
  }
371
- exports.isFederationType = isFederationType;
372
- function isFederationTypeName(typeName) {
373
- return FEDERATION_TYPES.includes(typeName);
577
+ exports.printSubgraphNames = printSubgraphNames;
578
+ function federationMetadata(schema) {
579
+ return schema['_federationMetadata'];
374
580
  }
375
- exports.isFederationTypeName = isFederationTypeName;
581
+ exports.federationMetadata = federationMetadata;
582
+ function isFederationSubgraphSchema(schema) {
583
+ return !!federationMetadata(schema);
584
+ }
585
+ exports.isFederationSubgraphSchema = isFederationSubgraphSchema;
376
586
  function isFederationField(field) {
377
587
  var _a;
378
588
  if (field.parent === ((_a = field.schema().schemaDefinition.root("query")) === null || _a === void 0 ? void 0 : _a.type)) {
379
- return FEDERATION_ROOT_FIELDS.includes(field.name);
589
+ return exports.FEDERATION_OPERATION_FIELDS.includes(field.name);
380
590
  }
381
591
  return false;
382
592
  }
383
593
  exports.isFederationField = isFederationField;
384
- function isFederationDirective(directive) {
385
- return FEDERATION_DIRECTIVES.includes(directive.name);
386
- }
387
- exports.isFederationDirective = isFederationDirective;
388
594
  function isEntityType(type) {
389
- 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());
390
600
  }
391
601
  exports.isEntityType = isEntityType;
392
- function buildSubgraph(name, source) {
602
+ function buildSubgraph(name, url, source) {
603
+ const buildOptions = {
604
+ blueprint: federationBlueprint,
605
+ validate: false,
606
+ };
607
+ let subgraph;
393
608
  try {
394
- return typeof source === 'string'
395
- ? (0, buildSchema_1.buildSchema)(new graphql_1.Source(source, name), exports.federationBuiltIns)
396
- : (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);
397
613
  }
398
614
  catch (e) {
399
615
  if (e instanceof graphql_1.GraphQLError) {
@@ -403,14 +619,73 @@ function buildSubgraph(name, source) {
403
619
  throw e;
404
620
  }
405
621
  }
622
+ return subgraph.validate();
406
623
  }
407
624
  exports.buildSubgraph = buildSubgraph;
408
- 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, }) {
409
681
  var _a;
410
682
  try {
411
- const selectionSet = (0, operations_1.parseSelectionSet)(parentType, validateFieldSetValue(directive), new definitions_1.VariableDefinitions(), undefined, fieldAccessor);
412
- selectionSet.validate();
413
- return selectionSet;
683
+ return (0, operations_1.parseSelectionSet)({
684
+ parentType,
685
+ source: validateFieldSetValue(directive),
686
+ fieldAccessor,
687
+ validate,
688
+ });
414
689
  }
415
690
  catch (e) {
416
691
  if (!(e instanceof graphql_1.GraphQLError)) {
@@ -425,14 +700,14 @@ function parseFieldSetArgument(parentType, directive, fieldAccessor = (type, nam
425
700
  if (msg.endsWith('.')) {
426
701
  msg = msg.slice(0, msg.length - 1);
427
702
  }
428
- if (directive.name === exports.keyDirectiveName) {
703
+ if (directive.name === federationSpec_1.keyDirectiveSpec.name) {
429
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).';
430
705
  }
431
706
  else {
432
707
  msg = msg + ' (if the field is defined in another subgraph, you need to add it to this subgraph with @external).';
433
708
  }
434
709
  }
435
- const codeDef = (_a = (0, error_1.errorCodeDef)(e)) !== null && _a !== void 0 ? _a : _1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS.get(directive.name);
710
+ const codeDef = (_a = (0, error_1.errorCodeDef)(e)) !== null && _a !== void 0 ? _a : error_1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS.get(directive.name);
436
711
  throw codeDef.err({
437
712
  message: `${fieldSetErrorDescriptor(directive)}: ${msg}`,
438
713
  nodes,
@@ -441,12 +716,45 @@ function parseFieldSetArgument(parentType, directive, fieldAccessor = (type, nam
441
716
  }
442
717
  }
443
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;
444
752
  function validateFieldSetValue(directive) {
445
753
  var _a;
446
754
  const fields = directive.arguments().fields;
447
755
  const nodes = directive.sourceAST;
448
756
  if (typeof fields !== 'string') {
449
- throw _1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS_TYPE.get(directive.name).err({
757
+ throw error_1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS_TYPE.get(directive.name).err({
450
758
  message: `Invalid value for argument "${directive.definition.argument('fields').name}": must be a string.`,
451
759
  nodes,
452
760
  });
@@ -455,7 +763,7 @@ function validateFieldSetValue(directive) {
455
763
  for (const argNode of (_a = nodes.arguments) !== null && _a !== void 0 ? _a : []) {
456
764
  if (argNode.name.value === 'fields') {
457
765
  if (argNode.value.kind !== 'StringValue') {
458
- throw _1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS_TYPE.get(directive.name).err({
766
+ throw error_1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS_TYPE.get(directive.name).err({
459
767
  message: `Invalid value for argument "${directive.definition.argument('fields').name}": must be a string.`,
460
768
  nodes,
461
769
  });
@@ -472,7 +780,7 @@ function subgraphsFromServiceList(serviceList) {
472
780
  const subgraphs = new Subgraphs();
473
781
  for (const service of serviceList) {
474
782
  try {
475
- 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));
476
784
  }
477
785
  catch (e) {
478
786
  const causes = (0, definitions_1.errorCauses)(e);
@@ -491,18 +799,12 @@ class Subgraphs {
491
799
  constructor() {
492
800
  this.subgraphs = new utils_1.OrderedMap();
493
801
  }
494
- add(subgraphOrName, url, schema) {
495
- const toAdd = typeof subgraphOrName === 'string'
496
- ? new Subgraph(subgraphOrName, url, schema instanceof definitions_1.Schema ? schema : buildSubgraph(subgraphOrName, schema))
497
- : subgraphOrName;
498
- if (toAdd.name === exports.FEDERATION_RESERVED_SUBGRAPH_NAME) {
499
- throw error_1.ERRORS.INVALID_SUBGRAPH_NAME.err({ message: `Invalid name ${exports.FEDERATION_RESERVED_SUBGRAPH_NAME} for a subgraph: this name is reserved` });
500
- }
501
- if (this.subgraphs.has(toAdd.name)) {
502
- 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}')` : ''));
503
805
  }
504
- this.subgraphs.add(toAdd.name, toAdd);
505
- return toAdd;
806
+ this.subgraphs.add(subgraph.name, subgraph);
807
+ return subgraph;
506
808
  }
507
809
  get(name) {
508
810
  return this.subgraphs.get(name);
@@ -521,22 +823,123 @@ class Subgraphs {
521
823
  yield subgraph;
522
824
  }
523
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
+ }
524
842
  toString() {
525
843
  return '[' + this.subgraphs.keys().join(', ') + ']';
526
844
  }
527
845
  }
528
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];
529
862
  class Subgraph {
530
- constructor(name, url, schema, validateSchema = true) {
863
+ constructor(name, url, schema) {
531
864
  this.name = name;
532
865
  this.url = url;
533
866
  this.schema = schema;
534
- if (validateSchema) {
535
- 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` });
536
869
  }
537
870
  }
538
- toString() {
539
- 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
+ });
540
943
  }
541
944
  }
542
945
  exports.Subgraph = Subgraph;
@@ -562,48 +965,61 @@ function addSubgraphToError(e, subgraphName, errorCode) {
562
965
  source: cause.source,
563
966
  positions: cause.positions,
564
967
  path: cause.path,
565
- originalError: cause.originalError,
968
+ originalError: cause,
566
969
  extensions: cause.extensions,
567
970
  });
568
971
  }
569
972
  else {
570
- 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);
571
974
  }
572
975
  });
573
- return (0, definitions_1.ErrGraphQLValidationFailed)(updatedCauses);
976
+ return updatedCauses.length === 1 ? updatedCauses[0] : (0, definitions_1.ErrGraphQLValidationFailed)(updatedCauses);
574
977
  }
575
978
  exports.addSubgraphToError = addSubgraphToError;
576
979
  class ExternalTester {
577
980
  constructor(schema) {
578
981
  this.schema = schema;
579
982
  this.fakeExternalFields = new Set();
983
+ this.providedFields = new Set();
984
+ this.externalDirective = this.metadata().externalDirective();
580
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;
581
992
  }
582
993
  collectFakeExternals() {
583
- const keyDirective = exports.federationBuiltIns.keyDirective(this.schema);
584
- if (!keyDirective) {
585
- return;
586
- }
587
- for (const key of keyDirective.applications()) {
588
- const parent = key.parent;
589
- 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))) {
590
999
  continue;
591
1000
  }
592
- try {
593
- parseFieldSetArgument(parent, key, (parentType, fieldName) => {
594
- const field = parentType.field(fieldName);
595
- if (field && field.hasAppliedDirective(exports.externalDirectiveName)) {
596
- this.fakeExternalFields.add(field.coordinate);
597
- }
598
- return field;
599
- });
600
- }
601
- catch (e) {
602
- }
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));
603
1019
  }
604
1020
  }
605
1021
  isExternal(field) {
606
- return field.hasAppliedDirective(exports.externalDirectiveName) && !this.isFakeExternal(field);
1022
+ return field.hasAppliedDirective(this.externalDirective) && !this.isFakeExternal(field);
607
1023
  }
608
1024
  isFakeExternal(field) {
609
1025
  return this.fakeExternalFields.has(field.coordinate);
@@ -621,6 +1037,102 @@ class ExternalTester {
621
1037
  }
622
1038
  return false;
623
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;
624
1137
  }
625
- exports.ExternalTester = ExternalTester;
626
1138
  //# sourceMappingURL=federation.js.map