Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/early-cats-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@apollo/composition": patch
---

Fixed `@composeDirective` logic to use correct source for validation

`validateAndFilterExternal` was passing `source.name` (the field name)
instead of `this.names[i]` (the subgraph name) to `isMergedDirective`.
This caused `shouldComposeDirective` to miss `@composeDirective`-composed
directives on external fields, since it looked up the directive under
a nonexistent subgraph name.
36 changes: 36 additions & 0 deletions composition-js/src/__tests__/compose.composeDirective.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1200,4 +1200,40 @@ describe('composing custom core directives', () => {
const appliedDirectives = schema.elementByCoordinate('Query.shared')?.appliedDirectives;
expect(appliedDirectives?.map(d => [d.name, d.arguments()])).toMatchObject([['auth', { scope: 'VIEWER'}], ['auth', {}]]);
});

it('rejects composed directive on external field', () => {
const subgraphA = {
typeDefs: gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@shareable"])
type Query { a: Int }
type Product @key(fields: "id") @shareable {
id: ID!
name: String!
}
`,
name: 'subgraphA',
};

const subgraphB = {
typeDefs: gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@external", "@composeDirective", "@requires"])
@link(url: "https://custom.dev/custom/v1.0", import: ["@custom"])
@composeDirective(name: "@custom")
directive @custom(scope: String!) on FIELD_DEFINITION | OBJECT
type Product @key(fields: "id") {
id: ID!
name: String! @external @custom(scope: "read")
description: String @requires(fields: "name") @custom(scope: "read")
}
`,
name: 'subgraphB',
};

const result = composeServices([subgraphA, subgraphB]);
expect(errors(result)).toStrictEqual([
['MERGED_DIRECTIVE_APPLICATION_ON_EXTERNAL', '[subgraphB] Cannot apply merged directive @custom(scope: "read") to external field "Product.name"'],
]);
});
});
2 changes: 1 addition & 1 deletion composition-js/src/merging/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1593,7 +1593,7 @@ class Merger {
// Note that if we change our mind on this semantic and wanted directives on external to propagate, then we'll also
// need to update the merging of fields since external fields are filtered out (by this very method).
for (const directive of source.appliedDirectives) {
if (this.isMergedDirective(source.name, directive)) {
if (this.isMergedDirective(this.names[i], directive)) {
// Contrarily to most of the errors during merging that "merge" errors for related elements, we're logging one
// error for every application here. But this is because there error is somewhat subgraph specific and is
// unlikely to span multiple subgraphs. In fact, we could almost have thrown this error during subgraph validation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ For a comprehensive changelog for Apollo Federation and its associated libraries

- If you maintain a [subgraph-compatible library](/graphos/reference/federation/compatible-subgraphs), consult this article to stay current with recently added directives. All of these directive definitions are also listed in the [subgraph specification](/graphos/reference/federation/subgraph-spec/#subgraph-schema-additions).

## v2.14

<hr/>

| First release | Available in GraphOS? | Minimum router version |
| ------------- | --------------------- | ---------------------- |
| **May 2026** | **Yes** | **`2.14.0`** |

#### Composition and validation changes

#### `@interfaceObject` alongside Federation 1 subgraphs

Previously, composition failed whenever a Federation 2 subgraph used `@interfaceObject` and any Federation 1 subgraph was in the graph. The rule is now scoped to a single type name: composition reports an error only if a Federation 2 subgraph uses `@interfaceObject` on a type `T` and a Federation 1 subgraph defines `@key` on an interface that is also named `T`. `@interfaceObject` on other types can coexist with Federation 1 subgraphs when that specific conflict does not occur.

#### `@composeDirective` and custom specifications

When custom directives from the same spec are merged into the supergraph via `@composeDirective`, subgraphs on the latest version of that spec no longer need to each declare every composed directive. A directive definition must appear in at least one of those subgraphs. For example, one subgraph can link a spec, compose both `@foo` and `@bar`, and define both directives, while another subgraph links the same spec, composes only `@foo`, and defines only `@foo`.

#### `@link` usage

Composition validates `@link` to prevent name conflicts between linked specs and imported definitions. Disambiguate conflicting names with `import:` (and `as:` when needed). If you linked `https://specs.apollo.dev/tag` or `https://specs.apollo.dev/inaccessible` as separate specs, import `@tag` and `@inaccessible` from the [`federation` specification](/graphos/reference/federation/directives) instead; relying on those standalone links was tied to behavior that this release fixes.

## v2.13

<hr/>
Expand All @@ -46,7 +68,7 @@ Federation v2.13 is a prerequisite for the Connector specification version 0.4.
| **November 2025** | **Yes** | **`2.8.0`** |

Federation v2.12 is a prerequisite for the Connector specification version 0.3.
[Learn more.](/graphos/connectors/reference/changelog)
[Learn more.](/graphos/connectors/reference/changelog#connectors-03--federation-2120)

#### Directive changes

Expand Down