@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.
- package/cjs/subgraph/helpers.js +14 -6
- package/cjs/subgraph/validation/rules/elements/provides.js +3 -1
- package/cjs/subgraph/validation/rules/elements/requires.js +3 -1
- package/cjs/subgraph/validation/validate-subgraph.js +39 -27
- package/cjs/supergraph/composition/ast.js +11 -1
- package/cjs/supergraph/composition/object-type.js +33 -16
- package/esm/subgraph/helpers.js +14 -6
- package/esm/subgraph/validation/rules/elements/provides.js +3 -1
- package/esm/subgraph/validation/rules/elements/requires.js +3 -1
- package/esm/subgraph/validation/validate-subgraph.js +39 -27
- package/esm/supergraph/composition/ast.js +11 -1
- package/esm/supergraph/composition/object-type.js +33 -16
- package/package.json +1 -1
package/cjs/subgraph/helpers.js
CHANGED
|
@@ -138,7 +138,8 @@ function visitFields({ context, selectionSet, typeDefinition, interceptField, in
|
|
|
138
138
|
}
|
|
139
139
|
break;
|
|
140
140
|
}
|
|
141
|
-
|
|
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
|
-
|
|
150
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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())
|
|
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
|
-
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
476
|
+
const isUsedAsNonExternalKey = fieldStateInGraph.usedAsKey && !fieldStateInGraph.external;
|
|
460
477
|
const hasOverride = typeof overridesMap[graphId] === 'string';
|
|
461
|
-
if (
|
|
462
|
-
return
|
|
478
|
+
if (hasOverride && (isUsedAsNonExternalKey || hasMatchingInterfaceFieldInGraph)) {
|
|
479
|
+
return true;
|
|
463
480
|
}
|
|
464
|
-
return
|
|
481
|
+
return false;
|
|
465
482
|
}
|
|
466
483
|
function getOrCreateObjectType(state, typeName) {
|
|
467
484
|
const existing = state.get(typeName);
|
package/esm/subgraph/helpers.js
CHANGED
|
@@ -134,7 +134,8 @@ export function visitFields({ context, selectionSet, typeDefinition, interceptFi
|
|
|
134
134
|
}
|
|
135
135
|
break;
|
|
136
136
|
}
|
|
137
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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())
|
|
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
|
-
|
|
453
|
-
return false;
|
|
454
|
-
}
|
|
471
|
+
const isUsedAsNonExternalKey = fieldStateInGraph.usedAsKey && !fieldStateInGraph.external;
|
|
455
472
|
const hasOverride = typeof overridesMap[graphId] === 'string';
|
|
456
|
-
if (
|
|
457
|
-
return
|
|
473
|
+
if (hasOverride && (isUsedAsNonExternalKey || hasMatchingInterfaceFieldInGraph)) {
|
|
474
|
+
return true;
|
|
458
475
|
}
|
|
459
|
-
return
|
|
476
|
+
return false;
|
|
460
477
|
}
|
|
461
478
|
function getOrCreateObjectType(state, typeName) {
|
|
462
479
|
const existing = state.get(typeName);
|
package/package.json
CHANGED