@apollo/federation-internals 2.4.0-alpha.1 → 2.4.1
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 +41 -0
- package/dist/coreSpec.js +1 -1
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +15 -15
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +35 -55
- package/dist/definitions.js.map +1 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +19 -18
- package/dist/federation.js.map +1 -1
- package/dist/operations.d.ts +222 -88
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +934 -605
- package/dist/operations.js.map +1 -1
- package/dist/precompute.d.ts.map +1 -1
- package/dist/precompute.js +13 -10
- package/dist/precompute.js.map +1 -1
- package/dist/values.d.ts +3 -3
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +22 -28
- package/dist/values.js.map +1 -1
- package/package.json +4 -2
- package/src/__tests__/operations.test.ts +727 -145
- package/src/__tests__/schemaUpgrader.test.ts +1 -1
- package/src/definitions.ts +53 -57
- package/src/federation.ts +27 -23
- package/src/operations.ts +1370 -855
- package/src/precompute.ts +18 -12
- package/src/values.ts +24 -30
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -212,7 +212,7 @@ test('reject @interfaceObject usage if not all subgraphs are fed2', () => {
|
|
|
212
212
|
|
|
213
213
|
const s1 = `
|
|
214
214
|
extend schema
|
|
215
|
-
@link(url: "https://specs.apollo.dev/federation/v2.
|
|
215
|
+
@link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key", "@interfaceObject"])
|
|
216
216
|
|
|
217
217
|
type Query {
|
|
218
218
|
a: A
|
package/src/definitions.ts
CHANGED
|
@@ -32,7 +32,16 @@ import {
|
|
|
32
32
|
removeAllCoreFeatures,
|
|
33
33
|
} from "./coreSpec";
|
|
34
34
|
import { assert, mapValues, MapWithCachedArrays, removeArrayElement } from "./utils";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
withDefaultValues,
|
|
37
|
+
valueEquals,
|
|
38
|
+
valueToString,
|
|
39
|
+
valueToAST,
|
|
40
|
+
valueFromAST,
|
|
41
|
+
valueNodeToConstValueNode,
|
|
42
|
+
argumentsEquals,
|
|
43
|
+
collectVariablesInValue
|
|
44
|
+
} from "./values";
|
|
36
45
|
import { removeInaccessibleElements } from "./inaccessibleSpec";
|
|
37
46
|
import { printDirectiveDefinition, printSchema } from './print';
|
|
38
47
|
import { sameType } from './types';
|
|
@@ -343,54 +352,39 @@ export interface Named {
|
|
|
343
352
|
export type ExtendableElement = SchemaDefinition | NamedType;
|
|
344
353
|
|
|
345
354
|
export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
346
|
-
|
|
355
|
+
readonly appliedDirectives: Directive<T>[];
|
|
347
356
|
|
|
348
|
-
constructor(
|
|
357
|
+
constructor(
|
|
358
|
+
private readonly _schema: Schema,
|
|
359
|
+
directives: readonly Directive<any>[] = [],
|
|
360
|
+
) {
|
|
361
|
+
this.appliedDirectives = directives.map((d) => this.attachDirective(d));
|
|
362
|
+
}
|
|
349
363
|
|
|
350
364
|
schema(): Schema {
|
|
351
365
|
return this._schema;
|
|
352
366
|
}
|
|
353
367
|
|
|
368
|
+
private attachDirective(directive: Directive<any>): Directive<T> {
|
|
369
|
+
// if the directive is not attached, we can assume we're fine just attaching it to use. Otherwise, we're "copying" it.
|
|
370
|
+
const toAdd = directive.isAttached()
|
|
371
|
+
? new Directive(directive.name, directive.arguments())
|
|
372
|
+
: directive;
|
|
373
|
+
|
|
374
|
+
Element.prototype['setParent'].call(toAdd, this);
|
|
375
|
+
return toAdd;
|
|
376
|
+
}
|
|
377
|
+
|
|
354
378
|
appliedDirectivesOf<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(nameOrDefinition: string | DirectiveDefinition<TApplicationArgs>): Directive<T, TApplicationArgs>[] {
|
|
355
379
|
const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition.name;
|
|
356
380
|
return this.appliedDirectives.filter(d => d.name == directiveName) as Directive<T, TApplicationArgs>[];
|
|
357
381
|
}
|
|
358
382
|
|
|
359
|
-
get appliedDirectives(): readonly Directive<T>[] {
|
|
360
|
-
return this._appliedDirectives ?? [];
|
|
361
|
-
}
|
|
362
|
-
|
|
363
383
|
hasAppliedDirective(nameOrDefinition: string | DirectiveDefinition): boolean {
|
|
364
384
|
const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition.name;
|
|
365
385
|
return this.appliedDirectives.some(d => d.name == directiveName);
|
|
366
386
|
}
|
|
367
387
|
|
|
368
|
-
applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
|
|
369
|
-
defOrDirective: Directive<T, TApplicationArgs> | DirectiveDefinition<TApplicationArgs>,
|
|
370
|
-
args?: TApplicationArgs
|
|
371
|
-
): Directive<T, TApplicationArgs> {
|
|
372
|
-
let toAdd: Directive<T, TApplicationArgs>;
|
|
373
|
-
if (defOrDirective instanceof Directive) {
|
|
374
|
-
if (defOrDirective.schema() != this.schema()) {
|
|
375
|
-
throw new Error(`Cannot add directive ${defOrDirective} to ${this} as it is attached to another schema`);
|
|
376
|
-
}
|
|
377
|
-
toAdd = defOrDirective;
|
|
378
|
-
if (args) {
|
|
379
|
-
toAdd.setArguments(args);
|
|
380
|
-
}
|
|
381
|
-
} else {
|
|
382
|
-
toAdd = new Directive<T, TApplicationArgs>(defOrDirective.name, args ?? Object.create(null));
|
|
383
|
-
}
|
|
384
|
-
Element.prototype['setParent'].call(toAdd, this);
|
|
385
|
-
// TODO: we should typecheck arguments or our TApplicationArgs business is just a lie.
|
|
386
|
-
if (this._appliedDirectives) {
|
|
387
|
-
this._appliedDirectives.push(toAdd);
|
|
388
|
-
} else {
|
|
389
|
-
this._appliedDirectives = [ toAdd ];
|
|
390
|
-
}
|
|
391
|
-
return toAdd;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
388
|
appliedDirectivesToDirectiveNodes() : ConstDirectiveNode[] | undefined {
|
|
395
389
|
if (this.appliedDirectives.length == 0) {
|
|
396
390
|
return undefined;
|
|
@@ -414,8 +408,10 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
|
|
|
414
408
|
: ' ' + this.appliedDirectives.join(' ');
|
|
415
409
|
}
|
|
416
410
|
|
|
417
|
-
|
|
418
|
-
|
|
411
|
+
collectVariablesInAppliedDirectives(collector: VariableCollector) {
|
|
412
|
+
for (const applied of this.appliedDirectives) {
|
|
413
|
+
collector.collectInArguments(applied.arguments());
|
|
414
|
+
}
|
|
419
415
|
}
|
|
420
416
|
}
|
|
421
417
|
|
|
@@ -3069,7 +3065,7 @@ export class Directive<
|
|
|
3069
3065
|
// applied to a field that is part of an extension, the field will have its extension set, but not the underlying directive.
|
|
3070
3066
|
private _extension?: Extension<any>;
|
|
3071
3067
|
|
|
3072
|
-
constructor(readonly name: string, private _args: TArgs) {
|
|
3068
|
+
constructor(readonly name: string, private _args: TArgs = Object.create(null)) {
|
|
3073
3069
|
super();
|
|
3074
3070
|
}
|
|
3075
3071
|
|
|
@@ -3314,38 +3310,38 @@ export class Variable {
|
|
|
3314
3310
|
|
|
3315
3311
|
export type Variables = readonly Variable[];
|
|
3316
3312
|
|
|
3317
|
-
export
|
|
3318
|
-
|
|
3319
|
-
|
|
3313
|
+
export class VariableCollector {
|
|
3314
|
+
private readonly _variables = new Map<string, Variable>();
|
|
3315
|
+
|
|
3316
|
+
add(variable: Variable) {
|
|
3317
|
+
this._variables.set(variable.name, variable);
|
|
3320
3318
|
}
|
|
3321
|
-
|
|
3322
|
-
|
|
3319
|
+
|
|
3320
|
+
addAll(variables: Variables) {
|
|
3321
|
+
for (const variable of variables) {
|
|
3322
|
+
this.add(variable);
|
|
3323
|
+
}
|
|
3323
3324
|
}
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3325
|
+
|
|
3326
|
+
collectInArguments(args: {[key: string]: any}) {
|
|
3327
|
+
for (const value of Object.values(args)) {
|
|
3328
|
+
collectVariablesInValue(value, this);
|
|
3328
3329
|
}
|
|
3329
3330
|
}
|
|
3330
|
-
return res;
|
|
3331
|
-
}
|
|
3332
3331
|
|
|
3333
|
-
|
|
3334
|
-
|
|
3332
|
+
variables() {
|
|
3333
|
+
return mapValues(this._variables);
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
toString(): string {
|
|
3337
|
+
return this.variables().toString();
|
|
3338
|
+
}
|
|
3335
3339
|
}
|
|
3336
3340
|
|
|
3337
3341
|
export function isVariable(v: any): v is Variable {
|
|
3338
3342
|
return v instanceof Variable;
|
|
3339
3343
|
}
|
|
3340
3344
|
|
|
3341
|
-
export function variablesInArguments(args: {[key: string]: any}): Variables {
|
|
3342
|
-
let variables: Variables = [];
|
|
3343
|
-
for (const value of Object.values(args)) {
|
|
3344
|
-
variables = mergeVariables(variables, variablesInValue(value));
|
|
3345
|
-
}
|
|
3346
|
-
return variables;
|
|
3347
|
-
}
|
|
3348
|
-
|
|
3349
3345
|
export class VariableDefinition extends DirectiveTargetElement<VariableDefinition> {
|
|
3350
3346
|
constructor(
|
|
3351
3347
|
schema: Schema,
|
package/src/federation.ts
CHANGED
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
} from "graphql";
|
|
49
49
|
import { KnownTypeNamesInFederationRule } from "./validation/KnownTypeNamesInFederationRule";
|
|
50
50
|
import { buildSchema, buildSchemaFromAST } from "./buildSchema";
|
|
51
|
-
import { parseSelectionSet,
|
|
51
|
+
import { parseSelectionSet, SelectionSet } from './operations';
|
|
52
52
|
import { TAG_VERSIONS } from "./tagSpec";
|
|
53
53
|
import {
|
|
54
54
|
errorCodeDef,
|
|
@@ -129,7 +129,7 @@ function validateFieldSetSelections({
|
|
|
129
129
|
allowFieldsWithArguments: boolean,
|
|
130
130
|
}): void {
|
|
131
131
|
for (const selection of selectionSet.selections()) {
|
|
132
|
-
const appliedDirectives = selection.element
|
|
132
|
+
const appliedDirectives = selection.element.appliedDirectives;
|
|
133
133
|
if (appliedDirectives.length > 0) {
|
|
134
134
|
onError(ERROR_CATEGORIES.DIRECTIVE_IN_FIELDS_ARG.get(directiveName).err(
|
|
135
135
|
`cannot have directive applications in the @${directiveName}(fields:) argument but found ${appliedDirectives.join(', ')}.`,
|
|
@@ -137,7 +137,7 @@ function validateFieldSetSelections({
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
if (selection.kind === 'FieldSelection') {
|
|
140
|
-
const field = selection.element
|
|
140
|
+
const field = selection.element.definition;
|
|
141
141
|
const isExternal = metadata.isFieldExternal(field);
|
|
142
142
|
if (!allowFieldsWithArguments && field.hasArguments()) {
|
|
143
143
|
onError(ERROR_CATEGORIES.FIELDS_HAS_ARGS.get(directiveName).err(
|
|
@@ -1817,12 +1817,17 @@ class ExternalTester {
|
|
|
1817
1817
|
private collectProvidedFields() {
|
|
1818
1818
|
for (const provides of this.metadata().providesDirective().applications()) {
|
|
1819
1819
|
const parent = provides.parent as FieldDefinition<CompositeType>;
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1820
|
+
const parentType = baseType(parent.type!);
|
|
1821
|
+
// If `parentType` is not a composite, that means an invalid @provides, but we ignore such errors
|
|
1822
|
+
// for now (also why we pass 'validate: false'). Proper errors will be thrown later during validation.
|
|
1823
|
+
if (isCompositeType(parentType)) {
|
|
1824
|
+
collectTargetFields({
|
|
1825
|
+
parentType,
|
|
1826
|
+
directive: provides as Directive<any, {fields: any}>,
|
|
1827
|
+
includeInterfaceFieldsImplementations: true,
|
|
1828
|
+
validate: false,
|
|
1829
|
+
}).forEach((f) => this.providedFields.add(f.coordinate));
|
|
1830
|
+
}
|
|
1826
1831
|
}
|
|
1827
1832
|
}
|
|
1828
1833
|
|
|
@@ -1854,7 +1859,7 @@ class ExternalTester {
|
|
|
1854
1859
|
|
|
1855
1860
|
selectsAnyExternalField(selectionSet: SelectionSet): boolean {
|
|
1856
1861
|
for (const selection of selectionSet.selections()) {
|
|
1857
|
-
if (selection.kind === 'FieldSelection' && this.isExternal(selection.element
|
|
1862
|
+
if (selection.kind === 'FieldSelection' && this.isExternal(selection.element.definition)) {
|
|
1858
1863
|
return true;
|
|
1859
1864
|
}
|
|
1860
1865
|
if (selection.selectionSet) {
|
|
@@ -1966,7 +1971,7 @@ function selectsNonExternalLeafField(selection: SelectionSet): boolean {
|
|
|
1966
1971
|
return selection.selections().some(s => {
|
|
1967
1972
|
if (s.kind === 'FieldSelection') {
|
|
1968
1973
|
// If it's external, we're good and don't need to recurse.
|
|
1969
|
-
if (isExternalOrHasExternalImplementations(s.
|
|
1974
|
+
if (isExternalOrHasExternalImplementations(s.element.definition)) {
|
|
1970
1975
|
return false;
|
|
1971
1976
|
}
|
|
1972
1977
|
// Otherwise, we select a non-external if it's a leaf, or the sub-selection does.
|
|
@@ -1978,25 +1983,24 @@ function selectsNonExternalLeafField(selection: SelectionSet): boolean {
|
|
|
1978
1983
|
}
|
|
1979
1984
|
|
|
1980
1985
|
function withoutNonExternalLeafFields(selectionSet: SelectionSet): SelectionSet {
|
|
1981
|
-
|
|
1982
|
-
for (const selection of selectionSet.selections()) {
|
|
1986
|
+
return selectionSet.lazyMap((selection) => {
|
|
1983
1987
|
if (selection.kind === 'FieldSelection') {
|
|
1984
|
-
if (isExternalOrHasExternalImplementations(selection.
|
|
1988
|
+
if (isExternalOrHasExternalImplementations(selection.element.definition)) {
|
|
1985
1989
|
// That field is external, so we can add the selection back entirely.
|
|
1986
|
-
|
|
1987
|
-
continue;
|
|
1990
|
+
return selection;
|
|
1988
1991
|
}
|
|
1989
1992
|
}
|
|
1990
|
-
// Note that for fragments will always be true (and we just recurse), while
|
|
1991
|
-
// for fields, we'll only get here if the field is not external, and so
|
|
1992
|
-
// we want to add the selection only if it's not a leaf and even then, only
|
|
1993
|
-
// the part where we've recursed.
|
|
1994
1993
|
if (selection.selectionSet) {
|
|
1994
|
+
// Note that for fragments this will always be true (and we just recurse), while
|
|
1995
|
+
// for fields, we'll only get here if the field is not external, and so
|
|
1996
|
+
// we want to add the selection only if it's not a leaf and even then, only
|
|
1997
|
+
// the part where we've recursed.
|
|
1995
1998
|
const updated = withoutNonExternalLeafFields(selection.selectionSet);
|
|
1996
1999
|
if (!updated.isEmpty()) {
|
|
1997
|
-
|
|
2000
|
+
return selection.withUpdatedSelectionSet(updated);
|
|
1998
2001
|
}
|
|
1999
2002
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2003
|
+
// We skip that selection.
|
|
2004
|
+
return undefined;
|
|
2005
|
+
});
|
|
2002
2006
|
}
|