@theguild/federation-composition 0.6.1 → 0.6.2-rc-20231219171219-079169e

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.
@@ -38,13 +38,15 @@ function validateDirectiveAgainstOriginal(providedDirectiveNode, directiveName,
38
38
  const isNonNullableString = providedType === 'String!';
39
39
  const allowedFieldSetTypes = isFederationV2
40
40
  ? ['FieldSet!', 'federation__FieldSet!', '_FieldSet!']
41
- : ['_FieldSet!', 'String'];
41
+ : ['_FieldSet!', 'String', 'String!'];
42
42
  const fieldSetTypesInSpec = isFederationV2
43
43
  ? ['FieldSet!', 'federation__FieldSet!', '_FieldSet!']
44
44
  : ['_FieldSet!', 'FieldSet!', 'String'];
45
45
  const expectsFieldSet = fieldSetTypesInSpec.includes(expectedType);
46
46
  if (!isNonNullableString && expectsFieldSet) {
47
- const isOneOfExpected = allowedFieldSetTypes.includes(providedType);
47
+ const isOneOfExpected = allowedFieldSetTypes
48
+ .concat(allowedFieldSetTypes.map(f => `[${f}]!`))
49
+ .includes(providedType);
48
50
  if (!isOneOfExpected) {
49
51
  errors.push(new graphql_1.GraphQLError(`Invalid definition for directive "@${directiveName}": argument "${argName}" should have type "${expectedType}" but found type "${providedType}"`, {
50
52
  nodes: providedDirectiveNode,
@@ -69,10 +69,14 @@ function KeyRules(context) {
69
69
  const knownObjectsAndInterfaces = context.getSubgraphObjectOrInterfaceTypes();
70
70
  let isValid = true;
71
71
  const fieldsUsedInKey = new Set();
72
+ const mergedTypeDef = context.getSubgraphObjectOrInterfaceTypes().get(typeDef.name.value);
73
+ if (!mergedTypeDef) {
74
+ throw new Error(`Could not find type "${typeDef.name.value}"`);
75
+ }
72
76
  (0, helpers_js_1.visitFields)({
73
77
  context,
74
78
  selectionSet,
75
- typeDefinition: typeDef,
79
+ typeDefinition: mergedTypeDef,
76
80
  interceptField(info) {
77
81
  if (info.typeDefinition.name.value === typeDef.name.value) {
78
82
  fieldsUsedInKey.add(info.fieldName);
@@ -129,11 +129,17 @@ function ProvidesRules(context) {
129
129
  const fieldsArg = keyDirective.arguments?.find(arg => arg.name.value === 'fields' && arg.value.kind === graphql_1.Kind.STRING);
130
130
  if (fieldsArg) {
131
131
  const keyFields = (0, helpers_js_1.parseFields)(fieldsArg.value.value);
132
+ const mergedTypeDef = context
133
+ .getSubgraphObjectOrInterfaceTypes()
134
+ .get(info.typeDefinition.name.value);
135
+ if (!mergedTypeDef) {
136
+ throw new Error(`Could not find type "${info.typeDefinition.name.value}"`);
137
+ }
132
138
  if (keyFields) {
133
139
  (0, helpers_js_1.visitFields)({
134
140
  context,
135
141
  selectionSet: keyFields,
136
- typeDefinition: info.typeDefinition,
142
+ typeDefinition: mergedTypeDef,
137
143
  interceptField(keyFieldInfo) {
138
144
  if (keyFieldInfo.typeDefinition.name.value === info.typeDefinition.name.value &&
139
145
  keyFieldInfo.fieldName === info.fieldName) {
@@ -68,10 +68,16 @@ function RequiresRules(context) {
68
68
  annotatedType.kind !== graphql_1.Kind.OBJECT_TYPE_EXTENSION) {
69
69
  return;
70
70
  }
71
+ const mergedTypeDef = context
72
+ .getSubgraphObjectOrInterfaceTypes()
73
+ .get(annotatedType.name.value);
74
+ if (!mergedTypeDef) {
75
+ throw new Error(`Could not find type "${annotatedType.name.value}"`);
76
+ }
71
77
  (0, helpers_js_1.visitFields)({
72
78
  context,
73
79
  selectionSet,
74
- typeDefinition: annotatedType,
80
+ typeDefinition: mergedTypeDef,
75
81
  interceptField(info) {
76
82
  if (info.typeDefinition.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION ||
77
83
  info.typeDefinition.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) {
@@ -65,10 +65,27 @@ function createSubgraphValidationContext(subgraph, federation, typeNodeInfo, sta
65
65
  const { version, imports } = federation;
66
66
  const availableSpec = (0, federation_js_1.createSpecSchema)(version, imports);
67
67
  const knownSpec = (0, federation_js_1.createSpecSchema)(version);
68
- const knownSubgraphEntities = new Map(subgraph.typeDefs.definitions.filter(def => def.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION ||
68
+ const knownSubgraphEntities = new Map();
69
+ for (const def of subgraph.typeDefs.definitions.filter(def => def.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION ||
69
70
  def.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION ||
70
71
  def.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION ||
71
- def.kind === graphql_1.Kind.INTERFACE_TYPE_EXTENSION).map(def => [def.name.value, def]));
72
+ def.kind === graphql_1.Kind.INTERFACE_TYPE_EXTENSION)) {
73
+ const found = knownSubgraphEntities.get(def.name.value);
74
+ if (!found) {
75
+ knownSubgraphEntities.set(def.name.value, { ...def });
76
+ continue;
77
+ }
78
+ found.fields = (found.fields ?? []).concat(def.fields ?? []);
79
+ found.interfaces = (found.interfaces ?? []).concat(def.interfaces ?? []);
80
+ found.directives = (found.directives ?? []).concat(def.directives ?? []);
81
+ if (def.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && found.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) {
82
+ found.kind = graphql_1.Kind.OBJECT_TYPE_DEFINITION;
83
+ }
84
+ else if (def.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION &&
85
+ found.kind === graphql_1.Kind.INTERFACE_TYPE_EXTENSION) {
86
+ found.kind = graphql_1.Kind.INTERFACE_TYPE_DEFINITION;
87
+ }
88
+ }
72
89
  const knownSubgraphDirectiveDefinitions = new Map(subgraph.typeDefs.definitions.filter(def => def.kind === graphql_1.Kind.DIRECTIVE_DEFINITION).map(def => [def.name.value, def]));
73
90
  const leafTypeNames = new Set(graphql_1.specifiedScalarTypes.map(type => type.name));
74
91
  for (const def of subgraph.typeDefs.definitions) {
@@ -5,27 +5,28 @@ const graphql_1 = require("graphql");
5
5
  function ExternalArgumentMissingRule(context) {
6
6
  return {
7
7
  ObjectTypeFieldArg(objectState, fieldState, argState) {
8
- if (argState.type.endsWith('!')) {
9
- if (argState.byGraph.size === 1 && fieldState.byGraph.size === argState.byGraph.size) {
10
- return;
11
- }
12
- if (fieldState.byGraph.size === argState.byGraph.size) {
13
- return;
14
- }
15
- const graphsWithRequiredArg = Array.from(argState.byGraph)
16
- .filter(([graph, arg]) => arg.type.endsWith('!') && fieldState.byGraph.get(graph)?.external !== true)
17
- .map(([graph]) => graph);
18
- const externalGraphsWithoutArg = Array.from(fieldState.byGraph.keys()).filter(graph => !argState.byGraph.has(graph) && fieldState.byGraph.get(graph)?.external === true);
19
- const requiredIn = `subgraph${graphsWithRequiredArg.length > 1 ? 's' : ''} "${graphsWithRequiredArg.map(context.graphIdToName).join('", "')}"`;
20
- const missingIn = `subgraph${externalGraphsWithoutArg.length > 1 ? 's' : ''} "${externalGraphsWithoutArg.map(context.graphIdToName).join('", "')}"`;
21
- const fieldCoordinate = `${objectState.name}.${fieldState.name}`;
22
- const argCoordinate = `${objectState.name}.${fieldState.name}(${argState.name}:)`;
23
- context.reportError(new graphql_1.GraphQLError(`Field "${fieldCoordinate}" is missing argument "${argCoordinate}" in some subgraphs where it is marked @external: argument "${argCoordinate}" is declared in ${requiredIn} but not in ${missingIn} (where "${fieldCoordinate}" is @external)`, {
24
- extensions: {
25
- code: 'EXTERNAL_ARGUMENT_MISSING',
26
- },
27
- }));
8
+ if (argState.byGraph.size === 1 && fieldState.byGraph.size === argState.byGraph.size) {
9
+ return;
28
10
  }
11
+ if (fieldState.byGraph.size === argState.byGraph.size) {
12
+ return;
13
+ }
14
+ const graphsWithRequiredArg = Array.from(argState.byGraph)
15
+ .filter(([graph, arg]) => fieldState.byGraph.get(graph)?.external !== true)
16
+ .map(([graph]) => graph);
17
+ const externalGraphsWithoutArg = Array.from(fieldState.byGraph.keys()).filter(graph => !argState.byGraph.has(graph) && fieldState.byGraph.get(graph)?.external === true);
18
+ if (!externalGraphsWithoutArg.length) {
19
+ return;
20
+ }
21
+ const requiredIn = `subgraph${graphsWithRequiredArg.length > 1 ? 's' : ''} "${graphsWithRequiredArg.map(context.graphIdToName).join('", "')}"`;
22
+ const missingIn = `subgraph${externalGraphsWithoutArg.length > 1 ? 's' : ''} "${externalGraphsWithoutArg.map(context.graphIdToName).join('", "')}"`;
23
+ const fieldCoordinate = `${objectState.name}.${fieldState.name}`;
24
+ const argCoordinate = `${objectState.name}.${fieldState.name}(${argState.name}:)`;
25
+ context.reportError(new graphql_1.GraphQLError(`Field "${fieldCoordinate}" is missing argument "${argCoordinate}" in some subgraphs where it is marked @external: argument "${argCoordinate}" is declared in ${requiredIn} but not in ${missingIn} (where "${fieldCoordinate}" is @external)`, {
26
+ extensions: {
27
+ code: 'EXTERNAL_ARGUMENT_MISSING',
28
+ },
29
+ }));
29
30
  },
30
31
  };
31
32
  }
@@ -35,13 +35,15 @@ export function validateDirectiveAgainstOriginal(providedDirectiveNode, directiv
35
35
  const isNonNullableString = providedType === 'String!';
36
36
  const allowedFieldSetTypes = isFederationV2
37
37
  ? ['FieldSet!', 'federation__FieldSet!', '_FieldSet!']
38
- : ['_FieldSet!', 'String'];
38
+ : ['_FieldSet!', 'String', 'String!'];
39
39
  const fieldSetTypesInSpec = isFederationV2
40
40
  ? ['FieldSet!', 'federation__FieldSet!', '_FieldSet!']
41
41
  : ['_FieldSet!', 'FieldSet!', 'String'];
42
42
  const expectsFieldSet = fieldSetTypesInSpec.includes(expectedType);
43
43
  if (!isNonNullableString && expectsFieldSet) {
44
- const isOneOfExpected = allowedFieldSetTypes.includes(providedType);
44
+ const isOneOfExpected = allowedFieldSetTypes
45
+ .concat(allowedFieldSetTypes.map(f => `[${f}]!`))
46
+ .includes(providedType);
45
47
  if (!isOneOfExpected) {
46
48
  errors.push(new GraphQLError(`Invalid definition for directive "@${directiveName}": argument "${argName}" should have type "${expectedType}" but found type "${providedType}"`, {
47
49
  nodes: providedDirectiveNode,
@@ -66,10 +66,14 @@ export function KeyRules(context) {
66
66
  const knownObjectsAndInterfaces = context.getSubgraphObjectOrInterfaceTypes();
67
67
  let isValid = true;
68
68
  const fieldsUsedInKey = new Set();
69
+ const mergedTypeDef = context.getSubgraphObjectOrInterfaceTypes().get(typeDef.name.value);
70
+ if (!mergedTypeDef) {
71
+ throw new Error(`Could not find type "${typeDef.name.value}"`);
72
+ }
69
73
  visitFields({
70
74
  context,
71
75
  selectionSet,
72
- typeDefinition: typeDef,
76
+ typeDefinition: mergedTypeDef,
73
77
  interceptField(info) {
74
78
  if (info.typeDefinition.name.value === typeDef.name.value) {
75
79
  fieldsUsedInKey.add(info.fieldName);
@@ -126,11 +126,17 @@ export function ProvidesRules(context) {
126
126
  const fieldsArg = keyDirective.arguments?.find(arg => arg.name.value === 'fields' && arg.value.kind === Kind.STRING);
127
127
  if (fieldsArg) {
128
128
  const keyFields = parseFields(fieldsArg.value.value);
129
+ const mergedTypeDef = context
130
+ .getSubgraphObjectOrInterfaceTypes()
131
+ .get(info.typeDefinition.name.value);
132
+ if (!mergedTypeDef) {
133
+ throw new Error(`Could not find type "${info.typeDefinition.name.value}"`);
134
+ }
129
135
  if (keyFields) {
130
136
  visitFields({
131
137
  context,
132
138
  selectionSet: keyFields,
133
- typeDefinition: info.typeDefinition,
139
+ typeDefinition: mergedTypeDef,
134
140
  interceptField(keyFieldInfo) {
135
141
  if (keyFieldInfo.typeDefinition.name.value === info.typeDefinition.name.value &&
136
142
  keyFieldInfo.fieldName === info.fieldName) {
@@ -65,10 +65,16 @@ export function RequiresRules(context) {
65
65
  annotatedType.kind !== Kind.OBJECT_TYPE_EXTENSION) {
66
66
  return;
67
67
  }
68
+ const mergedTypeDef = context
69
+ .getSubgraphObjectOrInterfaceTypes()
70
+ .get(annotatedType.name.value);
71
+ if (!mergedTypeDef) {
72
+ throw new Error(`Could not find type "${annotatedType.name.value}"`);
73
+ }
68
74
  visitFields({
69
75
  context,
70
76
  selectionSet,
71
- typeDefinition: annotatedType,
77
+ typeDefinition: mergedTypeDef,
72
78
  interceptField(info) {
73
79
  if (info.typeDefinition.kind === Kind.OBJECT_TYPE_DEFINITION ||
74
80
  info.typeDefinition.kind === Kind.OBJECT_TYPE_EXTENSION) {
@@ -61,10 +61,27 @@ export function createSubgraphValidationContext(subgraph, federation, typeNodeIn
61
61
  const { version, imports } = federation;
62
62
  const availableSpec = createSpecSchema(version, imports);
63
63
  const knownSpec = createSpecSchema(version);
64
- const knownSubgraphEntities = new Map(subgraph.typeDefs.definitions.filter(def => def.kind === Kind.OBJECT_TYPE_DEFINITION ||
64
+ const knownSubgraphEntities = new Map();
65
+ for (const def of subgraph.typeDefs.definitions.filter(def => def.kind === Kind.OBJECT_TYPE_DEFINITION ||
65
66
  def.kind === Kind.OBJECT_TYPE_EXTENSION ||
66
67
  def.kind === Kind.INTERFACE_TYPE_DEFINITION ||
67
- def.kind === Kind.INTERFACE_TYPE_EXTENSION).map(def => [def.name.value, def]));
68
+ def.kind === Kind.INTERFACE_TYPE_EXTENSION)) {
69
+ const found = knownSubgraphEntities.get(def.name.value);
70
+ if (!found) {
71
+ knownSubgraphEntities.set(def.name.value, { ...def });
72
+ continue;
73
+ }
74
+ found.fields = (found.fields ?? []).concat(def.fields ?? []);
75
+ found.interfaces = (found.interfaces ?? []).concat(def.interfaces ?? []);
76
+ found.directives = (found.directives ?? []).concat(def.directives ?? []);
77
+ if (def.kind === Kind.OBJECT_TYPE_DEFINITION && found.kind === Kind.OBJECT_TYPE_EXTENSION) {
78
+ found.kind = Kind.OBJECT_TYPE_DEFINITION;
79
+ }
80
+ else if (def.kind === Kind.INTERFACE_TYPE_DEFINITION &&
81
+ found.kind === Kind.INTERFACE_TYPE_EXTENSION) {
82
+ found.kind = Kind.INTERFACE_TYPE_DEFINITION;
83
+ }
84
+ }
68
85
  const knownSubgraphDirectiveDefinitions = new Map(subgraph.typeDefs.definitions.filter(def => def.kind === Kind.DIRECTIVE_DEFINITION).map(def => [def.name.value, def]));
69
86
  const leafTypeNames = new Set(specifiedScalarTypes.map(type => type.name));
70
87
  for (const def of subgraph.typeDefs.definitions) {
@@ -2,27 +2,28 @@ import { GraphQLError } from 'graphql';
2
2
  export function ExternalArgumentMissingRule(context) {
3
3
  return {
4
4
  ObjectTypeFieldArg(objectState, fieldState, argState) {
5
- if (argState.type.endsWith('!')) {
6
- if (argState.byGraph.size === 1 && fieldState.byGraph.size === argState.byGraph.size) {
7
- return;
8
- }
9
- if (fieldState.byGraph.size === argState.byGraph.size) {
10
- return;
11
- }
12
- const graphsWithRequiredArg = Array.from(argState.byGraph)
13
- .filter(([graph, arg]) => arg.type.endsWith('!') && fieldState.byGraph.get(graph)?.external !== true)
14
- .map(([graph]) => graph);
15
- const externalGraphsWithoutArg = Array.from(fieldState.byGraph.keys()).filter(graph => !argState.byGraph.has(graph) && fieldState.byGraph.get(graph)?.external === true);
16
- const requiredIn = `subgraph${graphsWithRequiredArg.length > 1 ? 's' : ''} "${graphsWithRequiredArg.map(context.graphIdToName).join('", "')}"`;
17
- const missingIn = `subgraph${externalGraphsWithoutArg.length > 1 ? 's' : ''} "${externalGraphsWithoutArg.map(context.graphIdToName).join('", "')}"`;
18
- const fieldCoordinate = `${objectState.name}.${fieldState.name}`;
19
- const argCoordinate = `${objectState.name}.${fieldState.name}(${argState.name}:)`;
20
- context.reportError(new GraphQLError(`Field "${fieldCoordinate}" is missing argument "${argCoordinate}" in some subgraphs where it is marked @external: argument "${argCoordinate}" is declared in ${requiredIn} but not in ${missingIn} (where "${fieldCoordinate}" is @external)`, {
21
- extensions: {
22
- code: 'EXTERNAL_ARGUMENT_MISSING',
23
- },
24
- }));
5
+ if (argState.byGraph.size === 1 && fieldState.byGraph.size === argState.byGraph.size) {
6
+ return;
25
7
  }
8
+ if (fieldState.byGraph.size === argState.byGraph.size) {
9
+ return;
10
+ }
11
+ const graphsWithRequiredArg = Array.from(argState.byGraph)
12
+ .filter(([graph, arg]) => fieldState.byGraph.get(graph)?.external !== true)
13
+ .map(([graph]) => graph);
14
+ const externalGraphsWithoutArg = Array.from(fieldState.byGraph.keys()).filter(graph => !argState.byGraph.has(graph) && fieldState.byGraph.get(graph)?.external === true);
15
+ if (!externalGraphsWithoutArg.length) {
16
+ return;
17
+ }
18
+ const requiredIn = `subgraph${graphsWithRequiredArg.length > 1 ? 's' : ''} "${graphsWithRequiredArg.map(context.graphIdToName).join('", "')}"`;
19
+ const missingIn = `subgraph${externalGraphsWithoutArg.length > 1 ? 's' : ''} "${externalGraphsWithoutArg.map(context.graphIdToName).join('", "')}"`;
20
+ const fieldCoordinate = `${objectState.name}.${fieldState.name}`;
21
+ const argCoordinate = `${objectState.name}.${fieldState.name}(${argState.name}:)`;
22
+ context.reportError(new GraphQLError(`Field "${fieldCoordinate}" is missing argument "${argCoordinate}" in some subgraphs where it is marked @external: argument "${argCoordinate}" is declared in ${requiredIn} but not in ${missingIn} (where "${fieldCoordinate}" is @external)`, {
23
+ extensions: {
24
+ code: 'EXTERNAL_ARGUMENT_MISSING',
25
+ },
26
+ }));
26
27
  },
27
28
  };
28
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theguild/federation-composition",
3
- "version": "0.6.1",
3
+ "version": "0.6.2-rc-20231219171219-079169e",
4
4
  "description": "Open Source Composition library for Apollo Federation",
5
5
  "peerDependencies": {
6
6
  "graphql": "^16.0.0"