@apollo/federation-internals 2.12.0 → 2.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/argumentCompositionStrategies.d.ts +2 -1
- package/dist/argumentCompositionStrategies.d.ts.map +1 -1
- package/dist/argumentCompositionStrategies.js +24 -2
- package/dist/argumentCompositionStrategies.js.map +1 -1
- package/dist/definitions.d.ts +1 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +5 -1
- package/dist/definitions.js.map +1 -1
- package/dist/error.d.ts +1 -1
- package/dist/error.js +2 -2
- package/dist/error.js.map +1 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +10 -13
- package/dist/federation.js.map +1 -1
- package/dist/specs/authenticatedSpec.d.ts.map +1 -1
- package/dist/specs/authenticatedSpec.js.map +1 -1
- package/dist/specs/policySpec.d.ts.map +1 -1
- package/dist/specs/policySpec.js.map +1 -1
- package/dist/specs/requiresScopesSpec.d.ts.map +1 -1
- package/dist/specs/requiresScopesSpec.js.map +1 -1
- package/dist/supergraphs.d.ts +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +2 -1
- package/dist/supergraphs.js.map +1 -1
- package/package.json +1 -1
- package/src/argumentCompositionStrategies.ts +38 -4
- package/src/definitions.ts +4 -0
- package/src/error.ts +4 -4
- package/src/federation.ts +19 -18
- package/src/specs/authenticatedSpec.ts +4 -0
- package/src/specs/policySpec.ts +4 -0
- package/src/specs/requiresScopesSpec.ts +4 -0
- package/src/supergraphs.ts +9 -4
|
@@ -65,8 +65,9 @@ function unionValues(values: any[]): any {
|
|
|
65
65
|
/**
|
|
66
66
|
* Performs conjunction of 2d arrays that represent conditions in Disjunctive Normal Form.
|
|
67
67
|
*
|
|
68
|
-
*
|
|
69
|
-
* *
|
|
68
|
+
* Each 2D array is interpreted as follows
|
|
69
|
+
* * Inner array is interpreted as the conjunction (an AND) of the conditions in the array.
|
|
70
|
+
* * Outer array is interpreted as the disjunction (an OR) of the inner arrays.
|
|
70
71
|
*
|
|
71
72
|
* Algorithm
|
|
72
73
|
* * filter out duplicate entries to limit the amount of necessary computations
|
|
@@ -74,12 +75,19 @@ function unionValues(values: any[]): any {
|
|
|
74
75
|
* * simplify combinations by dropping duplicate conditions (i.e. p ^ p = p, p ^ q = q ^ p)
|
|
75
76
|
* * eliminate entries that are subsumed by others (i.e. (p ^ q) subsumes (p ^ q ^ r))
|
|
76
77
|
*/
|
|
77
|
-
function dnfConjunction<T>(values: T[][][]): T[][] {
|
|
78
|
+
export function dnfConjunction<T>(values: T[][][]): T[][] {
|
|
78
79
|
// should never be the case
|
|
79
80
|
if (values.length == 0) {
|
|
80
81
|
return [];
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
// Copy the 2D arrays, as we'll be modifying them below (due to sorting).
|
|
85
|
+
for (let i = 0; i < values.length; i++) {
|
|
86
|
+
// See the doc string for `convertEmptyToTrue()` to understand why this is
|
|
87
|
+
// necessary.
|
|
88
|
+
values[i] = convertEmptyToTrue(dnfCopy(values[i]));
|
|
89
|
+
}
|
|
90
|
+
|
|
83
91
|
// we first filter out duplicate values from candidates
|
|
84
92
|
// this avoids exponential computation of exactly the same conditions
|
|
85
93
|
const filtered = filterNestedArrayDuplicates(values);
|
|
@@ -119,7 +127,14 @@ function filterNestedArrayDuplicates<T>(values: T[][][]): T[][][] {
|
|
|
119
127
|
const filtered: T[][][] = [];
|
|
120
128
|
const seen = new Set<string>;
|
|
121
129
|
values.forEach((value) => {
|
|
122
|
-
value.
|
|
130
|
+
value.forEach((inner) => {
|
|
131
|
+
inner.sort();
|
|
132
|
+
})
|
|
133
|
+
value.sort((a, b) => {
|
|
134
|
+
const left = JSON.stringify(a);
|
|
135
|
+
const right = JSON.stringify(b);
|
|
136
|
+
return left > right ? 1 : left < right ? -1 : 0;
|
|
137
|
+
});
|
|
123
138
|
const key = JSON.stringify(value);
|
|
124
139
|
if (!seen.has(key)) {
|
|
125
140
|
seen.add(key);
|
|
@@ -160,6 +175,25 @@ function deduplicateSubsumedValues<T>(values: T[][]): T[][] {
|
|
|
160
175
|
return result;
|
|
161
176
|
}
|
|
162
177
|
|
|
178
|
+
function dnfCopy<T>(value: T[][]): T[][] {
|
|
179
|
+
const newValue = new Array(value.length);
|
|
180
|
+
for (let i = 0; i < value.length; i++) {
|
|
181
|
+
newValue[i] = value[i].slice();
|
|
182
|
+
}
|
|
183
|
+
return newValue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Normally for DNF, you'd consider [] to be always false and [[]] to be always
|
|
188
|
+
* true, and code that uses some()/every() needs no special-casing to work with
|
|
189
|
+
* these definitions. However, router special-cases [] to also mean true, and so
|
|
190
|
+
* if we're about to do any evaluation on DNFs, we need to do these conversions
|
|
191
|
+
* beforehand.
|
|
192
|
+
*/
|
|
193
|
+
export function convertEmptyToTrue<T>(value: T[][]): T[][] {
|
|
194
|
+
return value.length === 0 ? [[]] : value;
|
|
195
|
+
}
|
|
196
|
+
|
|
163
197
|
export const ARGUMENT_COMPOSITION_STRATEGIES = {
|
|
164
198
|
MAX: {
|
|
165
199
|
name: 'MAX',
|
package/src/definitions.ts
CHANGED
|
@@ -3825,3 +3825,7 @@ function copyDirectiveDefinitionInner(
|
|
|
3825
3825
|
export function isFieldDefinition(elem: SchemaElement<any, any>): elem is FieldDefinition<any> {
|
|
3826
3826
|
return elem instanceof FieldDefinition;
|
|
3827
3827
|
}
|
|
3828
|
+
|
|
3829
|
+
export function isElementNamedType(elem: SchemaElement<any, any>): elem is NamedType {
|
|
3830
|
+
return elem instanceof BaseNamedType;
|
|
3831
|
+
}
|
package/src/error.ts
CHANGED
|
@@ -633,9 +633,9 @@ const MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED = makeCodeDefinition(
|
|
|
633
633
|
{ addedIn: '2.8.0' },
|
|
634
634
|
);
|
|
635
635
|
|
|
636
|
-
const
|
|
637
|
-
'
|
|
638
|
-
'The @authenticated, @requiresScopes and @policy directive cannot be applied on interface, interface
|
|
636
|
+
const AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE = makeCodeDefinition(
|
|
637
|
+
'AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE',
|
|
638
|
+
'The @authenticated, @requiresScopes and @policy directive cannot be applied on interface, interface fields and interface object',
|
|
639
639
|
{ addedIn: '2.9.4' },
|
|
640
640
|
);
|
|
641
641
|
|
|
@@ -746,7 +746,7 @@ export const ERRORS = {
|
|
|
746
746
|
LIST_SIZE_INVALID_SIZED_FIELD,
|
|
747
747
|
LIST_SIZE_INVALID_SLICING_ARGUMENT,
|
|
748
748
|
MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED,
|
|
749
|
-
|
|
749
|
+
AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE,
|
|
750
750
|
MISSING_TRANSITIVE_AUTH_REQUIREMENTS,
|
|
751
751
|
};
|
|
752
752
|
|
package/src/federation.ts
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
isWrapperType,
|
|
38
38
|
possibleRuntimeTypes,
|
|
39
39
|
isIntType,
|
|
40
|
-
Type, isFieldDefinition,
|
|
40
|
+
Type, isFieldDefinition, isElementNamedType,
|
|
41
41
|
} from "./definitions";
|
|
42
42
|
import { assert, MultiMap, printHumanReadableList, OrderedMap, mapValues, assertUnreachable } from "./utils";
|
|
43
43
|
import { SDLValidationRule } from "graphql/validation/ValidationContext";
|
|
@@ -1071,7 +1071,7 @@ function validateListSizeAppliedToList(
|
|
|
1071
1071
|
) {
|
|
1072
1072
|
const { sizedFields = [] } = application.arguments();
|
|
1073
1073
|
// @listSize must be applied to a list https://ibm.github.io/graphql-specs/cost-spec.html#sec-Valid-List-Size-Target
|
|
1074
|
-
if (!sizedFields.length && parent.type && !isListType(parent.type)) {
|
|
1074
|
+
if (!sizedFields.length && parent.type && !isListType(parent.type) && !isNonNullListType(parent.type)) {
|
|
1075
1075
|
errorCollector.push(ERRORS.LIST_SIZE_APPLIED_TO_NON_LIST.err(
|
|
1076
1076
|
`"${parent.coordinate}" is not a list`,
|
|
1077
1077
|
{ nodes: sourceASTs(application, parent) },
|
|
@@ -1141,8 +1141,9 @@ function validateSizedFieldsAreValidLists(
|
|
|
1141
1141
|
) {
|
|
1142
1142
|
const { sizedFields = [] } = application.arguments();
|
|
1143
1143
|
// Validate sizedFields https://ibm.github.io/graphql-specs/cost-spec.html#sec-Valid-Sized-Fields-Target
|
|
1144
|
-
if (sizedFields.length) {
|
|
1145
|
-
|
|
1144
|
+
if (sizedFields.length && parent.type) {
|
|
1145
|
+
const baseParentType = baseType(parent.type);
|
|
1146
|
+
if (!isCompositeType(baseParentType)) {
|
|
1146
1147
|
// The output type must have fields
|
|
1147
1148
|
errorCollector.push(ERRORS.LIST_SIZE_INVALID_SIZED_FIELD.err(
|
|
1148
1149
|
`Sized fields cannot be used because "${parent.type}" is not a composite type`,
|
|
@@ -1150,11 +1151,11 @@ function validateSizedFieldsAreValidLists(
|
|
|
1150
1151
|
));
|
|
1151
1152
|
} else {
|
|
1152
1153
|
for (const sizedFieldName of sizedFields) {
|
|
1153
|
-
const sizedField =
|
|
1154
|
+
const sizedField = baseParentType.field(sizedFieldName);
|
|
1154
1155
|
if (!sizedField) {
|
|
1155
1156
|
// Sized fields must be present on the output type
|
|
1156
1157
|
errorCollector.push(ERRORS.LIST_SIZE_INVALID_SIZED_FIELD.err(
|
|
1157
|
-
`Sized field "${sizedFieldName}" is not a field on type "${
|
|
1158
|
+
`Sized field "${sizedFieldName}" is not a field on type "${baseParentType.coordinate}"`,
|
|
1158
1159
|
{ nodes: sourceASTs(application, parent) }
|
|
1159
1160
|
));
|
|
1160
1161
|
} else if (!sizedField.type || !(isListType(sizedField.type) || isNonNullListType(sizedField.type))) {
|
|
@@ -1846,7 +1847,7 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
1846
1847
|
validateSizedFieldsAreValidLists(application, parent, errorCollector);
|
|
1847
1848
|
}
|
|
1848
1849
|
|
|
1849
|
-
// Validate @authenticated, @requireScopes and @policy
|
|
1850
|
+
// Validate @authenticated, @requireScopes and @policy usage on interfaces and interface objects
|
|
1850
1851
|
validateNoAuthenticationOnInterfaces(metadata, errorCollector);
|
|
1851
1852
|
|
|
1852
1853
|
return errorCollector;
|
|
@@ -2907,15 +2908,15 @@ function validateNoAuthenticationOnInterfaces(metadata: FederationMetadata, erro
|
|
|
2907
2908
|
const policyDirective = metadata.policyDirective();
|
|
2908
2909
|
[authenticatedDirective, requiresScopesDirective, policyDirective].forEach((directive) => {
|
|
2909
2910
|
for (const application of directive.applications()) {
|
|
2910
|
-
const element = application.parent;
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2911
|
+
const element: SchemaElement<any, any> = application.parent;
|
|
2912
|
+
if (
|
|
2913
|
+
// Is it applied on interface or interface object types?
|
|
2914
|
+
(isElementNamedType(element) &&
|
|
2915
|
+
(isInterfaceType(element) || isInterfaceObjectType(element))
|
|
2916
|
+
) ||
|
|
2917
|
+
// Is it applied on interface fields?
|
|
2918
|
+
(isFieldDefinition(element) && isInterfaceType(element.parent))
|
|
2919
|
+
) {
|
|
2919
2920
|
let kind = '';
|
|
2920
2921
|
switch (element.kind) {
|
|
2921
2922
|
case 'FieldDefinition':
|
|
@@ -2928,8 +2929,8 @@ function validateNoAuthenticationOnInterfaces(metadata: FederationMetadata, erro
|
|
|
2928
2929
|
kind = 'interface object';
|
|
2929
2930
|
break;
|
|
2930
2931
|
}
|
|
2931
|
-
errorCollector.push(ERRORS.
|
|
2932
|
-
`Invalid use of @${directive.name} on ${kind} "${element.coordinate}": @${directive.name} cannot be applied on interfaces, interface
|
|
2932
|
+
errorCollector.push(ERRORS.AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE.err(
|
|
2933
|
+
`Invalid use of @${directive.name} on ${kind} "${element.coordinate}": @${directive.name} cannot be applied on interfaces, interface fields and interface objects`,
|
|
2933
2934
|
{nodes: sourceASTs(application, element.parent)},
|
|
2934
2935
|
));
|
|
2935
2936
|
}
|
|
@@ -24,6 +24,10 @@ export class AuthenticatedSpecDefinition extends FeatureDefinition {
|
|
|
24
24
|
minimumFederationVersion,
|
|
25
25
|
);
|
|
26
26
|
|
|
27
|
+
// WARNING: we cannot declare staticArgumentTransform() as access control merge logic needs to propagate
|
|
28
|
+
// requirements upwards/downwards between types and interfaces. We hijack the merge process by providing
|
|
29
|
+
// implementations/interfaces as "additional sources". This means that we cannot apply staticArgumentTransform()
|
|
30
|
+
// as subgraph index index will be wrong/undefined.
|
|
27
31
|
this.registerDirective(createDirectiveSpecification({
|
|
28
32
|
name: AuthenticatedSpecDefinition.directiveName,
|
|
29
33
|
locations: [
|
package/src/specs/policySpec.ts
CHANGED
|
@@ -31,6 +31,10 @@ export class PolicySpecDefinition extends FeatureDefinition {
|
|
|
31
31
|
|
|
32
32
|
this.registerType(createScalarTypeSpecification({ name: PolicyTypeName.POLICY }));
|
|
33
33
|
|
|
34
|
+
// WARNING: we cannot declare staticArgumentTransform() as access control merge logic needs to propagate
|
|
35
|
+
// requirements upwards/downwards between types and interfaces. We hijack the merge process by providing
|
|
36
|
+
// implementations/interfaces as "additional sources". This means that we cannot apply staticArgumentTransform()
|
|
37
|
+
// as subgraph index index will be wrong/undefined.
|
|
34
38
|
this.registerDirective(createDirectiveSpecification({
|
|
35
39
|
name: PolicySpecDefinition.directiveName,
|
|
36
40
|
args: [{
|
|
@@ -32,6 +32,10 @@ export class RequiresScopesSpecDefinition extends FeatureDefinition {
|
|
|
32
32
|
|
|
33
33
|
this.registerType(createScalarTypeSpecification({ name: RequiresScopesTypeName.SCOPE }));
|
|
34
34
|
|
|
35
|
+
// WARNING: we cannot declare staticArgumentTransform() as access control merge logic needs to propagate
|
|
36
|
+
// requirements upwards/downwards between types and interfaces. We hijack the merge process by providing
|
|
37
|
+
// implementations/interfaces as "additional sources". This means that we cannot apply staticArgumentTransform()
|
|
38
|
+
// as subgraph index index will be wrong/undefined.
|
|
35
39
|
this.registerDirective(createDirectiveSpecification({
|
|
36
40
|
name: RequiresScopesSpecDefinition.directiveName,
|
|
37
41
|
args: [{
|
package/src/supergraphs.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { DocumentNode, GraphQLError } from "graphql";
|
|
2
2
|
import { CoreFeatures, Schema, sourceASTs } from "./definitions";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ErrCoreCheckFailed,
|
|
5
|
+
FeatureUrl,
|
|
6
|
+
FeatureVersion,
|
|
7
|
+
} from './specs/coreSpec';
|
|
4
8
|
import { joinIdentity, JoinSpecDefinition, JOIN_VERSIONS } from "./specs/joinSpec";
|
|
5
9
|
import { CONTEXT_VERSIONS, ContextSpecDefinition } from "./specs/contextSpec";
|
|
6
10
|
import { COST_VERSIONS, costIdentity, CostSpecDefinition } from "./specs/costSpec";
|
|
7
11
|
import { buildSchema, buildSchemaFromAST } from "./buildSchema";
|
|
8
12
|
import { extractSubgraphsNamesAndUrlsFromSupergraph, extractSubgraphsFromSupergraph } from "./extractSubgraphsFromSupergraph";
|
|
9
13
|
import { ERRORS } from "./error";
|
|
10
|
-
import { Subgraphs } from
|
|
14
|
+
import { Subgraphs } from './federation';
|
|
11
15
|
|
|
12
16
|
export const DEFAULT_SUPPORTED_SUPERGRAPH_FEATURES = new Set([
|
|
13
17
|
'https://specs.apollo.dev/core/v0.1',
|
|
@@ -24,7 +28,7 @@ export const DEFAULT_SUPPORTED_SUPERGRAPH_FEATURES = new Set([
|
|
|
24
28
|
'https://specs.apollo.dev/inaccessible/v0.2',
|
|
25
29
|
]);
|
|
26
30
|
|
|
27
|
-
export const ROUTER_SUPPORTED_SUPERGRAPH_FEATURES = new Set([
|
|
31
|
+
export const ROUTER_SUPPORTED_SUPERGRAPH_FEATURES: Set<string> = new Set([
|
|
28
32
|
'https://specs.apollo.dev/core/v0.1',
|
|
29
33
|
'https://specs.apollo.dev/core/v0.2',
|
|
30
34
|
'https://specs.apollo.dev/join/v0.1',
|
|
@@ -40,10 +44,11 @@ export const ROUTER_SUPPORTED_SUPERGRAPH_FEATURES = new Set([
|
|
|
40
44
|
'https://specs.apollo.dev/authenticated/v0.1',
|
|
41
45
|
'https://specs.apollo.dev/requiresScopes/v0.1',
|
|
42
46
|
'https://specs.apollo.dev/policy/v0.1',
|
|
43
|
-
'https://specs.apollo.dev/source/v0.1',
|
|
44
47
|
'https://specs.apollo.dev/context/v0.1',
|
|
45
48
|
'https://specs.apollo.dev/cost/v0.1',
|
|
46
49
|
'https://specs.apollo.dev/connect/v0.1',
|
|
50
|
+
'https://specs.apollo.dev/connect/v0.2',
|
|
51
|
+
'https://specs.apollo.dev/connect/v0.3',
|
|
47
52
|
'https://specs.apollo.dev/cacheTag/v0.1',
|
|
48
53
|
]);
|
|
49
54
|
|