-
Notifications
You must be signed in to change notification settings - Fork 240
feat(composition): add composeDirective #2703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
d70d6a7
3ba65a5
3d80ca9
8616632
0eefab2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,6 +71,7 @@ import { | |
| undefinedTypeError, | ||
| unexpectedNonCompositeOutputTypeError, | ||
| unknownFieldDataError, | ||
| composeDirectiveRepeatableConflictError, | ||
| unknownFieldSubgraphNameError, | ||
| unknownNamedTypeError, | ||
| } from '../../errors/errors'; | ||
|
|
@@ -138,6 +139,7 @@ import { | |
| } from '../../schema-building/types'; | ||
| import { | ||
| addValidPersistedDirectiveDefinitionNodeByData, | ||
| buildValidPersistedDirectiveDefinitionNode, | ||
| areKindsEqual, | ||
| compareAndValidateInputValueDefaultValues, | ||
| generateDeprecatedDirective, | ||
|
|
@@ -298,6 +300,7 @@ export class FederationFactory { | |
| [SEMANTIC_NON_NULL, SEMANTIC_NON_NULL_DEFINITION], | ||
| [TAG, TAG_DEFINITION], | ||
| ]); | ||
| composedDirectiveDefinitionDataByDirectiveName = new Map<DirectiveName, PersistedDirectiveDefinitionData>(); | ||
| potentialPersistedDirectiveDefinitionDataByDirectiveName = new Map<string, PersistedDirectiveDefinitionData>(); | ||
| referencedPersistedDirectiveNames = new Set<DirectiveName>(); | ||
| routerDefinitions: Array<MutableDefinitionNode | DefinitionNode> = []; | ||
|
|
@@ -1622,6 +1625,54 @@ export class FederationFactory { | |
| * This method is always necessary, regardless of whether federating a source graph or contract graph. | ||
| * */ | ||
| federateInternalSubgraphData() { | ||
| // Pre-pass: register composed directives before any type/field upserts so that | ||
| // extractPersistedDirectives can find them when walking type/field nodes. | ||
| for (const internalSubgraph of this.internalSubgraphBySubgraphName.values()) { | ||
| for (const [directiveName, data] of internalSubgraph.composedDirectiveDefinitionDataByDirectiveName) { | ||
| if (!this.persistedDirectiveDefinitionByDirectiveName.has(directiveName)) { | ||
| const node = internalSubgraph.directiveDefinitionByName.get(directiveName); | ||
| if (node) { | ||
| this.persistedDirectiveDefinitionByDirectiveName.set(directiveName, node); | ||
| } | ||
| } | ||
| const existing = this.composedDirectiveDefinitionDataByDirectiveName.get(directiveName); | ||
| if (!existing) { | ||
| const argumentDataByName = new Map<string, InputValueData>(); | ||
| for (const inputValueData of data.argumentDataByName.values()) { | ||
| this.namedInputValueTypeNames.add(getTypeNodeNamedTypeName(inputValueData.type)); | ||
| this.upsertInputValueData(argumentDataByName, inputValueData, `@${directiveName}`, false); | ||
| } | ||
| this.composedDirectiveDefinitionDataByDirectiveName.set(directiveName, { | ||
| argumentDataByName, | ||
| executableLocations: new Set(data.executableLocations), | ||
| locations: data.locations ? new Set(data.locations) : undefined, | ||
| name: data.name, | ||
| repeatable: data.repeatable, | ||
| subgraphNames: new Set(data.subgraphNames), | ||
| description: data.description, | ||
| }); | ||
| } else { | ||
| // Intersect locations so only mutually supported locations are emitted | ||
| if (existing.locations && data.locations) { | ||
| for (const loc of existing.locations) { | ||
| if (!data.locations.has(loc)) { | ||
| existing.locations.delete(loc); | ||
| } | ||
| } | ||
| } | ||
| setMutualExecutableLocations(existing, data.executableLocations); | ||
|
Comment on lines
+1660
to
+1668
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject directives whose mutual location set goes empty. This intersection can leave 🤖 Prompt for AI Agents |
||
| for (const inputValueData of data.argumentDataByName.values()) { | ||
| this.namedInputValueTypeNames.add(getTypeNodeNamedTypeName(inputValueData.type)); | ||
| this.upsertInputValueData(existing.argumentDataByName, inputValueData, `@${directiveName}`, false); | ||
| } | ||
| setLongestDescription(existing, data); | ||
| if (existing.repeatable !== data.repeatable) { | ||
| this.errors.push(composeDirectiveRepeatableConflictError(directiveName, existing.subgraphNames)); | ||
| } | ||
| addIterableToSet({ source: data.subgraphNames, target: existing.subgraphNames }); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| let subgraphNumber = 0; | ||
| let shouldSkipPersistedExecutableDirectives = false; | ||
| for (const internalSubgraph of this.internalSubgraphBySubgraphName.values()) { | ||
|
|
@@ -1993,6 +2044,7 @@ export class FederationFactory { | |
| } | ||
|
|
||
| pushParentDefinitionDataToDocumentDefinitions(interfaceImplementations: InterfaceImplementationData[]) { | ||
| const composedDirectiveNames = new Set(this.composedDirectiveDefinitionDataByDirectiveName.keys()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These client-schema helper calls currently strip composed directive usages.
Also applies to: 2066-2066, 2113-2113, 2137-2137, 2183-2183, 2209-2209, 2253-2253, 2271-2271, 2290-2290, 2358-2358, 2379-2379 🤖 Prompt for AI Agents |
||
| for (const [parentTypeName, parentDefinitionData] of this.parentDefinitionDataByTypeName) { | ||
| if (parentDefinitionData.extensionType !== ExtensionType.NONE) { | ||
| this.errors.push(noBaseDefinitionForExtensionError(kindToNodeType(parentDefinitionData.kind), parentTypeName)); | ||
|
|
@@ -2011,7 +2063,7 @@ export class FederationFactory { | |
| const isValueInaccessible = isNodeDataInaccessible(enumValueData); | ||
| const clientEnumValueNode: MutableEnumValueNode = { | ||
| ...enumValueData.node, | ||
| directives: getClientPersistedDirectiveNodes(enumValueData), | ||
| directives: getClientPersistedDirectiveNodes(enumValueData, composedDirectiveNames), | ||
| }; | ||
| switch (mergeMethod) { | ||
| case MergeMethod.CONSISTENT: | ||
|
|
@@ -2058,7 +2110,7 @@ export class FederationFactory { | |
| } | ||
| this.clientDefinitions.push({ | ||
| ...parentDefinitionData.node, | ||
| directives: getClientPersistedDirectiveNodes(parentDefinitionData), | ||
| directives: getClientPersistedDirectiveNodes(parentDefinitionData, composedDirectiveNames), | ||
| values: clientEnumValueNodes, | ||
| }); | ||
| break; | ||
|
|
@@ -2082,7 +2134,7 @@ export class FederationFactory { | |
| } | ||
| clientInputValueNodes.push({ | ||
| ...inputValueData.node, | ||
| directives: getClientPersistedDirectiveNodes(inputValueData), | ||
| directives: getClientPersistedDirectiveNodes(inputValueData, composedDirectiveNames), | ||
| }); | ||
| } else if (isTypeRequired(inputValueData.type)) { | ||
| invalidRequiredInputs.push({ | ||
|
|
@@ -2128,7 +2180,7 @@ export class FederationFactory { | |
| } | ||
| this.clientDefinitions.push({ | ||
| ...parentDefinitionData.node, | ||
| directives: getClientPersistedDirectiveNodes(parentDefinitionData), | ||
| directives: getClientPersistedDirectiveNodes(parentDefinitionData, composedDirectiveNames), | ||
| fields: clientInputValueNodes, | ||
| }); | ||
| break; | ||
|
|
@@ -2154,7 +2206,7 @@ export class FederationFactory { | |
| if (isNodeDataInaccessible(fieldData)) { | ||
| continue; | ||
| } | ||
| clientSchemaFieldNodes.push(getClientSchemaFieldNodeByFieldData(fieldData)); | ||
| clientSchemaFieldNodes.push(getClientSchemaFieldNodeByFieldData(fieldData, composedDirectiveNames)); | ||
| graphFieldDataByFieldName.set(fieldName, this.fieldDataToGraphFieldData(fieldData)); | ||
| } | ||
| if (isObject) { | ||
|
|
@@ -2198,7 +2250,7 @@ export class FederationFactory { | |
| } | ||
| this.clientDefinitions.push({ | ||
| ...parentDefinitionData.node, | ||
| directives: getClientPersistedDirectiveNodes(parentDefinitionData), | ||
| directives: getClientPersistedDirectiveNodes(parentDefinitionData, composedDirectiveNames), | ||
| fields: clientSchemaFieldNodes, | ||
| }); | ||
| break; | ||
|
|
@@ -2216,7 +2268,7 @@ export class FederationFactory { | |
| } | ||
| this.clientDefinitions.push({ | ||
| ...parentDefinitionData.node, | ||
| directives: getClientPersistedDirectiveNodes(parentDefinitionData), | ||
| directives: getClientPersistedDirectiveNodes(parentDefinitionData, composedDirectiveNames), | ||
| }); | ||
| break; | ||
| } | ||
|
|
@@ -2235,7 +2287,7 @@ export class FederationFactory { | |
| } | ||
| this.clientDefinitions.push({ | ||
| ...parentDefinitionData.node, | ||
| directives: getClientPersistedDirectiveNodes(parentDefinitionData), | ||
| directives: getClientPersistedDirectiveNodes(parentDefinitionData, composedDirectiveNames), | ||
| types: clientMembers, | ||
| }); | ||
| break; | ||
|
|
@@ -2303,6 +2355,7 @@ export class FederationFactory { | |
| validateInterfaceImplementationsAndPushToDocumentDefinitions( | ||
| interfaceImplementations: InterfaceImplementationData[], | ||
| ) { | ||
| const composedDirectiveNames = new Set(this.composedDirectiveDefinitionDataByDirectiveName.keys()); | ||
| for (const { data, clientSchemaFieldNodes } of interfaceImplementations) { | ||
| data.node.interfaces = this.getValidImplementedInterfaces(data); | ||
| this.routerDefinitions.push(this.getNodeForRouterSchemaByData(data)); | ||
|
|
@@ -2323,7 +2376,7 @@ export class FederationFactory { | |
| * */ | ||
| this.clientDefinitions.push({ | ||
| ...data.node, | ||
| directives: getClientPersistedDirectiveNodes(data), | ||
| directives: getClientPersistedDirectiveNodes(data, composedDirectiveNames), | ||
| fields: clientSchemaFieldNodes, | ||
| interfaces: clientInterfaces, | ||
| }); | ||
|
|
@@ -2859,6 +2912,17 @@ export class FederationFactory { | |
| this.errors, | ||
| ); | ||
| } | ||
| for (const data of this.composedDirectiveDefinitionDataByDirectiveName.values()) { | ||
| const node = buildValidPersistedDirectiveDefinitionNode( | ||
| data, | ||
| this.persistedDirectiveDefinitionByDirectiveName, | ||
| this.errors, | ||
| ); | ||
| if (node) { | ||
| this.routerDefinitions.push(node); | ||
| this.clientDefinitions.push(node); | ||
| } | ||
| } | ||
| const definitionsWithInterfaces: InterfaceImplementationData[] = []; | ||
| this.pushParentDefinitionDataToDocumentDefinitions(definitionsWithInterfaces); | ||
| this.validateInterfaceImplementationsAndPushToDocumentDefinitions(definitionsWithInterfaces); | ||
|
|
@@ -3182,6 +3246,17 @@ export class FederationFactory { | |
| this.errors, | ||
| ); | ||
| } | ||
| for (const data of this.composedDirectiveDefinitionDataByDirectiveName.values()) { | ||
| const node = buildValidPersistedDirectiveDefinitionNode( | ||
| data, | ||
| this.persistedDirectiveDefinitionByDirectiveName, | ||
| this.errors, | ||
| ); | ||
| if (node) { | ||
| this.routerDefinitions.push(node); | ||
| this.clientDefinitions.push(node); | ||
| } | ||
| } | ||
| const interfaceImplementations: InterfaceImplementationData[] = []; | ||
| this.pushParentDefinitionDataToDocumentDefinitions(interfaceImplementations); | ||
| this.validateInterfaceImplementationsAndPushToDocumentDefinitions(interfaceImplementations); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.