@theguild/federation-composition 0.7.0 → 0.7.1-alpha-20240108091943-78b56db

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.
@@ -138,7 +138,8 @@ function visitFields({ context, selectionSet, typeDefinition, interceptField, in
138
138
  }
139
139
  break;
140
140
  }
141
- if (interceptDirective && selection.directives?.length) {
141
+ const isTypename = selection.name.value === '__typename';
142
+ if (!isTypename && interceptDirective && selection.directives?.length) {
142
143
  for (const directive of selection.directives) {
143
144
  interceptDirective({
144
145
  directiveName: directive.name.value,
@@ -146,21 +147,23 @@ function visitFields({ context, selectionSet, typeDefinition, interceptField, in
146
147
  });
147
148
  }
148
149
  }
149
- context.markAsUsed('fields', typeDefinition.kind, typeDefinition.name.value, selectionFieldDef.name.value);
150
- if (interceptField) {
150
+ if (!isTypename) {
151
+ context.markAsUsed('fields', typeDefinition.kind, typeDefinition.name.value, selectionFieldDef.name.value);
152
+ }
153
+ if (!isTypename && interceptField) {
151
154
  interceptField({
152
155
  typeDefinition,
153
156
  fieldName: selection.name.value,
154
157
  });
155
158
  }
156
- if (selectionFieldDef.arguments?.length && interceptArguments) {
159
+ if (!isTypename && selectionFieldDef.arguments?.length && interceptArguments) {
157
160
  interceptArguments({
158
161
  typeDefinition,
159
162
  fieldName: selection.name.value,
160
163
  });
161
164
  continue;
162
165
  }
163
- if (interceptNonExternalField || interceptExternalField) {
166
+ if (!isTypename && (interceptNonExternalField || interceptExternalField)) {
164
167
  const isExternal = selectionFieldDef.directives?.some(d => context.isAvailableFederationDirective('external', d));
165
168
  const fieldName = selection.name.value;
166
169
  const fieldDef = typeDefinition.fields?.find(field => field.name.value === fieldName);
@@ -189,7 +192,8 @@ function visitFields({ context, selectionSet, typeDefinition, interceptField, in
189
192
  if (!innerTypeDef) {
190
193
  continue;
191
194
  }
192
- if (interceptInterfaceType &&
195
+ if (!isTypename &&
196
+ interceptInterfaceType &&
193
197
  (innerTypeDef.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION ||
194
198
  innerTypeDef.kind === graphql_1.Kind.INTERFACE_TYPE_EXTENSION)) {
195
199
  interceptInterfaceType({
@@ -212,9 +216,13 @@ function visitFields({ context, selectionSet, typeDefinition, interceptField, in
212
216
  context,
213
217
  selectionSet: innerSelection,
214
218
  typeDefinition: innerTypeDef,
219
+ interceptField,
215
220
  interceptArguments,
216
221
  interceptUnknownField,
217
222
  interceptInterfaceType,
223
+ interceptDirective,
224
+ interceptExternalField,
225
+ interceptFieldWithMissingSelectionSet,
218
226
  });
219
227
  }
220
228
  }
@@ -154,7 +154,9 @@ function ProvidesRules(context) {
154
154
  }
155
155
  if (info.typeDefinition.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION ||
156
156
  info.typeDefinition.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) {
157
- context.stateBuilder.objectType.field.markAsProvided(info.typeDefinition.name.value, info.fieldName);
157
+ if (info.fieldName !== '__typename') {
158
+ context.stateBuilder.objectType.field.markAsProvided(info.typeDefinition.name.value, info.fieldName);
159
+ }
158
160
  }
159
161
  },
160
162
  });
@@ -81,7 +81,9 @@ function RequiresRules(context) {
81
81
  interceptField(info) {
82
82
  if (info.typeDefinition.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION ||
83
83
  info.typeDefinition.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) {
84
- context.stateBuilder.objectType.field.markedAsRequired(info.typeDefinition.name.value, info.fieldName);
84
+ if (info.fieldName !== '__typename') {
85
+ context.stateBuilder.objectType.field.markedAsRequired(info.typeDefinition.name.value, info.fieldName);
86
+ }
85
87
  }
86
88
  },
87
89
  interceptUnknownField(info) {
@@ -62,6 +62,33 @@ function validateSubgraphCore(subgraph) {
62
62
  exports.validateSubgraphCore = validateSubgraphCore;
63
63
  function validateSubgraph(subgraph, stateBuilder, federation, __internal) {
64
64
  subgraph.typeDefs = cleanSubgraphTypeDefsFromSubgraphSpec(subgraph.typeDefs);
65
+ const linkSpecDefinitions = (0, graphql_1.parse)(`
66
+ enum Purpose {
67
+ EXECUTION
68
+ SECURITY
69
+ }
70
+
71
+ directive @link(
72
+ url: String
73
+ as: String
74
+ for: link__Purpose
75
+ import: [link__Import]
76
+ ) repeatable on SCHEMA
77
+
78
+ scalar link__Import
79
+
80
+ enum link__Purpose {
81
+ """
82
+ \`SECURITY\` features provide metadata necessary to securely resolve fields.
83
+ """
84
+ SECURITY
85
+
86
+ """
87
+ \`EXECUTION\` features provide metadata necessary for operation execution.
88
+ """
89
+ EXECUTION
90
+ }
91
+ `).definitions;
65
92
  const rulesToSkip = __internal?.disableValidationRules ?? [];
66
93
  const typeNodeInfo = new type_node_info_js_1.TypeNodeInfo();
67
94
  const validationContext = (0, validation_context_js_1.createSubgraphValidationContext)(subgraph, federation, typeNodeInfo, stateBuilder);
@@ -111,6 +138,12 @@ function validateSubgraph(subgraph, stateBuilder, federation, __internal) {
111
138
  return rule(validationContext);
112
139
  })))));
113
140
  const federationDefinitionReplacements = validationContext.collectFederationDefinitionReplacements();
141
+ const linkSpecDefinitionsToInclude = linkSpecDefinitions.filter(def => {
142
+ if ('name' in def && typeof def.name?.value === 'string') {
143
+ return !stateBuilder.state.types.has(def.name.value);
144
+ }
145
+ return true;
146
+ });
114
147
  const fullTypeDefs = (0, graphql_1.concatAST)([
115
148
  {
116
149
  kind: graphql_1.Kind.DOCUMENT,
@@ -120,33 +153,12 @@ function validateSubgraph(subgraph, stateBuilder, federation, __internal) {
120
153
  },
121
154
  validationContext.satisfiesVersionRange('> v1.0') && !stateBuilder.state.specs.link
122
155
  ?
123
- (0, graphql_1.parse)(`
124
- enum Purpose {
125
- EXECUTION
126
- SECURITY
127
- }
128
-
129
- directive @link(
130
- url: String
131
- as: String
132
- for: link__Purpose
133
- import: [link__Import]
134
- ) repeatable on SCHEMA
135
-
136
- scalar link__Import
137
-
138
- enum link__Purpose {
139
- """
140
- \`SECURITY\` features provide metadata necessary to securely resolve fields.
141
- """
142
- SECURITY
143
-
144
- """
145
- \`EXECUTION\` features provide metadata necessary for operation execution.
146
- """
147
- EXECUTION
148
- }
149
- `)
156
+ linkSpecDefinitionsToInclude.length > 0
157
+ ? {
158
+ kind: graphql_1.Kind.DOCUMENT,
159
+ definitions: linkSpecDefinitionsToInclude,
160
+ }
161
+ : null
150
162
  : null,
151
163
  subgraph.typeDefs,
152
164
  ].filter(onlyDocumentNode));
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.stripFederation = exports.schemaCoordinate = exports.createJoinGraphEnumTypeNode = exports.createScalarTypeNode = exports.createEnumTypeNode = exports.createUnionTypeNode = exports.createInputObjectTypeNode = exports.createInterfaceTypeNode = exports.createObjectTypeNode = exports.createDirectiveNode = exports.createSchemaNode = void 0;
4
4
  const graphql_1 = require("graphql");
5
+ const printer_js_1 = require("../../graphql/printer.js");
5
6
  function createSchemaNode(schema) {
6
7
  return {
7
8
  kind: graphql_1.Kind.SCHEMA_DEFINITION,
@@ -740,7 +741,16 @@ function createNamedTypeNode(name) {
740
741
  };
741
742
  }
742
743
  function applyDirectives(common) {
743
- return [].concat(common.ast?.directives ?? [], common.join?.type?.map(createJoinTypeDirectiveNode) ?? [], common.join?.implements?.map(createJoinImplementsDirectiveNode) ?? [], common.join?.field?.map(createJoinFieldDirectiveNode) ?? [], common.join?.unionMember?.map(createJoinUnionMemberDirectiveNode) ?? [], common.join?.enumValue?.map(createJoinEnumValueDirectiveNode) ?? [], common.tags?.map(createTagDirectiveNode) ?? [], common.inaccessible ? [createInaccessibleDirectiveNode()] : [], common.authenticated ? [createAuthenticatedDirectiveNode()] : [], common.policies?.length ? [createPolicyDirectiveNode(common.policies)] : [], common.scopes?.length ? [createRequiresScopesDirectiveNode(common.scopes)] : [], common.deprecated ? [createDeprecatedDirectiveNode(common.deprecated)] : [], common.specifiedBy ? [createSpecifiedByDirectiveNode(common.specifiedBy)] : []);
744
+ const deduplicatedDirectives = (common.ast?.directives ?? [])
745
+ .map(directive => {
746
+ return {
747
+ ast: directive,
748
+ string: (0, printer_js_1.print)(directive),
749
+ };
750
+ })
751
+ .filter((directive, index, all) => all.findIndex(d => d.string === directive.string) === index)
752
+ .map(d => d.ast);
753
+ return [].concat(deduplicatedDirectives, common.join?.type?.map(createJoinTypeDirectiveNode) ?? [], common.join?.implements?.map(createJoinImplementsDirectiveNode) ?? [], common.join?.field?.map(createJoinFieldDirectiveNode) ?? [], common.join?.unionMember?.map(createJoinUnionMemberDirectiveNode) ?? [], common.join?.enumValue?.map(createJoinEnumValueDirectiveNode) ?? [], common.tags?.map(createTagDirectiveNode) ?? [], common.inaccessible ? [createInaccessibleDirectiveNode()] : [], common.authenticated ? [createAuthenticatedDirectiveNode()] : [], common.policies?.length ? [createPolicyDirectiveNode(common.policies)] : [], common.scopes?.length ? [createRequiresScopesDirectiveNode(common.scopes)] : [], common.deprecated ? [createDeprecatedDirectiveNode(common.deprecated)] : [], common.specifiedBy ? [createSpecifiedByDirectiveNode(common.specifiedBy)] : []);
744
754
  }
745
755
  function nonEmpty(value) {
746
756
  return value != null;
@@ -225,7 +225,7 @@ function objectTypeBuilder() {
225
225
  .map(([graphId, meta]) => {
226
226
  const type = hasDifferentOutputType ? meta.type : undefined;
227
227
  const override = meta.override ?? undefined;
228
- const usedOverridden = provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId);
228
+ const usedOverridden = provideUsedOverriddenValue(field.name, meta, overridesMap, fieldNamesOfImplementedInterfaces, graphId);
229
229
  const external = shouldSetExternalOnJoinField(meta, graphId, field);
230
230
  const provides = meta.provides ?? undefined;
231
231
  const requires = meta.requires ?? undefined;
@@ -255,7 +255,8 @@ function objectTypeBuilder() {
255
255
  directives: (0, common_js_1.convertToConst)(objectType.ast.directives),
256
256
  },
257
257
  description: objectType.description,
258
- fields: Array.from(objectType.fields.values()).map(field => {
258
+ fields: Array.from(objectType.fields.values())
259
+ .map(field => {
259
260
  const fieldInGraphs = Array.from(field.byGraph.entries());
260
261
  const hasDifferentOutputType = fieldInGraphs.some(([_, meta]) => meta.type !== field.type);
261
262
  const isDefinedEverywhere = field.byGraph.size === (isQuery ? graphs.size : objectType.byGraph.size);
@@ -291,13 +292,30 @@ function objectTypeBuilder() {
291
292
  differencesBetweenGraphs.type = true;
292
293
  }
293
294
  }
295
+ if (!isQuery && field.byGraph.size === 1) {
296
+ const graphId = field.byGraph.keys().next().value;
297
+ const fieldInGraph = field.byGraph.get(graphId);
298
+ if (fieldInGraph.external &&
299
+ !fieldInGraph.usedAsKey &&
300
+ !fieldInGraph.required &&
301
+ !fieldInGraph.provided &&
302
+ !provideUsedOverriddenValue(field.name, fieldInGraph, overridesMap, fieldNamesOfImplementedInterfaces, graphId) &&
303
+ graphs.get(graphId).version === 'v1.0') {
304
+ return null;
305
+ }
306
+ }
294
307
  if (isQuery) {
295
308
  if (differencesBetweenGraphs.override) {
296
- const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null);
309
+ const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null &&
310
+ (objectType.byGraph.size > 1
311
+ ?
312
+ true
313
+ :
314
+ typeof graphNameToId(meta.override) === 'string'));
297
315
  joinFields = graphsWithOverride.map(([graphId, meta]) => ({
298
316
  graph: graphId,
299
317
  override: meta.override ?? undefined,
300
- usedOverridden: provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
318
+ usedOverridden: provideUsedOverriddenValue(field.name, meta, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
301
319
  type: differencesBetweenGraphs.type ? meta.type : undefined,
302
320
  external: meta.external ?? undefined,
303
321
  provides: meta.provides ?? undefined,
@@ -325,7 +343,7 @@ function objectTypeBuilder() {
325
343
  const graphsToEmit = fieldInGraphs.filter(([graphId, f]) => {
326
344
  const isExternal = f.external === true;
327
345
  const isOverridden = overriddenGraphs.includes(graphId);
328
- const needsToPrintUsedOverridden = provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId);
346
+ const needsToPrintUsedOverridden = provideUsedOverriddenValue(field.name, f, overridesMap, fieldNamesOfImplementedInterfaces, graphId);
329
347
  const isRequired = f.required === true;
330
348
  return (isExternal && isRequired) || needsToPrintUsedOverridden || !isOverridden;
331
349
  });
@@ -335,7 +353,7 @@ function objectTypeBuilder() {
335
353
  joinFields = graphsToEmit.map(([graphId, meta]) => ({
336
354
  graph: graphId,
337
355
  override: meta.override ?? undefined,
338
- usedOverridden: provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
356
+ usedOverridden: provideUsedOverriddenValue(field.name, meta, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
339
357
  type: differencesBetweenGraphs.type ? meta.type : undefined,
340
358
  external: meta.external ?? undefined,
341
359
  provides: meta.provides ?? undefined,
@@ -358,11 +376,11 @@ function objectTypeBuilder() {
358
376
  const graphsToPrintJoinField = fieldInGraphs.filter(([graphId, meta]) => meta.override !== null ||
359
377
  meta.external === true ||
360
378
  (meta.shareable && !overriddenGraphs.includes(graphId)) ||
361
- provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId));
379
+ provideUsedOverriddenValue(field.name, meta, overridesMap, fieldNamesOfImplementedInterfaces, graphId));
362
380
  joinFields = graphsToPrintJoinField.map(([graphId, meta]) => ({
363
381
  graph: graphId,
364
382
  override: meta.override ?? undefined,
365
- usedOverridden: provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
383
+ usedOverridden: provideUsedOverriddenValue(field.name, meta, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
366
384
  type: differencesBetweenGraphs.type ? meta.type : undefined,
367
385
  external: meta.external ?? undefined,
368
386
  provides: meta.provides ?? undefined,
@@ -423,7 +441,8 @@ function objectTypeBuilder() {
423
441
  };
424
442
  }),
425
443
  };
426
- }),
444
+ })
445
+ .filter(helpers_js_1.isDefined),
427
446
  interfaces: Array.from(objectType.interfaces),
428
447
  tags: Array.from(objectType.tags),
429
448
  inaccessible: objectType.inaccessible,
@@ -451,17 +470,15 @@ function objectTypeBuilder() {
451
470
  };
452
471
  }
453
472
  exports.objectTypeBuilder = objectTypeBuilder;
454
- function provideUsedOverriddenValue(fieldName, overridesMap, fieldNamesOfImplementedInterfaces, graphId) {
473
+ function provideUsedOverriddenValue(fieldName, fieldStateInGraph, overridesMap, fieldNamesOfImplementedInterfaces, graphId) {
455
474
  const inGraphs = fieldNamesOfImplementedInterfaces[fieldName];
456
475
  const hasMatchingInterfaceFieldInGraph = inGraphs && inGraphs.has(graphId);
457
- if (!hasMatchingInterfaceFieldInGraph) {
458
- return false;
459
- }
476
+ const isUsedAsNonExternalKey = fieldStateInGraph.usedAsKey && !fieldStateInGraph.external;
460
477
  const hasOverride = typeof overridesMap[graphId] === 'string';
461
- if (!hasOverride) {
462
- return false;
478
+ if (hasOverride && (isUsedAsNonExternalKey || hasMatchingInterfaceFieldInGraph)) {
479
+ return true;
463
480
  }
464
- return true;
481
+ return false;
465
482
  }
466
483
  function getOrCreateObjectType(state, typeName) {
467
484
  const existing = state.get(typeName);
@@ -134,7 +134,8 @@ export function visitFields({ context, selectionSet, typeDefinition, interceptFi
134
134
  }
135
135
  break;
136
136
  }
137
- if (interceptDirective && selection.directives?.length) {
137
+ const isTypename = selection.name.value === '__typename';
138
+ if (!isTypename && interceptDirective && selection.directives?.length) {
138
139
  for (const directive of selection.directives) {
139
140
  interceptDirective({
140
141
  directiveName: directive.name.value,
@@ -142,21 +143,23 @@ export function visitFields({ context, selectionSet, typeDefinition, interceptFi
142
143
  });
143
144
  }
144
145
  }
145
- context.markAsUsed('fields', typeDefinition.kind, typeDefinition.name.value, selectionFieldDef.name.value);
146
- if (interceptField) {
146
+ if (!isTypename) {
147
+ context.markAsUsed('fields', typeDefinition.kind, typeDefinition.name.value, selectionFieldDef.name.value);
148
+ }
149
+ if (!isTypename && interceptField) {
147
150
  interceptField({
148
151
  typeDefinition,
149
152
  fieldName: selection.name.value,
150
153
  });
151
154
  }
152
- if (selectionFieldDef.arguments?.length && interceptArguments) {
155
+ if (!isTypename && selectionFieldDef.arguments?.length && interceptArguments) {
153
156
  interceptArguments({
154
157
  typeDefinition,
155
158
  fieldName: selection.name.value,
156
159
  });
157
160
  continue;
158
161
  }
159
- if (interceptNonExternalField || interceptExternalField) {
162
+ if (!isTypename && (interceptNonExternalField || interceptExternalField)) {
160
163
  const isExternal = selectionFieldDef.directives?.some(d => context.isAvailableFederationDirective('external', d));
161
164
  const fieldName = selection.name.value;
162
165
  const fieldDef = typeDefinition.fields?.find(field => field.name.value === fieldName);
@@ -185,7 +188,8 @@ export function visitFields({ context, selectionSet, typeDefinition, interceptFi
185
188
  if (!innerTypeDef) {
186
189
  continue;
187
190
  }
188
- if (interceptInterfaceType &&
191
+ if (!isTypename &&
192
+ interceptInterfaceType &&
189
193
  (innerTypeDef.kind === Kind.INTERFACE_TYPE_DEFINITION ||
190
194
  innerTypeDef.kind === Kind.INTERFACE_TYPE_EXTENSION)) {
191
195
  interceptInterfaceType({
@@ -208,9 +212,13 @@ export function visitFields({ context, selectionSet, typeDefinition, interceptFi
208
212
  context,
209
213
  selectionSet: innerSelection,
210
214
  typeDefinition: innerTypeDef,
215
+ interceptField,
211
216
  interceptArguments,
212
217
  interceptUnknownField,
213
218
  interceptInterfaceType,
219
+ interceptDirective,
220
+ interceptExternalField,
221
+ interceptFieldWithMissingSelectionSet,
214
222
  });
215
223
  }
216
224
  }
@@ -151,7 +151,9 @@ export function ProvidesRules(context) {
151
151
  }
152
152
  if (info.typeDefinition.kind === Kind.OBJECT_TYPE_DEFINITION ||
153
153
  info.typeDefinition.kind === Kind.OBJECT_TYPE_EXTENSION) {
154
- context.stateBuilder.objectType.field.markAsProvided(info.typeDefinition.name.value, info.fieldName);
154
+ if (info.fieldName !== '__typename') {
155
+ context.stateBuilder.objectType.field.markAsProvided(info.typeDefinition.name.value, info.fieldName);
156
+ }
155
157
  }
156
158
  },
157
159
  });
@@ -78,7 +78,9 @@ export function RequiresRules(context) {
78
78
  interceptField(info) {
79
79
  if (info.typeDefinition.kind === Kind.OBJECT_TYPE_DEFINITION ||
80
80
  info.typeDefinition.kind === Kind.OBJECT_TYPE_EXTENSION) {
81
- context.stateBuilder.objectType.field.markedAsRequired(info.typeDefinition.name.value, info.fieldName);
81
+ if (info.fieldName !== '__typename') {
82
+ context.stateBuilder.objectType.field.markedAsRequired(info.typeDefinition.name.value, info.fieldName);
83
+ }
82
84
  }
83
85
  },
84
86
  interceptUnknownField(info) {
@@ -57,6 +57,33 @@ export function validateSubgraphCore(subgraph) {
57
57
  }
58
58
  export function validateSubgraph(subgraph, stateBuilder, federation, __internal) {
59
59
  subgraph.typeDefs = cleanSubgraphTypeDefsFromSubgraphSpec(subgraph.typeDefs);
60
+ const linkSpecDefinitions = parse(`
61
+ enum Purpose {
62
+ EXECUTION
63
+ SECURITY
64
+ }
65
+
66
+ directive @link(
67
+ url: String
68
+ as: String
69
+ for: link__Purpose
70
+ import: [link__Import]
71
+ ) repeatable on SCHEMA
72
+
73
+ scalar link__Import
74
+
75
+ enum link__Purpose {
76
+ """
77
+ \`SECURITY\` features provide metadata necessary to securely resolve fields.
78
+ """
79
+ SECURITY
80
+
81
+ """
82
+ \`EXECUTION\` features provide metadata necessary for operation execution.
83
+ """
84
+ EXECUTION
85
+ }
86
+ `).definitions;
60
87
  const rulesToSkip = __internal?.disableValidationRules ?? [];
61
88
  const typeNodeInfo = new TypeNodeInfo();
62
89
  const validationContext = createSubgraphValidationContext(subgraph, federation, typeNodeInfo, stateBuilder);
@@ -106,6 +133,12 @@ export function validateSubgraph(subgraph, stateBuilder, federation, __internal)
106
133
  return rule(validationContext);
107
134
  })))));
108
135
  const federationDefinitionReplacements = validationContext.collectFederationDefinitionReplacements();
136
+ const linkSpecDefinitionsToInclude = linkSpecDefinitions.filter(def => {
137
+ if ('name' in def && typeof def.name?.value === 'string') {
138
+ return !stateBuilder.state.types.has(def.name.value);
139
+ }
140
+ return true;
141
+ });
109
142
  const fullTypeDefs = concatAST([
110
143
  {
111
144
  kind: Kind.DOCUMENT,
@@ -115,33 +148,12 @@ export function validateSubgraph(subgraph, stateBuilder, federation, __internal)
115
148
  },
116
149
  validationContext.satisfiesVersionRange('> v1.0') && !stateBuilder.state.specs.link
117
150
  ?
118
- parse(`
119
- enum Purpose {
120
- EXECUTION
121
- SECURITY
122
- }
123
-
124
- directive @link(
125
- url: String
126
- as: String
127
- for: link__Purpose
128
- import: [link__Import]
129
- ) repeatable on SCHEMA
130
-
131
- scalar link__Import
132
-
133
- enum link__Purpose {
134
- """
135
- \`SECURITY\` features provide metadata necessary to securely resolve fields.
136
- """
137
- SECURITY
138
-
139
- """
140
- \`EXECUTION\` features provide metadata necessary for operation execution.
141
- """
142
- EXECUTION
143
- }
144
- `)
151
+ linkSpecDefinitionsToInclude.length > 0
152
+ ? {
153
+ kind: Kind.DOCUMENT,
154
+ definitions: linkSpecDefinitionsToInclude,
155
+ }
156
+ : null
145
157
  : null,
146
158
  subgraph.typeDefs,
147
159
  ].filter(onlyDocumentNode));
@@ -1,4 +1,5 @@
1
1
  import { Kind, OperationTypeNode, parseConstValue, parseType, specifiedDirectives, visit, visitInParallel, } from 'graphql';
2
+ import { print } from '../../graphql/printer.js';
2
3
  export function createSchemaNode(schema) {
3
4
  return {
4
5
  kind: Kind.SCHEMA_DEFINITION,
@@ -728,7 +729,16 @@ function createNamedTypeNode(name) {
728
729
  };
729
730
  }
730
731
  function applyDirectives(common) {
731
- return [].concat(common.ast?.directives ?? [], common.join?.type?.map(createJoinTypeDirectiveNode) ?? [], common.join?.implements?.map(createJoinImplementsDirectiveNode) ?? [], common.join?.field?.map(createJoinFieldDirectiveNode) ?? [], common.join?.unionMember?.map(createJoinUnionMemberDirectiveNode) ?? [], common.join?.enumValue?.map(createJoinEnumValueDirectiveNode) ?? [], common.tags?.map(createTagDirectiveNode) ?? [], common.inaccessible ? [createInaccessibleDirectiveNode()] : [], common.authenticated ? [createAuthenticatedDirectiveNode()] : [], common.policies?.length ? [createPolicyDirectiveNode(common.policies)] : [], common.scopes?.length ? [createRequiresScopesDirectiveNode(common.scopes)] : [], common.deprecated ? [createDeprecatedDirectiveNode(common.deprecated)] : [], common.specifiedBy ? [createSpecifiedByDirectiveNode(common.specifiedBy)] : []);
732
+ const deduplicatedDirectives = (common.ast?.directives ?? [])
733
+ .map(directive => {
734
+ return {
735
+ ast: directive,
736
+ string: print(directive),
737
+ };
738
+ })
739
+ .filter((directive, index, all) => all.findIndex(d => d.string === directive.string) === index)
740
+ .map(d => d.ast);
741
+ return [].concat(deduplicatedDirectives, common.join?.type?.map(createJoinTypeDirectiveNode) ?? [], common.join?.implements?.map(createJoinImplementsDirectiveNode) ?? [], common.join?.field?.map(createJoinFieldDirectiveNode) ?? [], common.join?.unionMember?.map(createJoinUnionMemberDirectiveNode) ?? [], common.join?.enumValue?.map(createJoinEnumValueDirectiveNode) ?? [], common.tags?.map(createTagDirectiveNode) ?? [], common.inaccessible ? [createInaccessibleDirectiveNode()] : [], common.authenticated ? [createAuthenticatedDirectiveNode()] : [], common.policies?.length ? [createPolicyDirectiveNode(common.policies)] : [], common.scopes?.length ? [createRequiresScopesDirectiveNode(common.scopes)] : [], common.deprecated ? [createDeprecatedDirectiveNode(common.deprecated)] : [], common.specifiedBy ? [createSpecifiedByDirectiveNode(common.specifiedBy)] : []);
732
742
  }
733
743
  function nonEmpty(value) {
734
744
  return value != null;
@@ -221,7 +221,7 @@ export function objectTypeBuilder() {
221
221
  .map(([graphId, meta]) => {
222
222
  const type = hasDifferentOutputType ? meta.type : undefined;
223
223
  const override = meta.override ?? undefined;
224
- const usedOverridden = provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId);
224
+ const usedOverridden = provideUsedOverriddenValue(field.name, meta, overridesMap, fieldNamesOfImplementedInterfaces, graphId);
225
225
  const external = shouldSetExternalOnJoinField(meta, graphId, field);
226
226
  const provides = meta.provides ?? undefined;
227
227
  const requires = meta.requires ?? undefined;
@@ -251,7 +251,8 @@ export function objectTypeBuilder() {
251
251
  directives: convertToConst(objectType.ast.directives),
252
252
  },
253
253
  description: objectType.description,
254
- fields: Array.from(objectType.fields.values()).map(field => {
254
+ fields: Array.from(objectType.fields.values())
255
+ .map(field => {
255
256
  const fieldInGraphs = Array.from(field.byGraph.entries());
256
257
  const hasDifferentOutputType = fieldInGraphs.some(([_, meta]) => meta.type !== field.type);
257
258
  const isDefinedEverywhere = field.byGraph.size === (isQuery ? graphs.size : objectType.byGraph.size);
@@ -287,13 +288,30 @@ export function objectTypeBuilder() {
287
288
  differencesBetweenGraphs.type = true;
288
289
  }
289
290
  }
291
+ if (!isQuery && field.byGraph.size === 1) {
292
+ const graphId = field.byGraph.keys().next().value;
293
+ const fieldInGraph = field.byGraph.get(graphId);
294
+ if (fieldInGraph.external &&
295
+ !fieldInGraph.usedAsKey &&
296
+ !fieldInGraph.required &&
297
+ !fieldInGraph.provided &&
298
+ !provideUsedOverriddenValue(field.name, fieldInGraph, overridesMap, fieldNamesOfImplementedInterfaces, graphId) &&
299
+ graphs.get(graphId).version === 'v1.0') {
300
+ return null;
301
+ }
302
+ }
290
303
  if (isQuery) {
291
304
  if (differencesBetweenGraphs.override) {
292
- const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null);
305
+ const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null &&
306
+ (objectType.byGraph.size > 1
307
+ ?
308
+ true
309
+ :
310
+ typeof graphNameToId(meta.override) === 'string'));
293
311
  joinFields = graphsWithOverride.map(([graphId, meta]) => ({
294
312
  graph: graphId,
295
313
  override: meta.override ?? undefined,
296
- usedOverridden: provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
314
+ usedOverridden: provideUsedOverriddenValue(field.name, meta, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
297
315
  type: differencesBetweenGraphs.type ? meta.type : undefined,
298
316
  external: meta.external ?? undefined,
299
317
  provides: meta.provides ?? undefined,
@@ -321,7 +339,7 @@ export function objectTypeBuilder() {
321
339
  const graphsToEmit = fieldInGraphs.filter(([graphId, f]) => {
322
340
  const isExternal = f.external === true;
323
341
  const isOverridden = overriddenGraphs.includes(graphId);
324
- const needsToPrintUsedOverridden = provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId);
342
+ const needsToPrintUsedOverridden = provideUsedOverriddenValue(field.name, f, overridesMap, fieldNamesOfImplementedInterfaces, graphId);
325
343
  const isRequired = f.required === true;
326
344
  return (isExternal && isRequired) || needsToPrintUsedOverridden || !isOverridden;
327
345
  });
@@ -331,7 +349,7 @@ export function objectTypeBuilder() {
331
349
  joinFields = graphsToEmit.map(([graphId, meta]) => ({
332
350
  graph: graphId,
333
351
  override: meta.override ?? undefined,
334
- usedOverridden: provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
352
+ usedOverridden: provideUsedOverriddenValue(field.name, meta, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
335
353
  type: differencesBetweenGraphs.type ? meta.type : undefined,
336
354
  external: meta.external ?? undefined,
337
355
  provides: meta.provides ?? undefined,
@@ -354,11 +372,11 @@ export function objectTypeBuilder() {
354
372
  const graphsToPrintJoinField = fieldInGraphs.filter(([graphId, meta]) => meta.override !== null ||
355
373
  meta.external === true ||
356
374
  (meta.shareable && !overriddenGraphs.includes(graphId)) ||
357
- provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId));
375
+ provideUsedOverriddenValue(field.name, meta, overridesMap, fieldNamesOfImplementedInterfaces, graphId));
358
376
  joinFields = graphsToPrintJoinField.map(([graphId, meta]) => ({
359
377
  graph: graphId,
360
378
  override: meta.override ?? undefined,
361
- usedOverridden: provideUsedOverriddenValue(field.name, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
379
+ usedOverridden: provideUsedOverriddenValue(field.name, meta, overridesMap, fieldNamesOfImplementedInterfaces, graphId),
362
380
  type: differencesBetweenGraphs.type ? meta.type : undefined,
363
381
  external: meta.external ?? undefined,
364
382
  provides: meta.provides ?? undefined,
@@ -419,7 +437,8 @@ export function objectTypeBuilder() {
419
437
  };
420
438
  }),
421
439
  };
422
- }),
440
+ })
441
+ .filter(isDefined),
423
442
  interfaces: Array.from(objectType.interfaces),
424
443
  tags: Array.from(objectType.tags),
425
444
  inaccessible: objectType.inaccessible,
@@ -446,17 +465,15 @@ export function objectTypeBuilder() {
446
465
  },
447
466
  };
448
467
  }
449
- function provideUsedOverriddenValue(fieldName, overridesMap, fieldNamesOfImplementedInterfaces, graphId) {
468
+ function provideUsedOverriddenValue(fieldName, fieldStateInGraph, overridesMap, fieldNamesOfImplementedInterfaces, graphId) {
450
469
  const inGraphs = fieldNamesOfImplementedInterfaces[fieldName];
451
470
  const hasMatchingInterfaceFieldInGraph = inGraphs && inGraphs.has(graphId);
452
- if (!hasMatchingInterfaceFieldInGraph) {
453
- return false;
454
- }
471
+ const isUsedAsNonExternalKey = fieldStateInGraph.usedAsKey && !fieldStateInGraph.external;
455
472
  const hasOverride = typeof overridesMap[graphId] === 'string';
456
- if (!hasOverride) {
457
- return false;
473
+ if (hasOverride && (isUsedAsNonExternalKey || hasMatchingInterfaceFieldInGraph)) {
474
+ return true;
458
475
  }
459
- return true;
476
+ return false;
460
477
  }
461
478
  function getOrCreateObjectType(state, typeName) {
462
479
  const existing = state.get(typeName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theguild/federation-composition",
3
- "version": "0.7.0",
3
+ "version": "0.7.1-alpha-20240108091943-78b56db",
4
4
  "description": "Open Source Composition library for Apollo Federation",
5
5
  "peerDependencies": {
6
6
  "graphql": "^16.0.0"