Skip to content

feat: expose subgraph defined directives#2287

Merged
wilsonrivera merged 3 commits intomainfrom
wilson/eng-8364-expose-subgraph-directive
Oct 17, 2025
Merged

feat: expose subgraph defined directives#2287
wilsonrivera merged 3 commits intomainfrom
wilson/eng-8364-expose-subgraph-directive

Conversation

@wilsonrivera
Copy link
Copy Markdown
Contributor

@wilsonrivera wilsonrivera commented Oct 17, 2025

Summary by CodeRabbit

  • New Features

    • Directive definitions are now tracked and exposed in subgraph and normalization configurations for improved schema introspection.
  • Refactor

    • Directive name mappings now use strongly-typed identifiers instead of plain strings for enhanced type safety and clarity.

Checklist

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 17, 2025

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (1)
  • composition-go/index.global.js

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request adds directive definition mapping capabilities across the normalization and subgraph composition layers. It introduces directiveDefinitionByDirectiveName fields to public types, changes the key type for directive maps from string to the strongly-typed DirectiveName, and propagates these changes through factory implementations and test coverage.

Changes

Cohort / File(s) Summary
Type System Updates
composition/src/normalization/types.ts, composition/src/subgraph/types.ts
Added directiveDefinitionByDirectiveName: Map<DirectiveName, DirectiveDefinitionNode> field to NormalizationSuccess, SubgraphConfig, and InternalSubgraph. Updated persistedDirectiveDefinitionDataByDirectiveName key type from string to DirectiveName in InternalSubgraph. Imported DirectiveName and DirectiveDefinitionNode types.
Constant Definitions
composition/src/v1/utils/constants.ts
Changed directive-name map key types from string to DirectiveName for BASE_DIRECTIVE_DEFINITION_BY_DIRECTIVE_NAME and V2_DIRECTIVE_DEFINITION_BY_DIRECTIVE_NAME. Updated ALL_IN_BUILT_DIRECTIVE_NAMES from Set<string> to Set<DirectiveName>.
Factory Implementation
composition/src/v1/federation/federation-factory.ts
Added directiveDefinitionByDirectiveName: subgraph.directiveDefinitionByDirectiveName propagation when constructing subgraphConfigBySubgraphName map entries.
Normalization Factory
composition/src/v1/normalization/normalization-factory.ts
Updated NormalizationFactory.directiveDefinitionByDirectiveName from Map<string, DirectiveDefinitionNode> to Map<DirectiveName, DirectiveDefinitionNode>. Modified NormalizationResult and BatchNormalizationResult return shapes to include the directiveDefinitionByDirectiveName field. Updated normalize() and batchNormalize() to propagate and return directive definitions through result structures.
Test Coverage
composition/tests/v1/federation-factory.test.ts
Exported BASE_DIRECTIVE_DEFINITION_BY_DIRECTIVE_NAME and V2_DIRECTIVE_DEFINITION_BY_DIRECTIVE_NAME constants. Updated FederateSubgraphsSuccess return destructuring to access subgraphConfigBySubgraphName. Added assertions validating directiveDefinitionByDirectiveName presence and cardinality in subgraph configurations. Added subgraph-b directive definition and corresponding validation tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Rationale: Changes span five files across type definitions, constants, and factory implementations with heterogeneous modifications (type signature changes, field additions, map key type migrations, and propagation logic). The public API surface expands significantly with stricter type constraints on directive maps. While individual changes follow consistent patterns, each layer requires independent reasoning to verify correctness of propagation and type alignment. Test coverage additions validate the changes but require verification of assertion completeness.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: expose subgraph defined directives" directly aligns with the primary objective of the changeset. The modifications across multiple files center on exposing directive definitions through new fields (directiveDefinitionByDirectiveName) in public types like NormalizationSuccess, SubgraphConfig, and InternalSubgraph, along with updating the type system from string-based keys to strongly-typed DirectiveName keys. The title accurately captures this core intent and is both concise and specific, avoiding vague language while clearly communicating the functional change to reviewers.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
composition/src/v1/normalization/normalization-factory.ts (1)

3448-3491: Keep exposed directiveDefinitionByDirectiveName in sync with all injected definitions

You push several directive definitions into definitions (EDFS-related, subscription filter, configure*, link, oneOf, requireFetchReasons, semanticNonNull), but not all are added to directiveDefinitionByDirectiveName. For consumers relying on the exposed map, this drifts from the actual SDL produced.

Suggestion: whenever a directive definition is pushed into definitions, also upsert it into directiveDefinitionByDirectiveName.

Apply the following additions near the existing pushes:

@@
   for (const directiveName of this.edfsDirectiveReferences) {
     const directiveDefinition = EVENT_DRIVEN_DIRECTIVE_DEFINITIONS_BY_DIRECTIVE_NAME.get(directiveName);
     if (!directiveDefinition) {
       // should never happen
       this.errors.push(invalidEdfsDirectiveName(directiveName));
       continue;
     }
     definitions.push(directiveDefinition);
+    this.directiveDefinitionByDirectiveName.set(directiveDefinition.name.value, directiveDefinition);
   }
@@
   if (this.edfsDirectiveReferences.size > 0 && this.referencedDirectiveNames.has(SUBSCRIPTION_FILTER)) {
     definitions.push(SUBSCRIPTION_FILTER_DEFINITION);
     definitions.push(SUBSCRIPTION_FILTER_CONDITION_DEFINITION);
     definitions.push(SUBSCRIPTION_FIELD_CONDITION_DEFINITION);
     definitions.push(SUBSCRIPTION_FILTER_VALUE_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(SUBSCRIPTION_FILTER, SUBSCRIPTION_FILTER_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(SUBSCRIPTION_FILTER_CONDITION, SUBSCRIPTION_FILTER_CONDITION_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(SUBSCRIPTION_FIELD_CONDITION, SUBSCRIPTION_FIELD_CONDITION_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(SUBSCRIPTION_FILTER_VALUE, SUBSCRIPTION_FILTER_VALUE_DEFINITION);
   }
@@
   if (this.referencedDirectiveNames.has(CONFIGURE_DESCRIPTION)) {
     definitions.push(CONFIGURE_DESCRIPTION_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(CONFIGURE_DESCRIPTION, CONFIGURE_DESCRIPTION_DEFINITION);
   }
@@
   if (this.referencedDirectiveNames.has(CONFIGURE_CHILD_DESCRIPTIONS)) {
     definitions.push(CONFIGURE_CHILD_DESCRIPTIONS_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(CONFIGURE_CHILD_DESCRIPTIONS, CONFIGURE_CHILD_DESCRIPTIONS_DEFINITION);
   }
@@
   if (this.referencedDirectiveNames.has(LINK)) {
     definitions.push(LINK_DEFINITION);
     definitions.push(LINK_IMPORT_DEFINITION);
     definitions.push(LINK_PURPOSE_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(LINK, LINK_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(LINK_IMPORT, LINK_IMPORT_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(LINK_PURPOSE, LINK_PURPOSE_DEFINITION);
   }
@@
   if (this.referencedDirectiveNames.has(ONE_OF)) {
     definitions.push(ONE_OF_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(ONE_OF, ONE_OF_DEFINITION);
   }
@@
   if (this.referencedDirectiveNames.has(REQUIRE_FETCH_REASONS)) {
     definitions.push(REQUIRE_FETCH_REASONS_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(REQUIRE_FETCH_REASONS, REQUIRE_FETCH_REASONS_DEFINITION);
   }
@@
   if (this.referencedDirectiveNames.has(SEMANTIC_NON_NULL)) {
     definitions.push(SEMANTIC_NON_NULL_DEFINITION);
+    this.directiveDefinitionByDirectiveName.set(SEMANTIC_NON_NULL, SEMANTIC_NON_NULL_DEFINITION);
   }

Also applies to: 3576-3579

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a94ffee and 195a1f3.

📒 Files selected for processing (6)
  • composition/src/normalization/types.ts (3 hunks)
  • composition/src/subgraph/types.ts (3 hunks)
  • composition/src/v1/federation/federation-factory.ts (2 hunks)
  • composition/src/v1/normalization/normalization-factory.ts (4 hunks)
  • composition/src/v1/utils/constants.ts (4 hunks)
  • composition/tests/v1/federation-factory.test.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
composition/src/subgraph/types.ts (2)
composition/src/types/types.ts (1)
  • DirectiveName (3-3)
composition/src/schema-building/types.ts (1)
  • PersistedDirectiveDefinitionData (193-200)
composition/src/normalization/types.ts (1)
composition/src/types/types.ts (1)
  • DirectiveName (3-3)
composition/src/v1/utils/constants.ts (1)
composition/src/types/types.ts (1)
  • DirectiveName (3-3)
composition/tests/v1/federation-factory.test.ts (3)
composition/tests/utils/utils.ts (2)
  • federateSubgraphsSuccess (59-72)
  • schemaToSortedNormalizedString (27-29)
composition/src/router-compatibility-version/router-compatibility-version.ts (1)
  • ROUTER_COMPATIBILITY_VERSION_ONE (3-3)
composition/src/v1/utils/constants.ts (2)
  • BASE_DIRECTIVE_DEFINITION_BY_DIRECTIVE_NAME (481-497)
  • V2_DIRECTIVE_DEFINITION_BY_DIRECTIVE_NAME (864-873)
composition/src/v1/normalization/normalization-factory.ts (1)
composition/src/types/types.ts (1)
  • DirectiveName (3-3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)
🔇 Additional comments (12)
composition/src/v1/federation/federation-factory.ts (2)

2820-2829: LGTM! Directive definitions propagated correctly.

The addition of directiveDefinitionByDirectiveName to the subgraph configuration is correct and properly propagates the directive definitions from the internal subgraph representation to the public API.


3112-3121: LGTM! Contract flow also propagates directive definitions.

Good consistency—the contract federation flow also correctly propagates directiveDefinitionByDirectiveName, ensuring directive definitions are available in both normal and contract federation results.

composition/src/subgraph/types.ts (1)

1-1: LGTM! Public API expanded to expose directive definitions.

The changes consistently add directiveDefinitionByDirectiveName to both the internal (InternalSubgraph) and public (SubgraphConfig) subgraph types. The type refinement on line 37 from Map<string, ...> to Map<DirectiveName, ...> improves semantic clarity.

Note: SubgraphConfig is a public type, so adding a required field expands the API surface. This is appropriate for exposing subgraph-defined directives to consumers.

Also applies to: 9-9, 19-19, 29-29, 37-37

composition/src/v1/utils/constants.ts (1)

106-106: LGTM! Type refinements improve semantic clarity.

The changes refine directive-related types from generic string/Map<string, ...>/Set<string> to the more specific DirectiveName type alias. This improves code readability and type safety by making the intent explicit—these are directive names, not arbitrary strings.

Since DirectiveName is a type alias for string (as shown in the relevant snippets), there are no runtime behavior changes, only compile-time type improvements.

Also applies to: 481-497, 499-529, 864-873

composition/src/normalization/types.ts (1)

2-2: LGTM! Initialization verified.

✅ Verified that directiveDefinitionByDirectiveName is properly initialized in the sole NormalizationSuccess return statement at composition/src/v1/normalization/normalization-factory.ts:3757. The field is correctly set to this.directiveDefinitionByDirectiveName and appears with all other required fields.

composition/src/v1/normalization/normalization-factory.ts (4)

379-381: LGTM: type import added

Importing DirectiveName aligns types across the file.


406-410: LGTM: key type strengthened to DirectiveName

Map<DirectiveName, DirectiveDefinitionNode> keeps semantics while clarifying intent.


3574-3580: LGTM: expose directiveDefinitionByDirectiveName on NormalizationResult

This is the right place to surface it to callers.


3858-3863: LGTM: propagate directives map into InternalSubgraph

This enables per-subgraph directive introspection later in composition.

composition/tests/v1/federation-factory.test.ts (3)

2-3: LGTM: import public directive definition maps

Using exported BASE_/V2_ maps is appropriate for assertions.

Also applies to: 16-18


527-531: LGTM: destructure to access subgraph configs

Makes the test clearer and avoids repeated property access.


1149-1151: LGTM: custom directive for subgraph-b

Adding @A verifies exposure of user-defined directives.

Comment thread composition/tests/v1/federation-factory.test.ts
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Oct 17, 2025

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-b1c3b068e0a474241be297ec4c00a6eb4fcfcdef

Copy link
Copy Markdown
Member

@Aenimus Aenimus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wilsonrivera wilsonrivera merged commit d77a760 into main Oct 17, 2025
33 checks passed
@wilsonrivera wilsonrivera deleted the wilson/eng-8364-expose-subgraph-directive branch October 17, 2025 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants