@apollo/federation-internals 2.0.0-preview.9 → 2.0.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.
- package/CHANGELOG.md +25 -0
- package/dist/coreSpec.d.ts +1 -1
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +34 -11
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +20 -8
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +97 -58
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +10 -1
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +10 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +22 -2
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +1 -1
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +4 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +33 -26
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.js +1 -1
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.d.ts +7 -3
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +622 -32
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/precompute.d.ts.map +1 -1
- package/dist/precompute.js +2 -2
- package/dist/precompute.js.map +1 -1
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +16 -5
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +1 -0
- package/dist/supergraphs.js.map +1 -1
- package/dist/validate.js +13 -7
- package/dist/validate.js.map +1 -1
- package/dist/values.d.ts +2 -2
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +13 -11
- package/dist/values.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/coreSpec.test.ts +112 -0
- package/src/__tests__/removeInaccessibleElements.test.ts +2216 -177
- package/src/__tests__/subgraphValidation.test.ts +22 -5
- package/src/__tests__/values.test.ts +315 -3
- package/src/coreSpec.ts +70 -16
- package/src/definitions.ts +201 -83
- package/src/directiveAndTypeSpecification.ts +18 -1
- package/src/error.ts +62 -2
- package/src/extractSubgraphsFromSupergraph.ts +1 -1
- package/src/federation.ts +36 -26
- package/src/federationSpec.ts +2 -2
- package/src/inaccessibleSpec.ts +973 -55
- package/src/precompute.ts +2 -4
- package/src/schemaUpgrader.ts +25 -6
- package/src/supergraphs.ts +1 -0
- package/src/validate.ts +20 -9
- package/src/values.ts +39 -12
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/definitions.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
FeatureUrl,
|
|
25
25
|
findCoreSpecVersion,
|
|
26
26
|
isCoreSpecDirectiveApplication,
|
|
27
|
-
|
|
27
|
+
removeAllCoreFeatures,
|
|
28
28
|
} from "./coreSpec";
|
|
29
29
|
import { assert, mapValues, MapWithCachedArrays, setValues } from "./utils";
|
|
30
30
|
import { withDefaultValues, valueEquals, valueToString, valueToAST, variablesInValue, valueFromAST, valueNodeToConstValueNode } from "./values";
|
|
@@ -47,7 +47,15 @@ const DEFAULT_VALIDATION_ERROR_MESSAGE = 'The schema is not a valid GraphQL sche
|
|
|
47
47
|
|
|
48
48
|
export const ErrGraphQLValidationFailed = (causes: GraphQLError[], message: string = DEFAULT_VALIDATION_ERROR_MESSAGE) =>
|
|
49
49
|
err(validationErrorCode, {
|
|
50
|
-
message,
|
|
50
|
+
message: message + '. Caused by:\n' + causes.map((c) => c.toString()).join('\n\n'),
|
|
51
|
+
causes
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const apiSchemaValidationErrorCode = 'GraphQLAPISchemaValidationFailed';
|
|
55
|
+
|
|
56
|
+
export const ErrGraphQLAPISchemaValidationFailed = (causes: GraphQLError[]) =>
|
|
57
|
+
err(apiSchemaValidationErrorCode, {
|
|
58
|
+
message: 'The supergraph schema failed to produce a valid API schema',
|
|
51
59
|
causes
|
|
52
60
|
});
|
|
53
61
|
|
|
@@ -57,7 +65,7 @@ export const ErrGraphQLValidationFailed = (causes: GraphQLError[], message: stri
|
|
|
57
65
|
*/
|
|
58
66
|
export function errorCauses(e: Error): GraphQLError[] | undefined {
|
|
59
67
|
if (e instanceof GraphQLErrorExt) {
|
|
60
|
-
if (e.code === validationErrorCode) {
|
|
68
|
+
if (e.code === validationErrorCode || e.code === apiSchemaValidationErrorCode) {
|
|
61
69
|
return ((e as any).causes) as GraphQLError[];
|
|
62
70
|
}
|
|
63
71
|
return [e];
|
|
@@ -214,6 +222,22 @@ export function isInputType(type: Type): type is InputType {
|
|
|
214
222
|
}
|
|
215
223
|
}
|
|
216
224
|
|
|
225
|
+
export function isTypeOfKind<T extends Type>(type: Type, kind: T['kind']): type is T {
|
|
226
|
+
return type.kind === kind;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function filterTypesOfKind<T extends Type>(types: readonly Type[], kind: T['kind']): T[] {
|
|
230
|
+
return types.reduce(
|
|
231
|
+
(acc: T[], type: Type) => {
|
|
232
|
+
if (isTypeOfKind(type, kind)) {
|
|
233
|
+
acc.push(type);
|
|
234
|
+
}
|
|
235
|
+
return acc;
|
|
236
|
+
},
|
|
237
|
+
[],
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
217
241
|
export function baseType(type: Type): NamedType {
|
|
218
242
|
return isWrapperType(type) ? type.baseType() : type;
|
|
219
243
|
}
|
|
@@ -383,8 +407,8 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
|
383
407
|
}
|
|
384
408
|
}
|
|
385
409
|
|
|
386
|
-
export function sourceASTs(...elts: ({ sourceAST?:
|
|
387
|
-
return elts.map(elt => elt?.sourceAST).filter((elt): elt is
|
|
410
|
+
export function sourceASTs<TNode extends ASTNode = ASTNode>(...elts: ({ sourceAST?: TNode } | undefined)[]): TNode[] {
|
|
411
|
+
return elts.map(elt => elt?.sourceAST).filter((elt): elt is TNode => elt !== undefined);
|
|
388
412
|
}
|
|
389
413
|
|
|
390
414
|
// Not exposed: mostly about avoid code duplication between SchemaElement and Directive (which is not a SchemaElement as it can't
|
|
@@ -492,39 +516,30 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
|
|
|
492
516
|
}
|
|
493
517
|
|
|
494
518
|
applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
|
|
495
|
-
|
|
519
|
+
nameOrDef: DirectiveDefinition<TApplicationArgs> | string,
|
|
496
520
|
args?: TApplicationArgs,
|
|
497
521
|
asFirstDirective: boolean = false,
|
|
498
522
|
): Directive<TOwnType, TApplicationArgs> {
|
|
499
|
-
let
|
|
500
|
-
if (
|
|
501
|
-
this.checkUpdate(
|
|
502
|
-
|
|
503
|
-
if (
|
|
504
|
-
|
|
523
|
+
let name: string;
|
|
524
|
+
if (typeof nameOrDef === 'string') {
|
|
525
|
+
this.checkUpdate();
|
|
526
|
+
const def = this.schema().directive(nameOrDef) ?? this.schema().blueprint.onMissingDirectiveDefinition(this.schema(), nameOrDef, args);
|
|
527
|
+
if (!def) {
|
|
528
|
+
throw this.schema().blueprint.onGraphQLJSValidationError(
|
|
529
|
+
this.schema(),
|
|
530
|
+
new GraphQLError(`Unknown directive "@${nameOrDef}".`)
|
|
531
|
+
);
|
|
505
532
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
if (typeof nameOrDefOrDirective === 'string') {
|
|
509
|
-
this.checkUpdate();
|
|
510
|
-
const def = this.schema().directive(nameOrDefOrDirective) ?? this.schema().blueprint.onMissingDirectiveDefinition(this.schema(), nameOrDefOrDirective, args);
|
|
511
|
-
if (!def) {
|
|
512
|
-
throw this.schema().blueprint.onGraphQLJSValidationError(
|
|
513
|
-
this.schema(),
|
|
514
|
-
new GraphQLError(`Unknown directive "@${nameOrDefOrDirective}".`)
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
if (Array.isArray(def)) {
|
|
518
|
-
throw ErrGraphQLValidationFailed(def);
|
|
519
|
-
}
|
|
520
|
-
name = nameOrDefOrDirective;
|
|
521
|
-
} else {
|
|
522
|
-
this.checkUpdate(nameOrDefOrDirective);
|
|
523
|
-
name = nameOrDefOrDirective.name;
|
|
533
|
+
if (Array.isArray(def)) {
|
|
534
|
+
throw ErrGraphQLValidationFailed(def);
|
|
524
535
|
}
|
|
525
|
-
|
|
526
|
-
|
|
536
|
+
name = nameOrDef;
|
|
537
|
+
} else {
|
|
538
|
+
this.checkUpdate(nameOrDef);
|
|
539
|
+
name = nameOrDef.name;
|
|
527
540
|
}
|
|
541
|
+
const toAdd = new Directive<TOwnType, TApplicationArgs>(name, args ?? Object.create(null));
|
|
542
|
+
Element.prototype['setParent'].call(toAdd, this);
|
|
528
543
|
// TODO: we should typecheck arguments or our TApplicationArgs business is just a lie.
|
|
529
544
|
if (asFirstDirective) {
|
|
530
545
|
this._appliedDirectives.unshift(toAdd);
|
|
@@ -708,7 +723,7 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
708
723
|
/**
|
|
709
724
|
* Removes this type definition from its parent schema.
|
|
710
725
|
*
|
|
711
|
-
* After calling this method, this type will be "detached": it
|
|
726
|
+
* After calling this method, this type will be "detached": it will have no parent, schema, fields,
|
|
712
727
|
* values, directives, etc...
|
|
713
728
|
*
|
|
714
729
|
* Note that it is always allowed to remove a type, but this may make a valid schema
|
|
@@ -724,15 +739,18 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
|
|
|
724
739
|
}
|
|
725
740
|
this.checkRemoval();
|
|
726
741
|
this.onModification();
|
|
727
|
-
this.
|
|
728
|
-
Schema.prototype['removeTypeInternal'].call(this._parent, this);
|
|
729
|
-
this.removeAppliedDirectives();
|
|
742
|
+
// Remove this type's children.
|
|
730
743
|
this.sourceAST = undefined;
|
|
744
|
+
this.removeAppliedDirectives();
|
|
745
|
+
this.removeInnerElements();
|
|
746
|
+
// Remove this type's references.
|
|
731
747
|
const toReturn = setValues(this._referencers).map(r => {
|
|
732
748
|
SchemaElement.prototype['removeTypeReferenceInternal'].call(r, this);
|
|
733
749
|
return r;
|
|
734
750
|
});
|
|
735
751
|
this._referencers.clear();
|
|
752
|
+
// Remove this type from its parent schema.
|
|
753
|
+
Schema.prototype['removeTypeInternal'].call(this._parent, this);
|
|
736
754
|
this._parent = undefined;
|
|
737
755
|
return toReturn;
|
|
738
756
|
}
|
|
@@ -919,9 +937,12 @@ export class CoreFeature {
|
|
|
919
937
|
}
|
|
920
938
|
|
|
921
939
|
isFeatureDefinition(element: NamedType | DirectiveDefinition): boolean {
|
|
940
|
+
const importName = element.kind === 'DirectiveDefinition'
|
|
941
|
+
? '@' + element.name
|
|
942
|
+
: element.name;
|
|
922
943
|
return element.name.startsWith(this.nameInSchema + '__')
|
|
923
944
|
|| (element.kind === 'DirectiveDefinition' && element.name === this.nameInSchema)
|
|
924
|
-
|| !!this.imports.find((i) =>
|
|
945
|
+
|| !!this.imports.find((i) => importName === (i.as ?? i.name));
|
|
925
946
|
}
|
|
926
947
|
|
|
927
948
|
directiveNameInSchema(name: string): string {
|
|
@@ -1136,13 +1157,7 @@ export class Schema {
|
|
|
1136
1157
|
|
|
1137
1158
|
const apiSchema = this.clone();
|
|
1138
1159
|
removeInaccessibleElements(apiSchema);
|
|
1139
|
-
|
|
1140
|
-
if (coreFeatures) {
|
|
1141
|
-
// Note that core being a feature itself, this will remove core itself and mark apiSchema as 'not core'
|
|
1142
|
-
for (const coreFeature of coreFeatures.allFeatures()) {
|
|
1143
|
-
removeFeatureElements(apiSchema, coreFeature);
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1160
|
+
removeAllCoreFeatures(apiSchema);
|
|
1146
1161
|
assert(!apiSchema.isCoreSchema(), "The API schema shouldn't be a core schema")
|
|
1147
1162
|
apiSchema.validate();
|
|
1148
1163
|
this.apiSchema = apiSchema;
|
|
@@ -1170,20 +1185,42 @@ export class Schema {
|
|
|
1170
1185
|
/**
|
|
1171
1186
|
* All the types defined on this schema, excluding the built-in types.
|
|
1172
1187
|
*/
|
|
1173
|
-
types
|
|
1174
|
-
|
|
1175
|
-
|
|
1188
|
+
types(): readonly NamedType[] {
|
|
1189
|
+
return this._types.values();
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
interfaceTypes(): readonly InterfaceType[] {
|
|
1193
|
+
return filterTypesOfKind<InterfaceType>(this.types(), 'InterfaceType');
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
objectTypes(): readonly ObjectType[] {
|
|
1197
|
+
return filterTypesOfKind<ObjectType>(this.types(), 'ObjectType');
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
unionTypes(): readonly UnionType[] {
|
|
1201
|
+
return filterTypesOfKind<UnionType>(this.types(), 'UnionType');
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
scalarTypes(): readonly ScalarType[] {
|
|
1205
|
+
return filterTypesOfKind<ScalarType>(this.types(), 'ScalarType');
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
inputTypes(): readonly InputObjectType[] {
|
|
1209
|
+
return filterTypesOfKind<InputObjectType>(this.types(), 'InputObjectType');
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
enumTypes(): readonly EnumType[] {
|
|
1213
|
+
return filterTypesOfKind<EnumType>(this.types(), 'EnumType');
|
|
1176
1214
|
}
|
|
1177
1215
|
|
|
1178
1216
|
/**
|
|
1179
1217
|
* All the built-in types for this schema (those that are not displayed when printing the schema).
|
|
1180
1218
|
*/
|
|
1181
|
-
builtInTypes
|
|
1219
|
+
builtInTypes(includeShadowed: boolean = false): readonly NamedType[] {
|
|
1182
1220
|
const allBuiltIns = this._builtInTypes.values();
|
|
1183
|
-
const forKind = (kind ? allBuiltIns.filter(t => t.kind === kind) : allBuiltIns) as readonly T[];
|
|
1184
1221
|
return includeShadowed
|
|
1185
|
-
?
|
|
1186
|
-
:
|
|
1222
|
+
? allBuiltIns
|
|
1223
|
+
: allBuiltIns.filter(t => !this.isShadowedBuiltInType(t));
|
|
1187
1224
|
}
|
|
1188
1225
|
|
|
1189
1226
|
private isShadowedBuiltInType(type: NamedType) {
|
|
@@ -1193,8 +1230,8 @@ export class Schema {
|
|
|
1193
1230
|
/**
|
|
1194
1231
|
* All the types, including the built-in ones.
|
|
1195
1232
|
*/
|
|
1196
|
-
allTypes
|
|
1197
|
-
return this.builtInTypes(
|
|
1233
|
+
allTypes(): readonly NamedType[] {
|
|
1234
|
+
return this.builtInTypes().concat(this.types());
|
|
1198
1235
|
}
|
|
1199
1236
|
|
|
1200
1237
|
/**
|
|
@@ -1492,11 +1529,11 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
|
|
|
1492
1529
|
}
|
|
1493
1530
|
|
|
1494
1531
|
applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
|
|
1495
|
-
|
|
1532
|
+
nameOrDef: DirectiveDefinition<TApplicationArgs> | string,
|
|
1496
1533
|
args?: TApplicationArgs,
|
|
1497
1534
|
asFirstDirective: boolean = false,
|
|
1498
1535
|
): Directive<SchemaDefinition, TApplicationArgs> {
|
|
1499
|
-
const applied = super.applyDirective(
|
|
1536
|
+
const applied = super.applyDirective(nameOrDef, args, asFirstDirective) as Directive<SchemaDefinition, TApplicationArgs>;
|
|
1500
1537
|
const schema = this.schema();
|
|
1501
1538
|
const coreFeatures = schema.coreFeatures;
|
|
1502
1539
|
if (isCoreSpecDirectiveApplication(applied)) {
|
|
@@ -2021,7 +2058,12 @@ export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
|
|
|
2021
2058
|
protected readonly _values: EnumValue[] = [];
|
|
2022
2059
|
|
|
2023
2060
|
get values(): readonly EnumValue[] {
|
|
2024
|
-
|
|
2061
|
+
// Because our abstractions are mutable, and removal is done by calling
|
|
2062
|
+
// `remove()` on the element to remove, it's not unlikely someone mauy
|
|
2063
|
+
// try to iterate on the result of this method and call `remove()` on
|
|
2064
|
+
// some of the return value based on some condition. But this will break
|
|
2065
|
+
// in an error-prone way if we don't copy, so we do.
|
|
2066
|
+
return Array.from(this._values);
|
|
2025
2067
|
}
|
|
2026
2068
|
|
|
2027
2069
|
value(name: string): EnumValue | undefined {
|
|
@@ -2320,23 +2362,36 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
|
|
|
2320
2362
|
/**
|
|
2321
2363
|
* Removes this field definition from its parent type.
|
|
2322
2364
|
*
|
|
2323
|
-
* After calling this method, this field definition will be "detached": it
|
|
2324
|
-
* arguments or directives.
|
|
2365
|
+
* After calling this method, this field definition will be "detached": it will have no parent, schema, type,
|
|
2366
|
+
* arguments, or directives.
|
|
2325
2367
|
*/
|
|
2326
2368
|
remove(): never[] {
|
|
2327
2369
|
if (!this._parent) {
|
|
2328
2370
|
return [];
|
|
2329
2371
|
}
|
|
2372
|
+
this.checkRemoval();
|
|
2330
2373
|
this.onModification();
|
|
2331
|
-
this.
|
|
2374
|
+
// Remove this field's children.
|
|
2375
|
+
this.sourceAST = undefined;
|
|
2332
2376
|
this.type = undefined;
|
|
2333
|
-
this.
|
|
2377
|
+
this.removeAppliedDirectives();
|
|
2334
2378
|
for (const arg of this.arguments()) {
|
|
2335
2379
|
arg.remove();
|
|
2336
2380
|
}
|
|
2381
|
+
// Note that we don't track field references outside of parents, so no
|
|
2382
|
+
// removal needed there.
|
|
2383
|
+
//
|
|
2384
|
+
// TODO: One could consider interface fields as references to implementing
|
|
2385
|
+
// object/interface fields, in the sense that removing an implementing
|
|
2386
|
+
// object/interface field breaks the validity of the implementing
|
|
2387
|
+
// interface field. Being aware that an object/interface field is being
|
|
2388
|
+
// referenced in such a way would be useful for understanding breakages
|
|
2389
|
+
// that need to be resolved as a consequence of removal.
|
|
2390
|
+
//
|
|
2391
|
+
// Remove this field from its parent object/interface type.
|
|
2337
2392
|
FieldBasedType.prototype['removeFieldInternal'].call(this._parent, this);
|
|
2338
2393
|
this._parent = undefined;
|
|
2339
|
-
|
|
2394
|
+
this._extension = undefined;
|
|
2340
2395
|
return [];
|
|
2341
2396
|
}
|
|
2342
2397
|
|
|
@@ -2399,20 +2454,38 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
|
|
|
2399
2454
|
}
|
|
2400
2455
|
|
|
2401
2456
|
/**
|
|
2402
|
-
* Removes this field definition from its parent type.
|
|
2457
|
+
* Removes this input field definition from its parent type.
|
|
2403
2458
|
*
|
|
2404
|
-
* After calling this method, this field definition will be "detached": it
|
|
2405
|
-
*
|
|
2459
|
+
* After calling this method, this input field definition will be "detached": it will have no parent, schema,
|
|
2460
|
+
* type, default value, or directives.
|
|
2406
2461
|
*/
|
|
2407
2462
|
remove(): never[] {
|
|
2408
2463
|
if (!this._parent) {
|
|
2409
2464
|
return [];
|
|
2410
2465
|
}
|
|
2466
|
+
this.checkRemoval();
|
|
2411
2467
|
this.onModification();
|
|
2468
|
+
// Remove this input field's children.
|
|
2469
|
+
this.sourceAST = undefined;
|
|
2470
|
+
this.type = undefined;
|
|
2471
|
+
this.defaultValue = undefined;
|
|
2472
|
+
this.removeAppliedDirectives();
|
|
2473
|
+
// Note that we don't track input field references outside of parents, so no
|
|
2474
|
+
// removal needed there.
|
|
2475
|
+
//
|
|
2476
|
+
// TODO: One could consider default values (in field arguments, input
|
|
2477
|
+
// fields, or directive definitions) as references to input fields they
|
|
2478
|
+
// use, in the sense that removing the input field breaks the validity of
|
|
2479
|
+
// the default value. Being aware that an input field is being referenced
|
|
2480
|
+
// in such a way would be useful for understanding breakages that need to
|
|
2481
|
+
// be resolved as a consequence of removal. (The reference is indirect
|
|
2482
|
+
// though, as input field usages are currently represented as strings
|
|
2483
|
+
// within GraphQL values).
|
|
2484
|
+
//
|
|
2485
|
+
// Remove this input field from its parent input object type.
|
|
2412
2486
|
InputObjectType.prototype['removeFieldInternal'].call(this._parent, this);
|
|
2413
2487
|
this._parent = undefined;
|
|
2414
|
-
this.
|
|
2415
|
-
// Fields have nothing that can reference them outside of their parents
|
|
2488
|
+
this._extension = undefined;
|
|
2416
2489
|
return [];
|
|
2417
2490
|
}
|
|
2418
2491
|
|
|
@@ -2457,22 +2530,39 @@ export class ArgumentDefinition<TParent extends FieldDefinition<any> | Directive
|
|
|
2457
2530
|
/**
|
|
2458
2531
|
* Removes this argument definition from its parent element (field or directive).
|
|
2459
2532
|
*
|
|
2460
|
-
* After calling this method, this argument definition will be "detached": it
|
|
2461
|
-
* default value or directives.
|
|
2533
|
+
* After calling this method, this argument definition will be "detached": it will have no parent, schema, type,
|
|
2534
|
+
* default value, or directives.
|
|
2462
2535
|
*/
|
|
2463
2536
|
remove(): never[] {
|
|
2464
2537
|
if (!this._parent) {
|
|
2465
2538
|
return [];
|
|
2466
2539
|
}
|
|
2540
|
+
this.checkRemoval();
|
|
2467
2541
|
this.onModification();
|
|
2542
|
+
// Remove this argument's children.
|
|
2543
|
+
this.sourceAST = undefined;
|
|
2544
|
+
this.type = undefined;
|
|
2545
|
+
this.defaultValue = undefined;
|
|
2546
|
+
this.removeAppliedDirectives();
|
|
2547
|
+
// Note that we don't track argument references outside of parents, so no
|
|
2548
|
+
// removal needed there.
|
|
2549
|
+
//
|
|
2550
|
+
// TODO: One could consider the arguments of directive applications as
|
|
2551
|
+
// references to the arguments of directive definitions, in the sense that
|
|
2552
|
+
// removing a directive definition argument can break the validity of the
|
|
2553
|
+
// directive application. Being aware that a directive definition argument
|
|
2554
|
+
// is being referenced in such a way would be useful for understanding
|
|
2555
|
+
// breakages that need to be resolved as a consequence of removal. (You
|
|
2556
|
+
// could make a similar claim about interface field arguments being
|
|
2557
|
+
// references to object field arguments.)
|
|
2558
|
+
//
|
|
2559
|
+
// Remove this argument from its parent field or directive definition.
|
|
2468
2560
|
if (this._parent instanceof FieldDefinition) {
|
|
2469
2561
|
FieldDefinition.prototype['removeArgumentInternal'].call(this._parent, this.name);
|
|
2470
2562
|
} else {
|
|
2471
2563
|
DirectiveDefinition.prototype['removeArgumentInternal'].call(this._parent, this.name);
|
|
2472
2564
|
}
|
|
2473
2565
|
this._parent = undefined;
|
|
2474
|
-
this.type = undefined;
|
|
2475
|
-
this.defaultValue = undefined;
|
|
2476
2566
|
return [];
|
|
2477
2567
|
}
|
|
2478
2568
|
|
|
@@ -2513,23 +2603,36 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
|
|
|
2513
2603
|
}
|
|
2514
2604
|
|
|
2515
2605
|
/**
|
|
2516
|
-
* Removes this
|
|
2606
|
+
* Removes this enum value definition from its parent type.
|
|
2517
2607
|
*
|
|
2518
|
-
* After calling this method, this
|
|
2519
|
-
* arguments or directives.
|
|
2608
|
+
* After calling this method, this enum value definition will be "detached": it will have no parent, schema, type,
|
|
2609
|
+
* arguments, or directives.
|
|
2520
2610
|
*/
|
|
2521
2611
|
remove(): never[] {
|
|
2522
2612
|
if (!this._parent) {
|
|
2523
2613
|
return [];
|
|
2524
2614
|
}
|
|
2615
|
+
this.checkRemoval();
|
|
2525
2616
|
this.onModification();
|
|
2617
|
+
// Remove this enum value's children.
|
|
2618
|
+
this.sourceAST = undefined;
|
|
2619
|
+
this.removeAppliedDirectives();
|
|
2620
|
+
// Note that we don't track enum value references outside of parents, so no
|
|
2621
|
+
// removal needed there.
|
|
2622
|
+
//
|
|
2623
|
+
// TODO: One could consider default values (in field arguments, input
|
|
2624
|
+
// fields, or directive definitions) as references to enum values they
|
|
2625
|
+
// use, in the sense that removing the enum value breaks the validity of
|
|
2626
|
+
// the default value. Being aware that an enum value is being referenced
|
|
2627
|
+
// in such a way would be useful for understanding breakages that need to
|
|
2628
|
+
// be resolved as a consequence of removal. (The reference is indirect
|
|
2629
|
+
// though, as enum value usages are currently represented as strings
|
|
2630
|
+
// within GraphQL values).
|
|
2631
|
+
//
|
|
2632
|
+
// Remove this enum value from its parent enum type.
|
|
2526
2633
|
EnumType.prototype['removeValueInternal'].call(this._parent, this);
|
|
2527
2634
|
this._parent = undefined;
|
|
2528
|
-
|
|
2529
|
-
// TODO: that's actually only semi-true if you include arguments, because default values in args and concrete directive applications can
|
|
2530
|
-
// indirectly refer to enum value. It's indirect though as we currently keep enum value as string in values. That said, it would
|
|
2531
|
-
// probably be really nice to be able to known if an enum value is used or not, rather then removing it and not knowing if we broke
|
|
2532
|
-
// something).
|
|
2635
|
+
this._extension = undefined;
|
|
2533
2636
|
return [];
|
|
2534
2637
|
}
|
|
2535
2638
|
|
|
@@ -2659,22 +2762,35 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
|
|
|
2659
2762
|
assert(false, `Directive definition ${this} can't reference other types (it's arguments can); shouldn't be asked to remove reference to ${type}`);
|
|
2660
2763
|
}
|
|
2661
2764
|
|
|
2765
|
+
/**
|
|
2766
|
+
* Removes this directive definition from its parent schema.
|
|
2767
|
+
*
|
|
2768
|
+
* After calling this method, this directive definition will be "detached": it will have no parent, schema, or
|
|
2769
|
+
* arguments.
|
|
2770
|
+
*/
|
|
2662
2771
|
remove(): Directive[] {
|
|
2663
2772
|
if (!this._parent) {
|
|
2664
2773
|
return [];
|
|
2665
2774
|
}
|
|
2775
|
+
this.checkRemoval();
|
|
2666
2776
|
this.onModification();
|
|
2667
|
-
|
|
2668
|
-
this.
|
|
2777
|
+
// Remove this directive definition's children.
|
|
2778
|
+
this.sourceAST = undefined;
|
|
2669
2779
|
assert(this._appliedDirectives.length === 0, "Directive definition should not have directive applied to it");
|
|
2670
2780
|
for (const arg of this.arguments()) {
|
|
2671
2781
|
arg.remove();
|
|
2672
2782
|
}
|
|
2673
|
-
//
|
|
2674
|
-
//
|
|
2675
|
-
//
|
|
2783
|
+
// Remove this directive definition's references.
|
|
2784
|
+
//
|
|
2785
|
+
// Note that while a directive application references its definition, it
|
|
2786
|
+
// doesn't store a link to that definition. Instead, we fetch the definition
|
|
2787
|
+
// from the schema when requested. So we don't have to do anything on the
|
|
2788
|
+
// referencers other than clear them (and return the pre-cleared set).
|
|
2676
2789
|
const toReturn = setValues(this._referencers);
|
|
2677
2790
|
this._referencers.clear();
|
|
2791
|
+
// Remove this directive definition from its parent schema.
|
|
2792
|
+
Schema.prototype['removeDirectiveInternal'].call(this._parent, this);
|
|
2793
|
+
this._parent = undefined;
|
|
2678
2794
|
return toReturn;
|
|
2679
2795
|
}
|
|
2680
2796
|
|
|
@@ -2840,10 +2956,12 @@ export class Directive<
|
|
|
2840
2956
|
if (!this._parent) {
|
|
2841
2957
|
return false;
|
|
2842
2958
|
}
|
|
2959
|
+
// Remove this directive application's reference to its definition.
|
|
2843
2960
|
const definition = this.definition;
|
|
2844
2961
|
if (definition && this.isAttachedToSchemaElement()) {
|
|
2845
2962
|
DirectiveDefinition.prototype['removeReferencer'].call(definition, this as Directive<SchemaElement<any, any>>);
|
|
2846
2963
|
}
|
|
2964
|
+
// Remove this directive application from its parent schema element.
|
|
2847
2965
|
const parentDirectives = this._parent.appliedDirectives as Directive<TParent>[];
|
|
2848
2966
|
const index = parentDirectives.indexOf(this);
|
|
2849
2967
|
assert(index >= 0, () => `Directive ${this} lists ${this._parent} as parent, but that parent doesn't list it as applied directive`);
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
DirectiveDefinition,
|
|
5
5
|
EnumType,
|
|
6
6
|
InputType,
|
|
7
|
+
isCustomScalarType,
|
|
7
8
|
isEnumType,
|
|
9
|
+
isListType,
|
|
8
10
|
isNonNullType,
|
|
9
11
|
isObjectType,
|
|
10
12
|
isUnionType,
|
|
@@ -310,7 +312,7 @@ function ensureSameArguments(
|
|
|
310
312
|
// is optional if you so wish.
|
|
311
313
|
actualType = actualType.ofType;
|
|
312
314
|
}
|
|
313
|
-
if (!sameType(type, actualType)) {
|
|
315
|
+
if (!sameType(type, actualType) && !isValidInputTypeRedefinition(type, actualType)) {
|
|
314
316
|
errors.push(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
315
317
|
message: `Invalid definition for ${what}: argument "${name}" should have type "${type}" but found type "${actualArgument.type!}"`,
|
|
316
318
|
nodes: actualArgument.sourceAST
|
|
@@ -334,3 +336,18 @@ function ensureSameArguments(
|
|
|
334
336
|
return errors;
|
|
335
337
|
}
|
|
336
338
|
|
|
339
|
+
function isValidInputTypeRedefinition(expectedType: InputType, actualType: InputType): boolean {
|
|
340
|
+
// If the expected type is a custom scalar, then we allow the redefinition to be another type (unless it's a custom scalar, in which
|
|
341
|
+
// case it has to be the same scalar). The rational being that since graphQL does no validation of values passed to a custom scalar,
|
|
342
|
+
// any code that gets some value as input for a custom scalar has to do validation manually, and so there is little harm in allowing
|
|
343
|
+
// a redefinition with another type since any truly invalid value would failed that "manual validation". In practice, this leeway
|
|
344
|
+
// make sense because many scalar will tend to accept only one kind of values (say, strings) and exists only to inform that said string
|
|
345
|
+
// needs to follow a specific format, and in such case, letting user redefine the type as String adds flexibility while doing little harm.
|
|
346
|
+
if (isListType(expectedType)) {
|
|
347
|
+
return isListType(actualType) && isValidInputTypeRedefinition(expectedType.ofType, actualType.ofType);
|
|
348
|
+
}
|
|
349
|
+
if (isNonNullType(expectedType)) {
|
|
350
|
+
return isNonNullType(actualType) && isValidInputTypeRedefinition(expectedType.ofType, actualType.ofType);
|
|
351
|
+
}
|
|
352
|
+
return isCustomScalarType(expectedType) && !isCustomScalarType(actualType);
|
|
353
|
+
}
|
package/src/error.ts
CHANGED
|
@@ -346,12 +346,62 @@ const LINK_IMPORT_NAME_MISMATCH = makeCodeDefinition(
|
|
|
346
346
|
|
|
347
347
|
const REFERENCED_INACCESSIBLE = makeCodeDefinition(
|
|
348
348
|
'REFERENCED_INACCESSIBLE',
|
|
349
|
-
'An element is marked as @inaccessible but is referenced by
|
|
349
|
+
'An element is marked as @inaccessible but is referenced by an element visible in the API schema.'
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const DEFAULT_VALUE_USES_INACCESSIBLE = makeCodeDefinition(
|
|
353
|
+
'DEFAULT_VALUE_USES_INACCESSIBLE',
|
|
354
|
+
'An element is marked as @inaccessible but is used in the default value of an element visible in the API schema.'
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const QUERY_ROOT_TYPE_INACCESSIBLE = makeCodeDefinition(
|
|
358
|
+
'QUERY_ROOT_TYPE_INACCESSIBLE',
|
|
359
|
+
'An element is marked as @inaccessible but is the query root type, which must be visible in the API schema.'
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const REQUIRED_INACCESSIBLE = makeCodeDefinition(
|
|
363
|
+
'REQUIRED_INACCESSIBLE',
|
|
364
|
+
'An element is marked as @inaccessible but is required by an element visible in the API schema.'
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const IMPLEMENTED_BY_INACCESSIBLE = makeCodeDefinition(
|
|
368
|
+
'IMPLEMENTED_BY_INACCESSIBLE',
|
|
369
|
+
'An element is marked as @inaccessible but implements an element visible in the API schema.'
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
const DISALLOWED_INACCESSIBLE = makeCodeDefinition(
|
|
373
|
+
'DISALLOWED_INACCESSIBLE',
|
|
374
|
+
'An element is marked as @inaccessible that is not allowed to be @inaccessible.'
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
const ONLY_INACCESSIBLE_CHILDREN = makeCodeDefinition(
|
|
378
|
+
'ONLY_INACCESSIBLE_CHILDREN',
|
|
379
|
+
'A type visible in the API schema has only @inaccessible children.'
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
const REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH = makeCodeDefinition(
|
|
383
|
+
'REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH',
|
|
384
|
+
'A field of an input object type is mandatory in some subgraphs, but the field is not defined in all the subgraphs that define the input object type.'
|
|
350
385
|
);
|
|
351
386
|
|
|
352
387
|
const REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH = makeCodeDefinition(
|
|
353
388
|
'REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH',
|
|
354
|
-
'An argument of a field or directive definition is mandatory in some subgraphs, but the argument is not defined in all subgraphs that define the field or directive definition.'
|
|
389
|
+
'An argument of a field or directive definition is mandatory in some subgraphs, but the argument is not defined in all the subgraphs that define the field or directive definition.'
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const EMPTY_MERGED_INPUT_TYPE = makeCodeDefinition(
|
|
393
|
+
'EMPTY_MERGED_INPUT_TYPE',
|
|
394
|
+
'An input object type has no field common to all the subgraphs that define the type. Merging that type would result in an invalid empty input object type.'
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const ENUM_VALUE_MISMATCH = makeCodeDefinition(
|
|
398
|
+
'ENUM_VALUE_MISMATCH',
|
|
399
|
+
'An enum type that is used as both an input and output type has a value that is not defined in all the subgraphs that define the enum type.'
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
const EMPTY_MERGED_ENUM_TYPE = makeCodeDefinition(
|
|
403
|
+
'EMPTY_MERGED_ENUM_TYPE',
|
|
404
|
+
'An enum type has no value common to all the subgraphs that define the type. Merging that type would result in an invalid empty enum type.'
|
|
355
405
|
);
|
|
356
406
|
|
|
357
407
|
const SATISFIABILITY_ERROR = makeCodeDefinition(
|
|
@@ -431,7 +481,17 @@ export const ERRORS = {
|
|
|
431
481
|
INVALID_LINK_DIRECTIVE_USAGE,
|
|
432
482
|
LINK_IMPORT_NAME_MISMATCH,
|
|
433
483
|
REFERENCED_INACCESSIBLE,
|
|
484
|
+
DEFAULT_VALUE_USES_INACCESSIBLE,
|
|
485
|
+
QUERY_ROOT_TYPE_INACCESSIBLE,
|
|
486
|
+
REQUIRED_INACCESSIBLE,
|
|
487
|
+
DISALLOWED_INACCESSIBLE,
|
|
488
|
+
IMPLEMENTED_BY_INACCESSIBLE,
|
|
489
|
+
ONLY_INACCESSIBLE_CHILDREN,
|
|
434
490
|
REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH,
|
|
491
|
+
REQUIRED_INPUT_FIELD_MISSING_IN_SOME_SUBGRAPH,
|
|
492
|
+
EMPTY_MERGED_INPUT_TYPE,
|
|
493
|
+
ENUM_VALUE_MISMATCH,
|
|
494
|
+
EMPTY_MERGED_ENUM_TYPE,
|
|
435
495
|
SATISFIABILITY_ERROR,
|
|
436
496
|
OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE,
|
|
437
497
|
OVERRIDE_FROM_SELF_ERROR,
|
|
@@ -268,7 +268,7 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
268
268
|
// truly defined, so we've so far added them everywhere with all their fields, but some fields may have been part
|
|
269
269
|
// of an extension and be only in a few subgraphs), we remove the field or the subgraph would be invalid.
|
|
270
270
|
for (const subgraph of subgraphs) {
|
|
271
|
-
for (const itf of subgraph.schema.
|
|
271
|
+
for (const itf of subgraph.schema.interfaceTypes()) {
|
|
272
272
|
// We only look at objects because interfaces are handled by this own loop in practice.
|
|
273
273
|
const implementations = itf.possibleRuntimeTypes();
|
|
274
274
|
for (const field of itf.fields()) {
|