Skip to content

Commit c644c2d

Browse files
committed
ensure we emit only one link per feature
1 parent 49a0542 commit c644c2d

2 files changed

Lines changed: 76 additions & 19 deletions

File tree

composition-js/src/__tests__/connectors.test.ts

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ describe("connect spec and join__directive", () => {
124124
it("composes v0.2", () => {
125125
const subgraphs = [
126126
{
127-
name: "with-connectors",
127+
name: "with-connectors-v0_2",
128128
typeDefs: parse(`
129129
extend schema
130130
@link(
@@ -148,6 +148,31 @@ describe("connect spec and join__directive", () => {
148148
}
149149
`),
150150
},
151+
{
152+
name: "with-connectors-v0_1",
153+
typeDefs: parse(`
154+
extend schema
155+
@link(
156+
url: "https://specs.apollo.dev/federation/v2.10"
157+
import: ["@key"]
158+
)
159+
@link(
160+
url: "https://specs.apollo.dev/connect/v0.1"
161+
import: ["@connect", "@source"]
162+
)
163+
@source(name: "v1", http: { baseURL: "http://v1" })
164+
165+
type Query {
166+
widgets: [Widget!]!
167+
@connect(source: "v1", http: { GET: "/widgets" }, selection: "")
168+
}
169+
170+
type Widget @key(fields: "id") {
171+
id: ID!
172+
name: String!
173+
}
174+
`),
175+
},
151176
];
152177

153178
const result = composeServices(subgraphs);
@@ -158,8 +183,9 @@ describe("connect spec and join__directive", () => {
158183
@link(url: \\"https://specs.apollo.dev/link/v1.0\\")
159184
@link(url: \\"https://specs.apollo.dev/join/v0.5\\", for: EXECUTION)
160185
@link(url: \\"https://specs.apollo.dev/connect/v0.2\\", for: EXECUTION)
161-
@join__directive(graphs: [WITH_CONNECTORS], name: \\"link\\", args: {url: \\"https://specs.apollo.dev/connect/v0.2\\", import: [\\"@connect\\", \\"@source\\"]})
162-
@join__directive(graphs: [WITH_CONNECTORS], name: \\"source\\", args: {name: \\"v1\\", http: {baseURL: \\"http://v1\\"}})
186+
@join__directive(graphs: [WITH_CONNECTORS_V0_1_], name: \\"link\\", args: {url: \\"https://specs.apollo.dev/connect/v0.1\\", import: [\\"@connect\\", \\"@source\\"]})
187+
@join__directive(graphs: [WITH_CONNECTORS_V0_2_], name: \\"link\\", args: {url: \\"https://specs.apollo.dev/connect/v0.2\\", import: [\\"@connect\\", \\"@source\\"]})
188+
@join__directive(graphs: [WITH_CONNECTORS_V0_1_, WITH_CONNECTORS_V0_2_], name: \\"source\\", args: {name: \\"v1\\", http: {baseURL: \\"http://v1\\"}})
163189
{
164190
query: Query
165191
}
@@ -195,7 +221,8 @@ describe("connect spec and join__directive", () => {
195221
scalar link__Import
196222
197223
enum join__Graph {
198-
WITH_CONNECTORS @join__graph(name: \\"with-connectors\\", url: \\"\\")
224+
WITH_CONNECTORS_V0_1_ @join__graph(name: \\"with-connectors-v0_1\\", url: \\"\\")
225+
WITH_CONNECTORS_V0_2_ @join__graph(name: \\"with-connectors-v0_2\\", url: \\"\\")
199226
}
200227
201228
scalar join__FieldSet
@@ -212,14 +239,23 @@ describe("connect spec and join__directive", () => {
212239
}
213240
214241
type Query
215-
@join__type(graph: WITH_CONNECTORS)
242+
@join__type(graph: WITH_CONNECTORS_V0_1_)
243+
@join__type(graph: WITH_CONNECTORS_V0_2_)
216244
{
217-
resources: [Resource!]! @join__directive(graphs: [WITH_CONNECTORS], name: \\"connect\\", args: {source: \\"v1\\", http: {GET: \\"/resources\\"}, selection: \\"\\"})
245+
widgets: [Widget!]! @join__field(graph: WITH_CONNECTORS_V0_1_) @join__directive(graphs: [WITH_CONNECTORS_V0_1_], name: \\"connect\\", args: {source: \\"v1\\", http: {GET: \\"/widgets\\"}, selection: \\"\\"})
246+
resources: [Resource!]! @join__field(graph: WITH_CONNECTORS_V0_2_) @join__directive(graphs: [WITH_CONNECTORS_V0_2_], name: \\"connect\\", args: {source: \\"v1\\", http: {GET: \\"/resources\\"}, selection: \\"\\"})
247+
}
248+
249+
type Widget
250+
@join__type(graph: WITH_CONNECTORS_V0_1_, key: \\"id\\")
251+
{
252+
id: ID!
253+
name: String!
218254
}
219255
220256
type Resource
221-
@join__type(graph: WITH_CONNECTORS, key: \\"id\\")
222-
@join__directive(graphs: [WITH_CONNECTORS], name: \\"connect\\", args: {source: \\"v1\\", http: {GET: \\"/resources\\"}, selection: \\"\\"})
257+
@join__type(graph: WITH_CONNECTORS_V0_2_, key: \\"id\\")
258+
@join__directive(graphs: [WITH_CONNECTORS_V0_2_], name: \\"connect\\", args: {source: \\"v1\\", http: {GET: \\"/resources\\"}, selection: \\"\\"})
223259
{
224260
id: ID!
225261
name: String!
@@ -228,15 +264,21 @@ describe("connect spec and join__directive", () => {
228264

229265
if (result.schema) {
230266
expect(printSchema(result.schema.toAPISchema())).toMatchInlineSnapshot(`
231-
"type Query {
232-
resources: [Resource!]!
233-
}
234-
235-
type Resource {
236-
id: ID!
237-
name: String!
238-
}"
239-
`);
267+
"type Query {
268+
widgets: [Widget!]!
269+
resources: [Resource!]!
270+
}
271+
272+
type Widget {
273+
id: ID!
274+
name: String!
275+
}
276+
277+
type Resource {
278+
id: ID!
279+
name: String!
280+
}"
281+
`);
240282
}
241283
});
242284

composition-js/src/merging/merge.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,7 +1102,7 @@ class Merger {
11021102
if (!source) {
11031103
continue;
11041104
}
1105-
1105+
11061106
const sourceMetadata = this.subgraphs.values()[idx].metadata();
11071107
const keyDirective = sourceMetadata.keyDirective();
11081108
if (source.hasAppliedDirective(keyDirective)) {
@@ -3208,7 +3208,22 @@ class Merger {
32083208
}
32093209

32103210
const linkDirective = this.linkSpec.coreDirective(this.merged);
3211-
for (const link of linksToPersist) {
3211+
3212+
// When persisting features as @link directives in the supergraph, we can't
3213+
// repeat features that have the same identity, but different versions. This
3214+
// chooses the highest version of each feature to persist.
3215+
//
3216+
// (The original feature version is still recorded in a @join__directive
3217+
// so we're not losing any information.)
3218+
const highestLinkByIdentity = [...linksToPersist].reduce((map, link) => {
3219+
const existing = map.get(link.identity);
3220+
if (!existing || existing.version.lt(link.version)) {
3221+
map.set(link.identity, link);
3222+
}
3223+
return map;
3224+
}, new Map<string, FeatureDefinition>());
3225+
3226+
for (const [_, link] of highestLinkByIdentity) {
32123227
dest.applyDirective(linkDirective, {
32133228
url: link.toString(),
32143229
for: link.defaultCorePurpose,

0 commit comments

Comments
 (0)