@theguild/federation-composition 0.20.2 → 0.21.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.
Files changed (57) hide show
  1. package/cjs/subgraph/state.js +21 -32
  2. package/cjs/subgraph/validation/rules/elements/authenticated.js +23 -2
  3. package/cjs/subgraph/validation/rules/elements/cost.js +12 -2
  4. package/cjs/subgraph/validation/rules/elements/list-size.js +10 -8
  5. package/cjs/subgraph/validation/rules/elements/policy.js +25 -5
  6. package/cjs/subgraph/validation/rules/elements/requires-scopes.js +23 -2
  7. package/cjs/supergraph/composition/ast.js +5 -14
  8. package/cjs/supergraph/composition/enum-type.js +3 -2
  9. package/cjs/supergraph/composition/interface-type.js +32 -11
  10. package/cjs/supergraph/composition/object-type.js +46 -7
  11. package/cjs/supergraph/composition/scalar-type.js +3 -2
  12. package/cjs/supergraph/state.js +43 -6
  13. package/cjs/supergraph/validation/rules/auth-on-requires-rule.js +211 -0
  14. package/cjs/supergraph/validation/rules/external-missing-on-base-rule.js +26 -1
  15. package/cjs/supergraph/validation/rules/list-size-slicing-arguments-rule.js +69 -0
  16. package/cjs/supergraph/validation/validate-supergraph.js +5 -0
  17. package/cjs/utils/auth.js +34 -0
  18. package/esm/subgraph/state.js +21 -32
  19. package/esm/subgraph/validation/rules/elements/authenticated.js +24 -3
  20. package/esm/subgraph/validation/rules/elements/cost.js +12 -2
  21. package/esm/subgraph/validation/rules/elements/list-size.js +10 -8
  22. package/esm/subgraph/validation/rules/elements/policy.js +26 -6
  23. package/esm/subgraph/validation/rules/elements/requires-scopes.js +24 -3
  24. package/esm/supergraph/composition/ast.js +5 -14
  25. package/esm/supergraph/composition/enum-type.js +3 -2
  26. package/esm/supergraph/composition/interface-type.js +33 -12
  27. package/esm/supergraph/composition/object-type.js +46 -7
  28. package/esm/supergraph/composition/scalar-type.js +3 -2
  29. package/esm/supergraph/state.js +43 -6
  30. package/esm/supergraph/validation/rules/auth-on-requires-rule.js +208 -0
  31. package/esm/supergraph/validation/rules/external-missing-on-base-rule.js +26 -1
  32. package/esm/supergraph/validation/rules/list-size-slicing-arguments-rule.js +66 -0
  33. package/esm/supergraph/validation/validate-supergraph.js +5 -0
  34. package/esm/utils/auth.js +31 -0
  35. package/package.json +1 -1
  36. package/typings/subgraph/state.d.cts +1 -6
  37. package/typings/subgraph/state.d.ts +1 -6
  38. package/typings/subgraph/validation/validation-context.d.cts +0 -3
  39. package/typings/subgraph/validation/validation-context.d.ts +0 -3
  40. package/typings/supergraph/composition/ast.d.cts +1 -0
  41. package/typings/supergraph/composition/ast.d.ts +1 -0
  42. package/typings/supergraph/composition/common.d.cts +4 -0
  43. package/typings/supergraph/composition/common.d.ts +4 -0
  44. package/typings/supergraph/composition/interface-type.d.cts +3 -0
  45. package/typings/supergraph/composition/interface-type.d.ts +3 -0
  46. package/typings/supergraph/composition/object-type.d.cts +6 -0
  47. package/typings/supergraph/composition/object-type.d.ts +6 -0
  48. package/typings/supergraph/state.d.cts +1 -0
  49. package/typings/supergraph/state.d.ts +1 -0
  50. package/typings/supergraph/validation/rules/auth-on-requires-rule.d.cts +5 -0
  51. package/typings/supergraph/validation/rules/auth-on-requires-rule.d.ts +5 -0
  52. package/typings/supergraph/validation/rules/external-missing-on-base-rule.d.cts +2 -1
  53. package/typings/supergraph/validation/rules/external-missing-on-base-rule.d.ts +2 -1
  54. package/typings/supergraph/validation/rules/list-size-slicing-arguments-rule.d.cts +5 -0
  55. package/typings/supergraph/validation/rules/list-size-slicing-arguments-rule.d.ts +5 -0
  56. package/typings/utils/auth.d.cts +2 -0
  57. package/typings/utils/auth.d.ts +2 -0
@@ -166,29 +166,29 @@ export function ListSizeRule(context) {
166
166
  const outputType = namedTypeFromTypeNode(fieldDef.type);
167
167
  const targetType = knownObjectsAndInterfaces.get(outputType.name.value);
168
168
  if (!targetType) {
169
- context.reportError(new GraphQLError(`${coordinate} has @listSize(sizedFields:) applied, but the output type is not an object`, {
169
+ context.reportError(new GraphQLError(`${coordinate} has @listSize(sizedFields:) applied, but the output type is not a composite type`, {
170
170
  extensions: {
171
171
  code: "LIST_SIZE_INVALID_SIZED_FIELD",
172
172
  },
173
173
  }));
174
174
  return;
175
175
  }
176
- const nonIntFields = [];
176
+ const nonListFields = [];
177
177
  const nonExistingFields = [];
178
178
  for (const sizedField of sizedFields) {
179
179
  const targetField = targetType.fields?.find((f) => f.name.value === sizedField);
180
180
  if (!targetField) {
181
181
  nonExistingFields.push(sizedField);
182
182
  }
183
- else if (!isIntTypeOrNullableIntType(targetField.type)) {
184
- nonIntFields.push(sizedField);
183
+ else if (!isListType(targetField.type)) {
184
+ nonListFields.push(sizedField);
185
185
  }
186
186
  }
187
- if (nonIntFields.length || nonExistingFields.length) {
188
- nonIntFields.forEach((fieldName) => {
189
- context.reportError(new GraphQLError(`${coordinate} references "${fieldName}" field in @listSize(sizedFields:) argument that is not an integer.`, {
187
+ if (nonListFields.length || nonExistingFields.length) {
188
+ nonListFields.forEach((fieldName) => {
189
+ context.reportError(new GraphQLError(`${coordinate} references "${fieldName}" field in @listSize(sizedFields:) argument that is not a list.`, {
190
190
  extensions: {
191
- code: "LIST_SIZE_INVALID_SIZED_FIELD",
191
+ code: "LIST_SIZE_APPLIED_TO_NON_LIST",
192
192
  },
193
193
  }));
194
194
  });
@@ -235,6 +235,7 @@ export function ListSizeRule(context) {
235
235
  if (typeDef.kind === Kind.OBJECT_TYPE_DEFINITION ||
236
236
  typeDef.kind === Kind.OBJECT_TYPE_EXTENSION) {
237
237
  context.stateBuilder.objectType.field.setListSize(typeDef.name.value, parent.name.value, {
238
+ printRequireOneSlicingArgument: false,
238
239
  assumedSize,
239
240
  slicingArguments,
240
241
  sizedFields,
@@ -244,6 +245,7 @@ export function ListSizeRule(context) {
244
245
  if (typeDef.kind === Kind.INTERFACE_TYPE_DEFINITION ||
245
246
  typeDef.kind === Kind.INTERFACE_TYPE_EXTENSION) {
246
247
  context.stateBuilder.interfaceType.field.setListSize(typeDef.name.value, parent.name.value, {
248
+ printRequireOneSlicingArgument: false,
247
249
  assumedSize,
248
250
  slicingArguments,
249
251
  sizedFields,
@@ -1,4 +1,4 @@
1
- import { Kind } from "graphql";
1
+ import { GraphQLError, Kind } from "graphql";
2
2
  import { print } from "../../../../graphql/printer.js";
3
3
  import { validateDirectiveAgainstOriginal } from "../../../helpers.js";
4
4
  export function PolicyRule(context) {
@@ -52,21 +52,41 @@ export function PolicyRule(context) {
52
52
  if (!typeDef) {
53
53
  throw new Error("Could not find the parent type of the field annotated with @policy");
54
54
  }
55
- if ((typeDef.kind === Kind.OBJECT_TYPE_DEFINITION ||
56
- typeDef.kind === Kind.OBJECT_TYPE_EXTENSION) &&
57
- !context.stateBuilder.isInterfaceObject(typeDef.name.value)) {
55
+ if (typeDef.kind === Kind.INTERFACE_TYPE_DEFINITION ||
56
+ typeDef.kind === Kind.INTERFACE_TYPE_EXTENSION) {
57
+ context.reportError(new GraphQLError(`Invalid use of @policy on field "${typeDef.name.value}.${parent.name.value}": @policy cannot be applied on interfaces, interface fields and interface objects`, {
58
+ extensions: {
59
+ code: "AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE",
60
+ },
61
+ }));
62
+ return;
63
+ }
64
+ if (typeDef.kind === Kind.OBJECT_TYPE_DEFINITION ||
65
+ typeDef.kind === Kind.OBJECT_TYPE_EXTENSION) {
58
66
  context.stateBuilder.objectType.field.setPolicies(typeDef.name.value, parent.name.value, policies);
59
67
  }
60
68
  break;
61
69
  }
62
70
  case Kind.OBJECT_TYPE_DEFINITION:
63
71
  case Kind.OBJECT_TYPE_EXTENSION:
72
+ if (context.stateBuilder.isInterfaceObject(parent.name.value)) {
73
+ context.reportError(new GraphQLError(`Invalid use of @policy on interface object "${parent.name.value}": @policy cannot be applied on interfaces, interface fields and interface objects`, {
74
+ extensions: {
75
+ code: "AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE",
76
+ },
77
+ }));
78
+ return;
79
+ }
64
80
  context.stateBuilder.objectType.setPolicies(parent.name.value, policies);
65
81
  break;
66
82
  case Kind.INTERFACE_TYPE_DEFINITION:
67
83
  case Kind.INTERFACE_TYPE_DEFINITION:
68
- context.stateBuilder.interfaceType.setPolicies(parent.name.value, policies);
69
- break;
84
+ context.reportError(new GraphQLError(`Invalid use of @policy on interface "${parent.name.value}": @policy cannot be applied on interfaces, interface fields and interface objects`, {
85
+ extensions: {
86
+ code: "AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE",
87
+ },
88
+ }));
89
+ return;
70
90
  case Kind.SCALAR_TYPE_DEFINITION:
71
91
  case Kind.SCALAR_TYPE_EXTENSION:
72
92
  context.stateBuilder.scalarType.setPolicies(parent.name.value, policies);
@@ -1,4 +1,4 @@
1
- import { Kind } from "graphql";
1
+ import { GraphQLError, Kind } from "graphql";
2
2
  import { print } from "../../../../graphql/printer.js";
3
3
  import { validateDirectiveAgainstOriginal } from "../../../helpers.js";
4
4
  export function RequiresScopesRule(context) {
@@ -52,6 +52,15 @@ export function RequiresScopesRule(context) {
52
52
  if (!typeDef) {
53
53
  throw new Error("Could not find the parent type of the field annotated with @requiresScopes");
54
54
  }
55
+ if (typeDef.kind === Kind.INTERFACE_TYPE_DEFINITION ||
56
+ typeDef.kind === Kind.INTERFACE_TYPE_EXTENSION) {
57
+ context.reportError(new GraphQLError(`Invalid use of @requiresScopes on field "${typeDef.name.value}.${parent.name.value}": @requiresScopes cannot be applied on interfaces, interface fields and interface objects`, {
58
+ extensions: {
59
+ code: "AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE",
60
+ },
61
+ }));
62
+ return;
63
+ }
55
64
  if (typeDef.kind === Kind.OBJECT_TYPE_DEFINITION ||
56
65
  typeDef.kind === Kind.OBJECT_TYPE_EXTENSION) {
57
66
  context.stateBuilder.objectType.field.setScopes(typeDef.name.value, parent.name.value, scopes);
@@ -60,12 +69,24 @@ export function RequiresScopesRule(context) {
60
69
  }
61
70
  case Kind.OBJECT_TYPE_DEFINITION:
62
71
  case Kind.OBJECT_TYPE_EXTENSION:
72
+ if (context.stateBuilder.isInterfaceObject(parent.name.value)) {
73
+ context.reportError(new GraphQLError(`Invalid use of @requiresScopes on interface object "${parent.name.value}": @requiresScopes cannot be applied on interfaces, interface fields and interface objects`, {
74
+ extensions: {
75
+ code: "AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE",
76
+ },
77
+ }));
78
+ return;
79
+ }
63
80
  context.stateBuilder.objectType.setScopes(parent.name.value, scopes);
64
81
  break;
65
82
  case Kind.INTERFACE_TYPE_DEFINITION:
66
83
  case Kind.INTERFACE_TYPE_DEFINITION:
67
- context.stateBuilder.interfaceType.setScopes(parent.name.value, scopes);
68
- break;
84
+ context.reportError(new GraphQLError(`Invalid use of @requiresScopes on interface "${parent.name.value}": @requiresScopes cannot be applied on interfaces, interface fields and interface objects`, {
85
+ extensions: {
86
+ code: "AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE",
87
+ },
88
+ }));
89
+ return;
69
90
  case Kind.SCALAR_TYPE_DEFINITION:
70
91
  case Kind.SCALAR_TYPE_EXTENSION:
71
92
  context.stateBuilder.scalarType.setScopes(parent.name.value, scopes);
@@ -515,16 +515,6 @@ function createAuthenticatedDirectiveNode() {
515
515
  arguments: [],
516
516
  };
517
517
  }
518
- function deduplicatePoliciesOrScopes(items) {
519
- const stringified = items.map((group) => group.sort().join("ɵ"));
520
- const indexesToRemove = [];
521
- for (let index = 0; index < stringified.length; index++) {
522
- if (stringified.indexOf(stringified[index]) !== index) {
523
- indexesToRemove.push(index);
524
- }
525
- }
526
- return items.filter((_, index) => !indexesToRemove.includes(index));
527
- }
528
518
  function createPolicyDirectiveNode(policies) {
529
519
  return {
530
520
  kind: Kind.DIRECTIVE,
@@ -541,7 +531,7 @@ function createPolicyDirectiveNode(policies) {
541
531
  },
542
532
  value: {
543
533
  kind: Kind.LIST,
544
- values: deduplicatePoliciesOrScopes(policies).map((group) => ({
534
+ values: policies.map((group) => ({
545
535
  kind: Kind.LIST,
546
536
  values: group.map((policy) => ({
547
537
  kind: Kind.STRING,
@@ -569,7 +559,7 @@ function createRequiresScopesDirectiveNode(scopes) {
569
559
  },
570
560
  value: {
571
561
  kind: Kind.LIST,
572
- values: deduplicatePoliciesOrScopes(scopes).map((group) => ({
562
+ values: scopes.map((group) => ({
573
563
  kind: Kind.LIST,
574
564
  values: group.map((scope) => ({
575
565
  kind: Kind.STRING,
@@ -713,7 +703,8 @@ function createCostDirectiveNode(input) {
713
703
  }
714
704
  function createListSizeDirectiveNode(input) {
715
705
  const args = [];
716
- if (input.requireOneSlicingArgument === false) {
706
+ if (input.requireOneSlicingArgument === false ||
707
+ input.printRequireOneSlicingArgument === true) {
717
708
  args.push({
718
709
  kind: Kind.ARGUMENT,
719
710
  name: {
@@ -722,7 +713,7 @@ function createListSizeDirectiveNode(input) {
722
713
  },
723
714
  value: {
724
715
  kind: Kind.BOOLEAN,
725
- value: false,
716
+ value: input.requireOneSlicingArgument === true,
726
717
  },
727
718
  });
728
719
  }
@@ -1,6 +1,7 @@
1
1
  import { ensureValue, mathMax } from "../../utils/helpers.js";
2
2
  import { createEnumTypeNode } from "./ast.js";
3
3
  import { convertToConst } from "./common.js";
4
+ import { mergeScopePolicies } from "../../utils/auth.js";
4
5
  export function enumTypeBuilder() {
5
6
  return {
6
7
  visitSubgraphState(graph, state, typeName, type) {
@@ -13,10 +14,10 @@ export function enumTypeBuilder() {
13
14
  enumTypeState.authenticated = true;
14
15
  }
15
16
  if (type.policies) {
16
- enumTypeState.policies.push(...type.policies);
17
+ enumTypeState.policies = mergeScopePolicies(enumTypeState.policies, type.policies);
17
18
  }
18
19
  if (type.scopes) {
19
- enumTypeState.scopes.push(...type.scopes);
20
+ enumTypeState.scopes = mergeScopePolicies(enumTypeState.scopes, type.scopes);
20
21
  }
21
22
  if (type.cost !== null) {
22
23
  enumTypeState.cost = mathMax(type.cost, enumTypeState.cost);
@@ -1,6 +1,7 @@
1
- import { ensureValue, mathMax, mathMaxNullable, nullableArrayUnion, } from "../../utils/helpers.js";
1
+ import { ensureValue, isDefined, mathMax, mathMaxNullable, nullableArrayUnion, } from "../../utils/helpers.js";
2
2
  import { createInterfaceTypeNode } from "./ast.js";
3
3
  import { convertToConst } from "./common.js";
4
+ import { mergeScopePolicies } from "../../utils/auth.js";
4
5
  export function interfaceTypeBuilder() {
5
6
  return {
6
7
  visitSubgraphState(graph, state, typeName, type) {
@@ -9,15 +10,6 @@ export function interfaceTypeBuilder() {
9
10
  if (type.inaccessible) {
10
11
  interfaceTypeState.inaccessible = true;
11
12
  }
12
- if (type.authenticated) {
13
- interfaceTypeState.authenticated = true;
14
- }
15
- if (type.policies) {
16
- interfaceTypeState.policies.push(...type.policies);
17
- }
18
- if (type.scopes) {
19
- interfaceTypeState.scopes.push(...type.scopes);
20
- }
21
13
  if (type.isDefinition) {
22
14
  interfaceTypeState.hasDefinition = true;
23
15
  }
@@ -59,16 +51,17 @@ export function interfaceTypeBuilder() {
59
51
  fieldState.authenticated = true;
60
52
  }
61
53
  if (field.policies) {
62
- fieldState.policies.push(...field.policies);
54
+ fieldState.policies = mergeScopePolicies(fieldState.policies, field.policies);
63
55
  }
64
56
  if (field.scopes) {
65
- fieldState.scopes.push(...field.scopes);
57
+ fieldState.scopes = mergeScopePolicies(fieldState.scopes, field.scopes);
66
58
  }
67
59
  if (field.cost !== null) {
68
60
  fieldState.cost = mathMax(field.cost, fieldState.cost);
69
61
  }
70
62
  if (field.listSize !== null) {
71
63
  fieldState.listSize = {
64
+ printRequireOneSlicingArgument: false,
72
65
  assumedSize: mathMaxNullable(fieldState.listSize?.assumedSize, field.listSize.assumedSize),
73
66
  requireOneSlicingArgument: (fieldState.listSize?.requireOneSlicingArgument ?? true) &&
74
67
  field.listSize.requireOneSlicingArgument,
@@ -98,6 +91,9 @@ export function interfaceTypeBuilder() {
98
91
  version: graph.version,
99
92
  external: field.external,
100
93
  shareable: field.shareable,
94
+ authenticated: field.authenticated,
95
+ policies: field.policies,
96
+ scopes: field.scopes,
101
97
  usedAsKey,
102
98
  });
103
99
  for (const arg of field.args.values()) {
@@ -131,6 +127,31 @@ export function interfaceTypeBuilder() {
131
127
  }
132
128
  }
133
129
  },
130
+ composeSupergraphState(interfaceType, graphs, { supergraphState }) {
131
+ const implementors = Array.from(interfaceType.implementedBy)
132
+ .map((typeName) => supergraphState.objectTypes.get(typeName))
133
+ .filter(isDefined);
134
+ for (const implementor of implementors) {
135
+ interfaceType.scopes = mergeScopePolicies(interfaceType.scopes, implementor.scopes);
136
+ interfaceType.policies = mergeScopePolicies(interfaceType.policies, implementor.policies);
137
+ if (implementor.authenticated) {
138
+ interfaceType.authenticated = true;
139
+ }
140
+ }
141
+ for (const field of interfaceType.fields.values()) {
142
+ for (const implementor of implementors) {
143
+ let implementorField = implementor.fields.get(field.name);
144
+ if (!implementorField) {
145
+ continue;
146
+ }
147
+ field.scopes = mergeScopePolicies(field.scopes, implementorField.scopes);
148
+ field.policies = mergeScopePolicies(field.policies, implementorField.policies);
149
+ if (implementorField.authenticated) {
150
+ field.authenticated = true;
151
+ }
152
+ }
153
+ }
154
+ },
134
155
  composeSupergraphNode(interfaceType, graphs, { supergraphState }) {
135
156
  return createInterfaceTypeNode({
136
157
  name: interfaceType.name,
@@ -1,6 +1,7 @@
1
1
  import { ensureValue, isDefined, mathMax, mathMaxNullable, nullableArrayUnion, } from "../../utils/helpers.js";
2
2
  import { createObjectTypeNode } from "./ast.js";
3
3
  import { convertToConst } from "./common.js";
4
+ import { mergeScopePolicies } from "../../utils/auth.js";
4
5
  export function isRealExtension(meta, version) {
5
6
  const hasExtendsDirective = meta.extensionType === "@extends";
6
7
  if (meta.extension) {
@@ -29,10 +30,10 @@ export function objectTypeBuilder() {
29
30
  objectTypeState.authenticated = true;
30
31
  }
31
32
  if (type.policies) {
32
- objectTypeState.policies.push(...type.policies);
33
+ objectTypeState.policies = mergeScopePolicies(objectTypeState.policies, type.policies);
33
34
  }
34
35
  if (type.scopes) {
35
- objectTypeState.scopes.push(...type.scopes);
36
+ objectTypeState.scopes = mergeScopePolicies(objectTypeState.scopes, type.scopes);
36
37
  }
37
38
  if (type.cost !== null) {
38
39
  objectTypeState.cost = mathMax(type.cost, objectTypeState.cost);
@@ -64,6 +65,9 @@ export function objectTypeBuilder() {
64
65
  shareable: type.shareable,
65
66
  interfaces: type.interfaces,
66
67
  version: graph.version,
68
+ authenticated: type.authenticated,
69
+ policies: type.policies.slice(),
70
+ scopes: type.scopes.slice(),
67
71
  });
68
72
  const typeInGraph = objectTypeState.byGraph.get(graph.id);
69
73
  for (const field of type.fields.values()) {
@@ -96,16 +100,17 @@ export function objectTypeBuilder() {
96
100
  fieldState.authenticated = true;
97
101
  }
98
102
  if (field.policies) {
99
- fieldState.policies.push(...field.policies);
103
+ fieldState.policies = mergeScopePolicies(fieldState.policies, field.policies);
100
104
  }
101
105
  if (field.scopes) {
102
- fieldState.scopes.push(...field.scopes);
106
+ fieldState.scopes = mergeScopePolicies(fieldState.scopes, field.scopes);
103
107
  }
104
108
  if (field.cost !== null) {
105
109
  fieldState.cost = mathMax(field.cost, fieldState.cost);
106
110
  }
107
111
  if (field.listSize !== null) {
108
112
  fieldState.listSize = {
113
+ printRequireOneSlicingArgument: false,
109
114
  assumedSize: mathMaxNullable(fieldState.listSize?.assumedSize, field.listSize.assumedSize),
110
115
  requireOneSlicingArgument: (fieldState.listSize?.requireOneSlicingArgument ?? true) &&
111
116
  field.listSize.requireOneSlicingArgument,
@@ -141,6 +146,9 @@ export function objectTypeBuilder() {
141
146
  required: field.required,
142
147
  shareable: field.shareable,
143
148
  extension: field.extension,
149
+ authenticated: field.authenticated,
150
+ policies: field.policies,
151
+ scopes: field.scopes,
144
152
  used: field.used,
145
153
  usedAsKey,
146
154
  version: graph.version,
@@ -180,6 +188,27 @@ export function objectTypeBuilder() {
180
188
  }
181
189
  }
182
190
  },
191
+ composeSupergraphState(objectType, _graphs, { supergraphState }) {
192
+ for (const interfaceName of objectType.interfaces) {
193
+ const interfaceState = supergraphState.interfaceTypes.get(interfaceName);
194
+ if (!interfaceState) {
195
+ throw new Error(`Interface "${interfaceName}" not found in Supergraph state`);
196
+ }
197
+ for (const [interfaceFieldName, interfaceField,] of interfaceState.fields) {
198
+ if (!interfaceState.hasInterfaceObject) {
199
+ continue;
200
+ }
201
+ const fieldState = objectType.fields.get(interfaceFieldName);
202
+ if (fieldState) {
203
+ fieldState.scopes = mergeScopePolicies(fieldState.scopes, interfaceField.scopes);
204
+ fieldState.policies = mergeScopePolicies(fieldState.policies, interfaceField.policies);
205
+ if (interfaceField.authenticated) {
206
+ fieldState.authenticated = true;
207
+ }
208
+ }
209
+ }
210
+ }
211
+ },
183
212
  composeSupergraphNode(objectType, graphs, { graphNameToId, supergraphState }) {
184
213
  const isQuery = objectType.name === "Query";
185
214
  const joinTypes = isQuery
@@ -569,13 +598,23 @@ export function objectTypeBuilder() {
569
598
  .concat(resolvableFieldsFromInterfaceObjects
570
599
  .filter((f) => !objectType.fields.has(f.name))
571
600
  .map((field) => {
601
+ let scopes = [];
602
+ let policies = [];
603
+ let authenticated = false;
604
+ for (const fieldInGraph of field.byGraph.values()) {
605
+ scopes = mergeScopePolicies(scopes, fieldInGraph.scopes);
606
+ policies = mergeScopePolicies(policies, fieldInGraph.policies);
607
+ if (fieldInGraph.authenticated) {
608
+ authenticated = true;
609
+ }
610
+ }
572
611
  return {
573
612
  name: field.name,
574
613
  type: field.type,
575
614
  inaccessible: field.inaccessible,
576
- authenticated: field.authenticated,
577
- policies: field.policies,
578
- scopes: field.scopes,
615
+ authenticated,
616
+ policies,
617
+ scopes,
579
618
  cost: field.cost !== null
580
619
  ? {
581
620
  cost: field.cost,
@@ -1,6 +1,7 @@
1
1
  import { ensureValue, mathMax } from "../../utils/helpers.js";
2
2
  import { createScalarTypeNode } from "./ast.js";
3
3
  import { convertToConst } from "./common.js";
4
+ import { mergeScopePolicies } from "../../utils/auth.js";
4
5
  export function scalarTypeBuilder() {
5
6
  return {
6
7
  visitSubgraphState(graph, state, typeName, type) {
@@ -13,10 +14,10 @@ export function scalarTypeBuilder() {
13
14
  scalarTypeState.authenticated = true;
14
15
  }
15
16
  if (type.policies) {
16
- scalarTypeState.policies.push(...type.policies);
17
+ scalarTypeState.policies = mergeScopePolicies(scalarTypeState.policies, type.policies);
17
18
  }
18
19
  if (type.scopes) {
19
- scalarTypeState.scopes.push(...type.scopes);
20
+ scalarTypeState.scopes = mergeScopePolicies(scalarTypeState.scopes, type.scopes);
20
21
  }
21
22
  if (type.cost !== null) {
22
23
  scalarTypeState.cost = mathMax(type.cost, scalarTypeState.cost);
@@ -46,6 +46,12 @@ export function createSupergraphStateBuilder() {
46
46
  const unionType = unionTypeBuilder();
47
47
  const subgraphStates = new Map();
48
48
  const graphNameToIdMap = {};
49
+ const helpers = {
50
+ graphNameToId(graphName) {
51
+ return graphNameToIdMap[graphName] ?? null;
52
+ },
53
+ supergraphState: state,
54
+ };
49
55
  return {
50
56
  addSubgraph(subgraph) {
51
57
  if (state.subgraphs.has(subgraph.graph.id)) {
@@ -57,6 +63,43 @@ export function createSupergraphStateBuilder() {
57
63
  getGraph(id) {
58
64
  return state.subgraphs.get(id);
59
65
  },
66
+ composeSupergraphState() {
67
+ if (directive.composeSupergraphState) {
68
+ state.directives.forEach((s) => {
69
+ directive.composeSupergraphState(s, state.subgraphs, helpers);
70
+ });
71
+ }
72
+ if (scalarType.composeSupergraphState) {
73
+ state.scalarTypes.forEach((s) => {
74
+ scalarType.composeSupergraphState(s, state.subgraphs, helpers);
75
+ });
76
+ }
77
+ if (enumType.composeSupergraphState) {
78
+ state.enumTypes.forEach((s) => {
79
+ enumType.composeSupergraphState(s, state.subgraphs, helpers);
80
+ });
81
+ }
82
+ if (inputObjectType.composeSupergraphState) {
83
+ state.inputObjectTypes.forEach((s) => {
84
+ inputObjectType.composeSupergraphState(s, state.subgraphs, helpers);
85
+ });
86
+ }
87
+ if (objectType.composeSupergraphState) {
88
+ state.objectTypes.forEach((s) => {
89
+ objectType.composeSupergraphState(s, state.subgraphs, helpers);
90
+ });
91
+ }
92
+ if (interfaceType.composeSupergraphState) {
93
+ state.interfaceTypes.forEach((s) => {
94
+ interfaceType.composeSupergraphState(s, state.subgraphs, helpers);
95
+ });
96
+ }
97
+ if (unionType.composeSupergraphState) {
98
+ state.unionTypes.forEach((s) => {
99
+ unionType.composeSupergraphState(s, state.subgraphs, helpers);
100
+ });
101
+ }
102
+ },
60
103
  visitSubgraphState(subgraphState) {
61
104
  subgraphStates.set(subgraphState.graph.id, subgraphState);
62
105
  for (const link of subgraphState.links) {
@@ -131,12 +174,6 @@ export function createSupergraphStateBuilder() {
131
174
  },
132
175
  build() {
133
176
  const transformFields = createFieldsTransformer(state);
134
- const helpers = {
135
- graphNameToId(graphName) {
136
- return graphNameToIdMap[graphName] ?? null;
137
- },
138
- supergraphState: state,
139
- };
140
177
  for (const directiveState of state.directives.values()) {
141
178
  if (!directiveState.isExecutable || !directiveState.byGraph.size) {
142
179
  continue;