@apollo/federation-internals 2.4.1 → 2.4.3

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/argumentCompositionStrategies.d.ts +34 -0
  3. package/dist/argumentCompositionStrategies.d.ts.map +1 -0
  4. package/dist/argumentCompositionStrategies.js +35 -0
  5. package/dist/argumentCompositionStrategies.js.map +1 -0
  6. package/dist/coreSpec.d.ts +12 -3
  7. package/dist/coreSpec.d.ts.map +1 -1
  8. package/dist/coreSpec.js +68 -17
  9. package/dist/coreSpec.js.map +1 -1
  10. package/dist/definitions.d.ts +1 -0
  11. package/dist/definitions.d.ts.map +1 -1
  12. package/dist/definitions.js +30 -27
  13. package/dist/definitions.js.map +1 -1
  14. package/dist/directiveAndTypeSpecification.d.ts +26 -7
  15. package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
  16. package/dist/directiveAndTypeSpecification.js +56 -4
  17. package/dist/directiveAndTypeSpecification.js.map +1 -1
  18. package/dist/federation.d.ts.map +1 -1
  19. package/dist/federation.js +24 -2
  20. package/dist/federation.js.map +1 -1
  21. package/dist/federationSpec.d.ts +2 -13
  22. package/dist/federationSpec.d.ts.map +1 -1
  23. package/dist/federationSpec.js +10 -60
  24. package/dist/federationSpec.js.map +1 -1
  25. package/dist/inaccessibleSpec.d.ts +0 -2
  26. package/dist/inaccessibleSpec.d.ts.map +1 -1
  27. package/dist/inaccessibleSpec.js +3 -6
  28. package/dist/inaccessibleSpec.js.map +1 -1
  29. package/dist/index.d.ts +3 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +5 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/knownCoreFeatures.d.ts +1 -0
  34. package/dist/knownCoreFeatures.d.ts.map +1 -1
  35. package/dist/knownCoreFeatures.js +5 -1
  36. package/dist/knownCoreFeatures.js.map +1 -1
  37. package/dist/operations.d.ts +18 -6
  38. package/dist/operations.d.ts.map +1 -1
  39. package/dist/operations.js +102 -37
  40. package/dist/operations.js.map +1 -1
  41. package/dist/print.d.ts +7 -1
  42. package/dist/print.d.ts.map +1 -1
  43. package/dist/print.js +33 -5
  44. package/dist/print.js.map +1 -1
  45. package/dist/tagSpec.d.ts +0 -2
  46. package/dist/tagSpec.d.ts.map +1 -1
  47. package/dist/tagSpec.js +4 -10
  48. package/dist/tagSpec.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/__tests__/directiveAndTypeSpecifications.test.ts +41 -0
  51. package/src/__tests__/operations.test.ts +175 -10
  52. package/src/argumentCompositionStrategies.ts +39 -0
  53. package/src/coreSpec.ts +94 -34
  54. package/src/definitions.ts +35 -29
  55. package/src/directiveAndTypeSpecification.ts +101 -14
  56. package/src/federation.ts +33 -4
  57. package/src/federationSpec.ts +13 -73
  58. package/src/inaccessibleSpec.ts +4 -11
  59. package/src/index.ts +3 -0
  60. package/src/knownCoreFeatures.ts +9 -0
  61. package/src/operations.ts +198 -40
  62. package/src/print.ts +39 -4
  63. package/src/tagSpec.ts +4 -12
  64. package/tsconfig.tsbuildinfo +1 -1
package/dist/tagSpec.js CHANGED
@@ -31,11 +31,11 @@ class TagSpecDefinition extends coreSpec_1.FeatureDefinition {
31
31
  name: 'tag',
32
32
  locations: this.tagLocations,
33
33
  repeatable: true,
34
- argumentFct: (schema) => ({
35
- args: [{ name: 'name', type: new definitions_1.NonNullType(schema.stringType()) }],
36
- errors: [],
37
- }),
34
+ args: [{ name: 'name', type: (schema) => new definitions_1.NonNullType(schema.stringType()) }],
35
+ composes: true,
36
+ supergraphSpecification: () => exports.TAG_VERSIONS.latest(),
38
37
  });
38
+ this.registerDirective(this.tagDirectiveSpec);
39
39
  }
40
40
  isV01() {
41
41
  return this.version.equals(new coreSpec_1.FeatureVersion(0, 1));
@@ -43,9 +43,6 @@ class TagSpecDefinition extends coreSpec_1.FeatureDefinition {
43
43
  isV02() {
44
44
  return this.version.equals(new coreSpec_1.FeatureVersion(0, 2));
45
45
  }
46
- addElementsToSchema(schema) {
47
- return this.addDirectiveSpec(schema, this.tagDirectiveSpec);
48
- }
49
46
  tagDirective(schema) {
50
47
  return this.directive(schema, 'tag');
51
48
  }
@@ -59,9 +56,6 @@ class TagSpecDefinition extends coreSpec_1.FeatureDefinition {
59
56
  }
60
57
  return undefined;
61
58
  }
62
- allElementNames() {
63
- return ["@tag"];
64
- }
65
59
  }
66
60
  exports.TagSpecDefinition = TagSpecDefinition;
67
61
  exports.TAG_VERSIONS = new coreSpec_1.FeatureDefinitions(exports.tagIdentity)
@@ -1 +1 @@
1
- {"version":3,"file":"tagSpec.js","sourceRoot":"","sources":["../src/tagSpec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tagSpec.js","sourceRoot":"","sources":["../src/tagSpec.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo/federation-internals",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "description": "Apollo Federation internal utilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,41 @@
1
+ import { DirectiveLocation } from "graphql";
2
+ import "../definitions";
3
+ import { createDirectiveSpecification } from "../directiveAndTypeSpecification";
4
+ import { ARGUMENT_COMPOSITION_STRATEGIES } from "../argumentCompositionStrategies";
5
+ import { TAG_VERSIONS } from "../tagSpec";
6
+
7
+ const supergraphSpecification = () => TAG_VERSIONS.latest();
8
+
9
+ test('must have supergraph link if composed', () => {
10
+ expect(() => createDirectiveSpecification({
11
+ name: 'foo',
12
+ locations: [DirectiveLocation.OBJECT],
13
+ composes: true,
14
+ })).toThrow('Should provide a @link specification to use in supergraph for directive @foo if it composes');
15
+ });
16
+
17
+ test('must have a merge strategy on all arguments if any', () => {
18
+ expect(() => createDirectiveSpecification({
19
+ name: 'foo',
20
+ locations: [DirectiveLocation.OBJECT],
21
+ composes: true,
22
+ supergraphSpecification,
23
+ args: [
24
+ { name: "v1", type: (schema) => schema.intType(), compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.MAX },
25
+ { name: "v2", type: (schema) => schema.intType() }
26
+ ],
27
+ })).toThrow('Invalid directive specification for @foo: not all arguments define a composition strategy');
28
+ });
29
+
30
+ test('must be not be repeatable if it has a merge strategy', () => {
31
+ expect(() => createDirectiveSpecification({
32
+ name: 'foo',
33
+ locations: [DirectiveLocation.OBJECT],
34
+ composes: true,
35
+ repeatable: true,
36
+ supergraphSpecification,
37
+ args: [
38
+ { name: "v", type: (schema) => schema.intType(), compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.MAX },
39
+ ],
40
+ })).toThrow('Invalid directive specification for @foo: @foo is repeatable and should not define composition strategy for its arguments');
41
+ });
@@ -154,8 +154,6 @@ describe('fragments optimization', () => {
154
154
  `);
155
155
 
156
156
  const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
157
- // Note that while we didn't use `onU` for `t` in the query, it's technically ok to use
158
- // it and it makes the query smaller, so it gets used.
159
157
  expect(optimized.toString()).toMatchString(`
160
158
  fragment OnT1 on T1 {
161
159
  a
@@ -171,17 +169,15 @@ describe('fragments optimization', () => {
171
169
  b
172
170
  }
173
171
 
174
- fragment OnU on U {
175
- ...OnI
176
- ...OnT1
177
- ...OnT2
178
- }
179
-
180
172
  {
181
173
  t {
182
- ...OnU
174
+ ...OnI
175
+ ...OnT1
176
+ ...OnT2
183
177
  u {
184
- ...OnU
178
+ ...OnI
179
+ ...OnT1
180
+ ...OnT2
185
181
  }
186
182
  }
187
183
  }
@@ -579,6 +575,175 @@ describe('fragments optimization', () => {
579
575
  });
580
576
  });
581
577
 
578
+ test('handles fragment matching at the top level of another fragment', () => {
579
+ const schema = parseSchema(`
580
+ type Query {
581
+ t: T
582
+ }
583
+
584
+ type T {
585
+ a: String
586
+ u: U
587
+ }
588
+
589
+ type U {
590
+ x: String
591
+ y: String
592
+ }
593
+ `);
594
+
595
+ testFragmentsRoundtrip({
596
+ schema,
597
+ query: `
598
+ fragment Frag1 on T {
599
+ a
600
+ }
601
+
602
+ fragment Frag2 on T {
603
+ u {
604
+ x
605
+ y
606
+ }
607
+ ...Frag1
608
+ }
609
+
610
+ fragment Frag3 on Query {
611
+ t {
612
+ ...Frag2
613
+ }
614
+ }
615
+
616
+ {
617
+ ...Frag3
618
+ }
619
+ `,
620
+ expanded: `
621
+ {
622
+ t {
623
+ u {
624
+ x
625
+ y
626
+ }
627
+ a
628
+ }
629
+ }
630
+ `,
631
+ });
632
+ });
633
+
634
+ test('handles fragments used in a context where they get trimmed', () => {
635
+ const schema = parseSchema(`
636
+ type Query {
637
+ t1: T1
638
+ }
639
+
640
+ interface I {
641
+ x: Int
642
+ }
643
+
644
+ type T1 implements I {
645
+ x: Int
646
+ y: Int
647
+ }
648
+
649
+ type T2 implements I {
650
+ x: Int
651
+ z: Int
652
+ }
653
+ `);
654
+
655
+ testFragmentsRoundtrip({
656
+ schema,
657
+ query: `
658
+ fragment FragOnI on I {
659
+ ... on T1 {
660
+ y
661
+ }
662
+ ... on T2 {
663
+ z
664
+ }
665
+ }
666
+
667
+ {
668
+ t1 {
669
+ ...FragOnI
670
+ }
671
+ }
672
+ `,
673
+ expanded: `
674
+ {
675
+ t1 {
676
+ y
677
+ }
678
+ }
679
+ `,
680
+ });
681
+ });
682
+
683
+ test('handles fragments used in the context of non-intersecting abstract types', () => {
684
+ const schema = parseSchema(`
685
+ type Query {
686
+ i2: I2
687
+ }
688
+
689
+ interface I1 {
690
+ x: Int
691
+ }
692
+
693
+ interface I2 {
694
+ y: Int
695
+ }
696
+
697
+ interface I3 {
698
+ z: Int
699
+ }
700
+
701
+ type T1 implements I1 & I2 {
702
+ x: Int
703
+ y: Int
704
+ }
705
+
706
+ type T2 implements I1 & I3 {
707
+ x: Int
708
+ z: Int
709
+ }
710
+ `);
711
+
712
+ testFragmentsRoundtrip({
713
+ schema,
714
+ query: `
715
+ fragment FragOnI1 on I1 {
716
+ ... on I2 {
717
+ y
718
+ }
719
+ ... on I3 {
720
+ z
721
+ }
722
+ }
723
+
724
+ {
725
+ i2 {
726
+ ...FragOnI1
727
+ }
728
+ }
729
+ `,
730
+ expanded: `
731
+ {
732
+ i2 {
733
+ ... on I1 {
734
+ ... on I2 {
735
+ y
736
+ }
737
+ ... on I3 {
738
+ z
739
+ }
740
+ }
741
+ }
742
+ }
743
+ `,
744
+ });
745
+ });
746
+
582
747
  describe('applied directives', () => {
583
748
  test('reuse fragments with directives on the fragment, but only when there is those directives', () => {
584
749
  const schema = parseSchema(`
@@ -0,0 +1,39 @@
1
+ import { InputType, ListType, NonNullType, Schema } from "./definitions"
2
+
3
+ export type ArgumentCompositionStrategy = {
4
+ name: string,
5
+ supportedTypes: (schema: Schema) => InputType[],
6
+ mergeValues: (values: any[]) => any,
7
+ }
8
+
9
+ export const ARGUMENT_COMPOSITION_STRATEGIES = {
10
+ MAX: {
11
+ name: 'MAX',
12
+ supportedTypes: (schema: Schema) => [new NonNullType(schema.intType())],
13
+ mergeValues: (values: any[]) => Math.max(...values),
14
+ },
15
+ MIN: {
16
+ name: 'MIN',
17
+ supportedTypes: (schema: Schema) => [new NonNullType(schema.intType())],
18
+ mergeValues: (values: any[]) => Math.min(...values),
19
+ },
20
+ SUM: {
21
+ name: 'SUM',
22
+ supportedTypes: (schema: Schema) => [new NonNullType(schema.intType())],
23
+ mergeValues: (values: any[]) => values.reduce((acc, val) => acc + val, 0),
24
+ },
25
+ INTERSECTION: {
26
+ name: 'INTERSECTION',
27
+ supportedTypes: (schema: Schema) => schema.builtInScalarTypes().map((t) => new NonNullType(new ListType(new NonNullType(t)))),
28
+ mergeValues: (values: any[]) => values.reduce((acc, val) => acc.filter((v: any) => val.includes(v)), values[0]),
29
+ },
30
+ UNION: {
31
+ name: 'UNION',
32
+ supportedTypes: (schema: Schema) => schema.builtInScalarTypes().map((t) => new NonNullType(new ListType(new NonNullType(t)))),
33
+ mergeValues: (values: any[]) =>
34
+ values.reduce((acc, val) => {
35
+ const newValues = val.filter((v: any) => !acc.includes(v));
36
+ return acc.concat(newValues);
37
+ }, []),
38
+ },
39
+ }
package/src/coreSpec.ts CHANGED
@@ -2,12 +2,12 @@ import { ASTNode, DirectiveLocation, GraphQLError, StringValueNode } from "graph
2
2
  import { URL } from "url";
3
3
  import { CoreFeature, Directive, DirectiveDefinition, EnumType, ErrGraphQLAPISchemaValidationFailed, ErrGraphQLValidationFailed, InputType, ListType, NamedType, NonNullType, ScalarType, Schema, SchemaDefinition, SchemaElement, sourceASTs } from "./definitions";
4
4
  import { sameType } from "./types";
5
- import { assert, firstOf } from './utils';
5
+ import { assert, firstOf, MapWithCachedArrays } from './utils';
6
6
  import { aggregateError, ERRORS } from "./error";
7
7
  import { valueToString } from "./values";
8
8
  import { coreFeatureDefinitionIfKnown, registerKnownFeature } from "./knownCoreFeatures";
9
9
  import { didYouMean, suggestionList } from "./suggestions";
10
- import { ArgumentSpecification, createDirectiveSpecification, createEnumTypeSpecification, createScalarTypeSpecification, DirectiveSpecification, TypeSpecification } from "./directiveAndTypeSpecification";
10
+ import { ArgumentSpecification, createDirectiveSpecification, createEnumTypeSpecification, createScalarTypeSpecification, DirectiveCompositionSpecification, DirectiveSpecification, TypeSpecification } from "./directiveAndTypeSpecification";
11
11
 
12
12
  export const coreIdentity = 'https://specs.apollo.dev/core';
13
13
  export const linkIdentity = 'https://specs.apollo.dev/link';
@@ -38,10 +38,37 @@ function purposesDescription(purpose: CorePurpose) {
38
38
  export abstract class FeatureDefinition {
39
39
  readonly url: FeatureUrl;
40
40
 
41
+ private readonly _directiveSpecs = new MapWithCachedArrays<string, DirectiveSpecification>();
42
+ private readonly _typeSpecs = new MapWithCachedArrays<string, TypeSpecification>();
43
+
41
44
  constructor(url: FeatureUrl | string) {
42
45
  this.url = typeof url === 'string' ? FeatureUrl.parse(url) : url;
43
46
  }
44
47
 
48
+ protected registerDirective(spec: DirectiveSpecification) {
49
+ this._directiveSpecs.set(spec.name, spec);
50
+ }
51
+
52
+ protected registerType(spec: TypeSpecification) {
53
+ this._typeSpecs.set(spec.name, spec);
54
+ }
55
+
56
+ directiveSpecs(): readonly DirectiveSpecification[] {
57
+ return this._directiveSpecs.values();
58
+ }
59
+
60
+ directiveSpec(name: string): DirectiveSpecification | undefined {
61
+ return this._directiveSpecs.get(name);
62
+ }
63
+
64
+ typeSpecs(): readonly TypeSpecification[] {
65
+ return this._typeSpecs.values();
66
+ }
67
+
68
+ typeSpec(name: string): TypeSpecification | undefined {
69
+ return this._typeSpecs.get(name);
70
+ }
71
+
45
72
  get identity(): string {
46
73
  return this.url.identity;
47
74
  }
@@ -60,9 +87,25 @@ export abstract class FeatureDefinition {
60
87
  return nameInSchema != undefined && (directive.name === nameInSchema || directive.name.startsWith(`${nameInSchema}__`));
61
88
  }
62
89
 
63
- abstract addElementsToSchema(schema: Schema): GraphQLError[];
90
+ addElementsToSchema(schema: Schema): GraphQLError[] {
91
+ const feature = this.featureInSchema(schema);
92
+ assert(feature, 'The federation specification should have been added to the schema before this is called');
64
93
 
65
- abstract allElementNames(): string[];
94
+ let errors: GraphQLError[] = [];
95
+ for (const type of this.typeSpecs()) {
96
+ errors = errors.concat(this.addTypeSpec(schema, type));
97
+ }
98
+
99
+ for (const directive of this.directiveSpecs()) {
100
+ errors = errors.concat(this.addDirectiveSpec(schema, directive));
101
+ }
102
+ return errors;
103
+ }
104
+
105
+ allElementNames(): string[] {
106
+ return this.directiveSpecs().map((spec) => `@${spec.name}`)
107
+ .concat(this.typeSpecs().map((spec) => spec.name));
108
+ }
66
109
 
67
110
  protected nameInSchema(schema: Schema): string | undefined {
68
111
  const feature = this.featureInSchema(schema);
@@ -79,12 +122,12 @@ export abstract class FeatureDefinition {
79
122
  return feature ? feature.typeNameInSchema(typeName) : undefined;
80
123
  }
81
124
 
82
- protected rootDirective<TApplicationArgs extends {[key: string]: any}>(schema: Schema): DirectiveDefinition<TApplicationArgs> | undefined {
125
+ protected rootDirective<TApplicationArgs extends { [key: string]: any }>(schema: Schema): DirectiveDefinition<TApplicationArgs> | undefined {
83
126
  const name = this.nameInSchema(schema);
84
127
  return name ? schema.directive(name) as DirectiveDefinition<TApplicationArgs> | undefined : undefined;
85
128
  }
86
129
 
87
- protected directive<TApplicationArgs extends {[key: string]: any}>(schema: Schema, elementName: string): DirectiveDefinition<TApplicationArgs> | undefined {
130
+ protected directive<TApplicationArgs extends { [key: string]: any }>(schema: Schema, elementName: string): DirectiveDefinition<TApplicationArgs> | undefined {
88
131
  const name = this.directiveNameInSchema(schema, elementName);
89
132
  return name ? schema.directive(name) as DirectiveDefinition<TApplicationArgs> | undefined : undefined;
90
133
  }
@@ -130,11 +173,17 @@ export abstract class FeatureDefinition {
130
173
  return undefined;
131
174
  }
132
175
 
176
+ compositionSpecification(directiveNameInFeature: string): DirectiveCompositionSpecification | undefined {
177
+ const spec = this._directiveSpecs.get(directiveNameInFeature);
178
+ return spec?.composition;
179
+ }
180
+
133
181
  toString(): string {
134
182
  return `${this.identity}/${this.version}`
135
183
  }
136
184
  }
137
185
 
186
+
138
187
  export type CoreDirectiveArgs = {
139
188
  url: undefined,
140
189
  feature: string,
@@ -289,7 +338,7 @@ export function isCoreSpecDirectiveApplication(directive: Directive<SchemaDefini
289
338
  if (url.identity === coreIdentity) {
290
339
  return directive.name === (args.as ?? 'core');
291
340
  } else {
292
- return url.identity === linkIdentity && directive.name === (args.as ?? linkDirectiveDefaultName);
341
+ return url.identity === linkIdentity && directive.name === (args.as ?? linkDirectiveDefaultName);
293
342
  }
294
343
  } catch (err) {
295
344
  return false;
@@ -307,7 +356,7 @@ function isValidUrlArgumentType(type: InputType, schema: Schema): boolean {
307
356
 
308
357
  const linkPurposeTypeSpec = createEnumTypeSpecification({
309
358
  name: 'Purpose',
310
- values: corePurposes.map((name) => ({ name, description: purposesDescription(name)}))
359
+ values: corePurposes.map((name) => ({ name, description: purposesDescription(name) }))
311
360
  });
312
361
 
313
362
  const linkImportTypeSpec = createScalarTypeSpecification({ name: 'Import' });
@@ -321,32 +370,43 @@ export class CoreSpecDefinition extends FeatureDefinition {
321
370
  name,
322
371
  locations: [DirectiveLocation.SCHEMA],
323
372
  repeatable: true,
324
- argumentFct: (schema, nameInSchema) => this.createDefinitionArgumentSpecifications(schema, nameInSchema),
373
+ args: this.createDefinitionArgumentSpecifications(),
325
374
  });
375
+ this.registerDirective(this.directiveDefinitionSpec);
326
376
  }
327
377
 
328
- private createDefinitionArgumentSpecifications(schema: Schema, nameInSchema?: string): { args: ArgumentSpecification[], errors: GraphQLError[] } {
378
+ private createDefinitionArgumentSpecifications(): ArgumentSpecification[] {
329
379
  const args: ArgumentSpecification[] = [
330
- { name: this.urlArgName(), type: schema.stringType() },
331
- { name: 'as', type: schema.stringType() },
380
+ { name: this.urlArgName(), type: (schema) => schema.stringType() },
381
+ { name: 'as', type: (schema) => schema.stringType() },
332
382
  ];
333
383
  if (this.supportPurposes()) {
334
- const purposeName = `${nameInSchema ?? this.url.name}__${linkPurposeTypeSpec.name}`;
335
- const errors = linkPurposeTypeSpec.checkOrAdd(schema, purposeName);
336
- if (errors.length > 0) {
337
- return { args, errors }
338
- }
339
- args.push({ name: 'for', type: schema.type(purposeName) as InputType });
384
+ args.push({
385
+ name: 'for',
386
+ type: (schema, nameInSchema) => {
387
+ const purposeName = `${nameInSchema ?? this.url.name}__${linkPurposeTypeSpec.name}`;
388
+ const errors = linkPurposeTypeSpec.checkOrAdd(schema, purposeName);
389
+ if (errors.length > 0) {
390
+ return errors;
391
+ }
392
+ return schema.type(purposeName) as InputType;
393
+ },
394
+ });
340
395
  }
341
396
  if (this.supportImport()) {
342
- const importName = `${nameInSchema ?? this.url.name}__${linkImportTypeSpec.name}`;
343
- const errors = linkImportTypeSpec.checkOrAdd(schema, importName);
344
- if (errors.length > 0) {
345
- return { args, errors }
346
- }
347
- args.push({ name: 'import', type: new ListType(schema.type(importName)!) });
397
+ args.push({
398
+ name: 'import',
399
+ type: (schema, nameInSchema) => {
400
+ const importName = `${nameInSchema ?? this.url.name}__${linkImportTypeSpec.name}`;
401
+ const errors = linkImportTypeSpec.checkOrAdd(schema, importName);
402
+ if (errors.length > 0) {
403
+ return errors;
404
+ }
405
+ return new ListType(schema.type(importName)!);
406
+ }
407
+ });
348
408
  }
349
- return { args, errors: [] };
409
+ return args;
350
410
  }
351
411
 
352
412
  addElementsToSchema(_: Schema): GraphQLError[] {
@@ -394,7 +454,7 @@ export class CoreSpecDefinition extends FeatureDefinition {
394
454
  //
395
455
  // So instead, we put the directive on the schema definition unless some extensions exists but no
396
456
  // definition does (that is, no non-extension elements are populated).
397
- const schemaDef = schema.schemaDefinition;
457
+ const schemaDef = schema.schemaDefinition;
398
458
  // Side-note: this test must be done _before_ we call `applyDirective`, otherwise it would take it into
399
459
  // account.
400
460
  const hasDefinition = schemaDef.hasNonExtensionElements();
@@ -429,7 +489,7 @@ export class CoreSpecDefinition extends FeatureDefinition {
429
489
  * must start with a `@`.
430
490
  */
431
491
  allElementNames(): string[] {
432
- const names = [ `@${this.url.name}` ];
492
+ const names = [`@${this.url.name}`];
433
493
  if (this.supportPurposes()) {
434
494
  names.push('Purpose');
435
495
  }
@@ -533,7 +593,7 @@ export class FeatureDefinitions<T extends FeatureDefinition = FeatureDefinition>
533
593
  * Versions are a (major, minor) number pair.
534
594
  */
535
595
  export class FeatureVersion {
536
- constructor(public readonly major: number, public readonly minor: number) {}
596
+ constructor(public readonly major: number, public readonly minor: number) { }
537
597
 
538
598
  /**
539
599
  * Parse a version specifier of the form "v(major).(minor)" or throw
@@ -565,8 +625,8 @@ export class FeatureVersion {
565
625
  * ```
566
626
  **/
567
627
  public satisfies(required: FeatureVersion): boolean {
568
- const {major, minor} = this
569
- const {major: rMajor, minor: rMinor} = required
628
+ const { major, minor } = this
629
+ const { major: rMajor, minor: rMinor } = required
570
630
  return rMajor == major && (
571
631
  major == 0
572
632
  ? rMinor == minor
@@ -580,7 +640,7 @@ export class FeatureVersion {
580
640
  * of compatibility, so those will just return the same thing as `this.toString()`.
581
641
  */
582
642
  public get series() {
583
- const {major} = this
643
+ const { major } = this
584
644
  return major > 0 ? `${major}.x` : String(this)
585
645
  }
586
646
 
@@ -650,7 +710,7 @@ export class FeatureUrl {
650
710
  public readonly name: string,
651
711
  public readonly version: FeatureVersion,
652
712
  public readonly element?: string,
653
- ) {}
713
+ ) { }
654
714
 
655
715
  /// Parse a spec URL or throw
656
716
  public static parse(input: string, node?: ASTNode): FeatureUrl {
@@ -668,7 +728,7 @@ export class FeatureUrl {
668
728
  if (!name) {
669
729
  throw ERRORS.INVALID_LINK_IDENTIFIER.err(`Missing feature name component in feature url '${url}'`, { nodes: node })
670
730
  }
671
- const element = url.hash ? url.hash.slice(1): undefined
731
+ const element = url.hash ? url.hash.slice(1) : undefined
672
732
  url.hash = ''
673
733
  url.search = ''
674
734
  url.password = ''
@@ -690,7 +750,7 @@ export class FeatureUrl {
690
750
  */
691
751
  public satisfies(requested: FeatureUrl): boolean {
692
752
  return requested.identity === this.identity &&
693
- this.version.satisfies(requested.version)
753
+ this.version.satisfies(requested.version)
694
754
  }
695
755
 
696
756
  public equals(other: FeatureUrl) {