├── .env ├── .github ├── CONTRIBUTING.md └── workflows │ └── ci.yaml ├── .gitignore ├── .node-version ├── LICENSE ├── README.md ├── REPORT.md ├── gateways ├── apollo-gateway │ ├── .gitignore │ ├── gateway.json │ ├── install.sh │ ├── results.txt │ ├── run.sh │ └── run.ts ├── apollo-router │ ├── .gitignore │ ├── gateway.json │ ├── install.sh │ ├── results.txt │ ├── router.yaml │ └── run.sh ├── cosmo-router │ ├── .gitignore │ ├── config.yaml │ ├── gateway.json │ ├── install.sh │ ├── results.txt │ ├── run.sh │ ├── subgraphs │ │ └── .gitkeep │ └── wgc.ts ├── grafbase-gateway │ ├── .gitignore │ ├── gateway.json │ ├── grafbase.toml │ ├── install.sh │ ├── results.txt │ └── run.sh ├── hive-gateway │ ├── .gitignore │ ├── gateway.json │ ├── install.sh │ ├── results.txt │ └── run.sh └── inigo-gateway │ ├── .gitignore │ ├── gateway.json │ ├── gateway.yaml │ ├── inigo.ts │ ├── install.sh │ ├── results.txt │ ├── run.sh │ └── subgraphs │ └── .gitkeep ├── makefile ├── package-lock.json ├── package.json ├── renovate.json ├── src ├── cli.ts ├── env.ts ├── index.ts ├── subgraph.ts ├── summary.ts ├── supergraph.ts ├── test-suites │ ├── abstract-types │ │ ├── agency.subgraph.ts │ │ ├── books.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ ├── inventory.subgraph.ts │ │ ├── magazines.subgraph.ts │ │ ├── products.subgraph.ts │ │ ├── reviews.subgraph.ts │ │ ├── test.ts │ │ └── users.subgraph.ts │ ├── child-type-mismatch │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── circular-reference-interface │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── complex-entity-call │ │ ├── data.ts │ │ ├── index.ts │ │ ├── link.subgraph.ts │ │ ├── list.subgraph.ts │ │ ├── price.subgraph.ts │ │ ├── products.subgraph.ts │ │ └── test.ts │ ├── corrupted-supergraph-node-id │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── enum-intersection │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── fed1-external-extends-resolvable │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── fed1-external-extends │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── fed1-external-extension │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── fed2-external-extends │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── fed2-external-extension │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── include-skip │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── input-object-intersection │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── interface-object-with-requires │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── keys-mashup │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── mutations │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── mysterious-external │ │ ├── data.ts │ │ ├── index.ts │ │ ├── price.subgraph.ts │ │ ├── product.subgraph.ts │ │ └── test.ts │ ├── nested-provides │ │ ├── all-products.subgraph.ts │ │ ├── category.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ ├── subcategories.subgraph.ts │ │ └── test.ts │ ├── node │ │ ├── data.ts │ │ ├── index.ts │ │ ├── node-two.ts │ │ ├── node.subgraph.ts │ │ ├── test.ts │ │ └── types.subgraph.ts │ ├── non-resolvable-interface-object │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── null-keys │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── override-type-interface │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── override-with-requires │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── parent-entity-call-complex │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── d.subgraph.ts │ │ ├── index.ts │ │ └── test.ts │ ├── parent-entity-call │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── provides-on-interface │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── provides-on-union │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── requires-interface │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── requires-requires │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── d.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── requires-with-argument │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── d.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── requires-with-fragments │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── shared-root │ │ ├── category.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ ├── name.subgraph.ts │ │ ├── price.subgraph.ts │ │ └── test.ts │ ├── simple-entity-call │ │ ├── data.ts │ │ ├── email.subgraph.ts │ │ ├── index.ts │ │ ├── nickname.subgraph.ts │ │ └── test.ts │ ├── simple-inaccessible │ │ ├── age.subgraph.ts │ │ ├── data.ts │ │ ├── friends.subgraph.ts │ │ ├── index.ts │ │ └── test.ts │ ├── simple-interface-object │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── c.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── simple-override │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── simple-requires-provides │ │ ├── accounts.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ ├── inventory.subgraph.ts │ │ ├── products.subgraph.ts │ │ ├── reviews.subgraph.ts │ │ └── test.ts │ ├── typename │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── unavailable-override │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ ├── union-interface-distributed │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts │ └── union-intersection │ │ ├── README.md │ │ ├── a.subgraph.ts │ │ ├── b.subgraph.ts │ │ ├── data.ts │ │ ├── index.ts │ │ └── test.ts ├── test.ts └── testkit.ts ├── tsconfig.json └── website ├── data.json ├── index.html ├── og-image-new.png ├── og-image.png └── robots.txt /.env: -------------------------------------------------------------------------------- 1 | PUNISH_FOR_POOR_PLANS=0 -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guide 2 | 3 | 1. Create a new directory in the `gateways` folder with the name of the new gateway. 4 | 2. Prepare the `install.sh` script that will install the necessary dependencies, the gateway itself. 5 | 3. Create the `run.sh` script that will start the gateway. See an example using [supergraph.graphql](../gateways/grafbase-gateway/run.sh) and [subgraphs information](../gateways/cosmo-router/run.sh). 6 | 4. Add the `gateway.json` file with the necessary information about the gateway. See an example [here](../gateways/hive-gateway/gateway.json). 7 | 5. The `.gitignore` should be created as well to avoid committing unnecessary files. 8 | 6. Execute `make test-[name of the gateway]` to test your gateway. 9 | 7. Run `make summary` to include your gateway in the results. 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store 4 | gateways/*/logs/*.log -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 22.16 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 - The Guild 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gateways/apollo-gateway/.gitignore: -------------------------------------------------------------------------------- 1 | supergraph.graphql -------------------------------------------------------------------------------- /gateways/apollo-gateway/gateway.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Apollo Gateway", 3 | "repository": "https://github.com/apollographql/federation", 4 | "website": "https://www.apollographql.com/", 5 | "graphql": "http://127.0.0.1:4000/graphql", 6 | "health": "http://127.0.0.1:4000/health" 7 | } 8 | -------------------------------------------------------------------------------- /gateways/apollo-gateway/install.sh: -------------------------------------------------------------------------------- 1 | echo "OK" -------------------------------------------------------------------------------- /gateways/apollo-gateway/results.txt: -------------------------------------------------------------------------------- 1 | abstract-types 2 | ................. 3 | child-type-mismatch 4 | .... 5 | circular-reference-interface 6 | .. 7 | complex-entity-call 8 | . 9 | corrupted-supergraph-node-id 10 | .......... 11 | enum-intersection 12 | ..... 13 | fed1-external-extends 14 | .... 15 | fed1-external-extends-resolvable 16 | . 17 | fed1-external-extension 18 | .... 19 | fed2-external-extends 20 | .... 21 | fed2-external-extension 22 | .... 23 | include-skip 24 | .... 25 | input-object-intersection 26 | ... 27 | interface-object-with-requires 28 | ....... 29 | keys-mashup 30 | X 31 | mutations 32 | ... 33 | mysterious-external 34 | .. 35 | nested-provides 36 | .. 37 | node 38 | . 39 | non-resolvable-interface-object 40 | .X..... 41 | null-keys 42 | . 43 | override-type-interface 44 | .... 45 | override-with-requires 46 | .... 47 | parent-entity-call 48 | . 49 | parent-entity-call-complex 50 | . 51 | provides-on-interface 52 | .. 53 | provides-on-union 54 | .. 55 | requires-interface 56 | ..... 57 | requires-requires 58 | ..... 59 | requires-with-argument 60 | ..XXX 61 | requires-with-fragments 62 | ...... 63 | shared-root 64 | .. 65 | simple-entity-call 66 | . 67 | simple-inaccessible 68 | .... 69 | simple-interface-object 70 | ............. 71 | simple-override 72 | .. 73 | simple-requires-provides 74 | ............ 75 | typename 76 | ...... 77 | unavailable-override 78 | .. 79 | union-interface-distributed 80 | ....... 81 | union-intersection 82 | ........ 83 | 84 | --- 85 | Total: 179 86 | Passed: 174 87 | Failed: 5 -------------------------------------------------------------------------------- /gateways/apollo-gateway/run.sh: -------------------------------------------------------------------------------- 1 | npm start supergraph -- --cwd ./gateways/apollo-gateway --test $1 2 | npx tsx ./run.ts 3 | -------------------------------------------------------------------------------- /gateways/apollo-gateway/run.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from "@apollo/server"; 2 | import { createServer } from "node:http"; 3 | import { expressMiddleware } from "@apollo/server/express4"; 4 | import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer"; 5 | import express from "express"; 6 | import { ApolloGateway } from "@apollo/gateway"; 7 | import { readFileSync } from "fs"; 8 | 9 | const supergraphSdl = readFileSync("./supergraph.graphql").toString(); 10 | 11 | const gateway = new ApolloGateway({ 12 | supergraphSdl, 13 | }); 14 | 15 | const app = express(); 16 | const httpServer = createServer(app); 17 | 18 | const server = new ApolloServer({ 19 | gateway, 20 | plugins: [ApolloServerPluginDrainHttpServer({ httpServer })], 21 | }); 22 | 23 | await server.start(); 24 | 25 | app.use("/graphql", express.json(), expressMiddleware(server)); 26 | app.use("/health", (_, res) => { 27 | if (gateway.__testing().state.phase === "loaded") { 28 | res.status(200).send("OK"); 29 | } else { 30 | res.status(503).send("Gateway: " + gateway.__testing().state.phase); 31 | } 32 | }); 33 | 34 | await new Promise((resolve) => 35 | httpServer.listen({ port: 4000 }, resolve), 36 | ); 37 | console.log(`🚀 Server ready at http://localhost:4000/`); 38 | -------------------------------------------------------------------------------- /gateways/apollo-router/.gitignore: -------------------------------------------------------------------------------- 1 | /router 2 | supergraph.graphql -------------------------------------------------------------------------------- /gateways/apollo-router/gateway.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Apollo Router", 3 | "repository": "https://github.com/apollographql/router", 4 | "website": "https://www.apollographql.com/", 5 | "graphql": "http://127.0.0.1:4000/", 6 | "health": "http://127.0.0.1:8088/health" 7 | } 8 | -------------------------------------------------------------------------------- /gateways/apollo-router/install.sh: -------------------------------------------------------------------------------- 1 | # https://github.com/apollographql/router/releases 2 | curl -sSL https://router.apollo.dev/download/nix/v2.2.1 | sh 3 | -------------------------------------------------------------------------------- /gateways/apollo-router/results.txt: -------------------------------------------------------------------------------- 1 | abstract-types 2 | ................. 3 | child-type-mismatch 4 | .... 5 | circular-reference-interface 6 | .. 7 | complex-entity-call 8 | . 9 | corrupted-supergraph-node-id 10 | .......... 11 | enum-intersection 12 | ..... 13 | fed1-external-extends 14 | .... 15 | fed1-external-extends-resolvable 16 | . 17 | fed1-external-extension 18 | .... 19 | fed2-external-extends 20 | .... 21 | fed2-external-extension 22 | .... 23 | include-skip 24 | .... 25 | input-object-intersection 26 | ... 27 | interface-object-with-requires 28 | ....... 29 | keys-mashup 30 | X 31 | mutations 32 | ... 33 | mysterious-external 34 | .. 35 | nested-provides 36 | .. 37 | node 38 | . 39 | non-resolvable-interface-object 40 | ....... 41 | null-keys 42 | . 43 | override-type-interface 44 | .... 45 | override-with-requires 46 | .... 47 | parent-entity-call 48 | . 49 | parent-entity-call-complex 50 | . 51 | provides-on-interface 52 | .. 53 | provides-on-union 54 | .. 55 | requires-interface 56 | ..... 57 | requires-requires 58 | ..... 59 | requires-with-argument 60 | ..XXX 61 | requires-with-fragments 62 | ...... 63 | shared-root 64 | .. 65 | simple-entity-call 66 | . 67 | simple-inaccessible 68 | .... 69 | simple-interface-object 70 | ............. 71 | simple-override 72 | .. 73 | simple-requires-provides 74 | ............ 75 | typename 76 | ...... 77 | unavailable-override 78 | .. 79 | union-interface-distributed 80 | ....... 81 | union-intersection 82 | ........ 83 | 84 | --- 85 | Total: 179 86 | Passed: 175 87 | Failed: 4 -------------------------------------------------------------------------------- /gateways/apollo-router/router.yaml: -------------------------------------------------------------------------------- 1 | sandbox: 2 | enabled: true 3 | homepage: 4 | enabled: false 5 | supergraph: 6 | listen: "0.0.0.0:4000" 7 | introspection: true 8 | plugins: 9 | # Enable with the header, Apollo-Expose-Query-Plan: true 10 | experimental.expose_query_plan: true 11 | -------------------------------------------------------------------------------- /gateways/apollo-router/run.sh: -------------------------------------------------------------------------------- 1 | npm start supergraph -- --cwd ./gateways/apollo-router --test $1 2 | ./router --supergraph supergraph.graphql --config router.yaml -------------------------------------------------------------------------------- /gateways/cosmo-router/.gitignore: -------------------------------------------------------------------------------- 1 | /subgraphs/*.graphql 2 | /cosmo 3 | /config.json 4 | /compose.yaml 5 | /subgraphs.json -------------------------------------------------------------------------------- /gateways/cosmo-router/config.yaml: -------------------------------------------------------------------------------- 1 | dev_mode: true 2 | # Generated file 3 | router_config_path: config.json 4 | listen_addr: 127.0.0.1:4000 5 | graph: 6 | token: "" 7 | -------------------------------------------------------------------------------- /gateways/cosmo-router/gateway.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cosmo Router", 3 | "repository": "https://github.com/wundergraph/cosmo", 4 | "website": "https://wundergraph.com", 5 | "graphql": "http://127.0.0.1:4000/graphql", 6 | "health": "http://127.0.0.1:4000/health/ready" 7 | } 8 | -------------------------------------------------------------------------------- /gateways/cosmo-router/results.txt: -------------------------------------------------------------------------------- 1 | abstract-types 2 | ................. 3 | child-type-mismatch 4 | XXX. 5 | circular-reference-interface 6 | .. 7 | complex-entity-call 8 | X 9 | corrupted-supergraph-node-id 10 | .......... 11 | enum-intersection 12 | ..... 13 | fed1-external-extends 14 | .... 15 | fed1-external-extends-resolvable 16 | . 17 | fed1-external-extension 18 | .... 19 | fed2-external-extends 20 | .... 21 | fed2-external-extension 22 | .... 23 | include-skip 24 | .... 25 | input-object-intersection 26 | ... 27 | interface-object-with-requires 28 | ....... 29 | keys-mashup 30 | . 31 | mutations 32 | ... 33 | mysterious-external 34 | .. 35 | nested-provides 36 | .. 37 | node 38 | . 39 | non-resolvable-interface-object 40 | ....... 41 | null-keys 42 | . 43 | override-type-interface 44 | .... 45 | override-with-requires 46 | .... 47 | parent-entity-call 48 | . 49 | parent-entity-call-complex 50 | X 51 | provides-on-interface 52 | XX 53 | provides-on-union 54 | XX 55 | requires-interface 56 | ..... 57 | requires-requires 58 | ..... 59 | requires-with-argument 60 | ..... 61 | requires-with-fragments 62 | ...... 63 | shared-root 64 | .. 65 | simple-entity-call 66 | . 67 | simple-inaccessible 68 | .... 69 | simple-interface-object 70 | ............. 71 | simple-override 72 | .. 73 | simple-requires-provides 74 | ............ 75 | typename 76 | ...... 77 | unavailable-override 78 | .. 79 | union-interface-distributed 80 | ....... 81 | union-intersection 82 | ........ 83 | 84 | --- 85 | Total: 179 86 | Passed: 170 87 | Failed: 9 -------------------------------------------------------------------------------- /gateways/cosmo-router/run.sh: -------------------------------------------------------------------------------- 1 | npm start subgraphs -- --cwd ./gateways/cosmo-router --test $1 2 | npx tsx ./wgc.js 3 | npx wgc router compose -i compose.yaml -o config.json 4 | ./cosmo -------------------------------------------------------------------------------- /gateways/cosmo-router/subgraphs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/federation-gateway-audit/6848cc3c43f6eb26fd777a2ee5f2ec9dfff0a5e6/gateways/cosmo-router/subgraphs/.gitkeep -------------------------------------------------------------------------------- /gateways/cosmo-router/wgc.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync, readFileSync, readdirSync, unlinkSync } from "node:fs"; 2 | 3 | for (const file of readdirSync("subgraphs")) { 4 | if (file.endsWith(".graphql")) { 5 | unlinkSync(`subgraphs/${file}`); 6 | } 7 | } 8 | 9 | const subgraphs: Array<{ 10 | name: string; 11 | url: string; 12 | sdl: string; 13 | }> = JSON.parse(readFileSync("subgraphs.json", "utf-8")); 14 | 15 | let yaml = ` 16 | version: 1 17 | subgraphs: 18 | `; 19 | 20 | for (const subgraph of subgraphs) { 21 | writeFileSync(`subgraphs/${subgraph.name}.graphql`, subgraph.sdl, "utf-8"); 22 | 23 | yaml += ` 24 | - name: ${subgraph.name} 25 | routing_url: ${subgraph.url} 26 | schema: 27 | file: ./subgraphs/${subgraph.name}.graphql 28 | `; 29 | } 30 | 31 | writeFileSync("compose.yaml", yaml, "utf-8"); 32 | -------------------------------------------------------------------------------- /gateways/grafbase-gateway/.gitignore: -------------------------------------------------------------------------------- 1 | /supergraph.graphql 2 | /grafbase-gateway -------------------------------------------------------------------------------- /gateways/grafbase-gateway/gateway.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Grafbase Gateway", 3 | "repository": "https://github.com/grafbase/grafbase", 4 | "website": "https://grafbase.com", 5 | "graphql": "http://127.0.0.1:4000/graphql", 6 | "health": "http://127.0.0.1:4000/health" 7 | } 8 | -------------------------------------------------------------------------------- /gateways/grafbase-gateway/grafbase.toml: -------------------------------------------------------------------------------- 1 | 2 | request_body_limit = "2MiB" 3 | -------------------------------------------------------------------------------- /gateways/grafbase-gateway/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # I took their install script and modified it to download the gateway binary 5 | # to the current directory instead of /usr/local/bin 6 | 7 | # https://github.com/grafbase/grafbase/releases 8 | VERSION="0.38.0" 9 | 10 | error() { 11 | echo -e "${Red}error${Color_Off}:" "$@" >&2 12 | exit 1 13 | } 14 | 15 | 16 | case $(uname -ms) in 17 | 'Darwin x86_64') 18 | # target=x86_64-apple-darwin 19 | target=aarch64-apple-darwin 20 | ;; 21 | 'Darwin arm64') 22 | target=aarch64-apple-darwin 23 | ;; 24 | 'Linux aarch64' | 'Linux arm64') 25 | target=aarch64-unknown-linux-musl 26 | ;; 27 | 'Linux x86_64' | *) 28 | target=x86_64-unknown-linux-musl 29 | ;; 30 | esac 31 | 32 | if [[ $target = darwin-x64 ]]; then 33 | # Is this process running in Rosetta? 34 | # redirect stderr to devnull to avoid error message when not running in Rosetta 35 | if [[ $(sysctl -n sysctl.proc_translated 2>/dev/null) = 1 ]]; then 36 | target=darwin-aarch64 37 | info "Your shell is running in Rosetta 2. Downloading grafbase-gateway for $target instead" 38 | fi 39 | fi 40 | 41 | gateway_uri=https://github.com/grafbase/grafbase/releases/download/gateway-$VERSION/grafbase-gateway-$target 42 | 43 | exe="./grafbase-gateway" 44 | 45 | echo "Downloading $gateway_uri" 46 | 47 | curl --fail --location --progress-bar --output "$exe" "$gateway_uri" || 48 | error "Failed to download grafbase gateway from \"$gateway_uri\"" 49 | 50 | chmod +x "$exe" || 51 | error 'Failed to set permissions on grafbase gateway executable' 52 | 53 | 54 | echo "success" 55 | -------------------------------------------------------------------------------- /gateways/grafbase-gateway/results.txt: -------------------------------------------------------------------------------- 1 | abstract-types 2 | ................. 3 | child-type-mismatch 4 | .... 5 | circular-reference-interface 6 | .. 7 | complex-entity-call 8 | . 9 | corrupted-supergraph-node-id 10 | X.X..X.... 11 | enum-intersection 12 | ..... 13 | fed1-external-extends 14 | .... 15 | fed1-external-extends-resolvable 16 | . 17 | fed1-external-extension 18 | .... 19 | fed2-external-extends 20 | .... 21 | fed2-external-extension 22 | .... 23 | include-skip 24 | .... 25 | input-object-intersection 26 | ... 27 | interface-object-with-requires 28 | ....... 29 | keys-mashup 30 | . 31 | mutations 32 | ... 33 | mysterious-external 34 | .. 35 | nested-provides 36 | .. 37 | node 38 | . 39 | non-resolvable-interface-object 40 | ....... 41 | null-keys 42 | . 43 | override-type-interface 44 | XXXX 45 | override-with-requires 46 | .... 47 | parent-entity-call 48 | . 49 | parent-entity-call-complex 50 | . 51 | provides-on-interface 52 | XX 53 | provides-on-union 54 | .X 55 | requires-interface 56 | ..... 57 | requires-requires 58 | ..... 59 | requires-with-argument 60 | ..... 61 | requires-with-fragments 62 | ...... 63 | shared-root 64 | .. 65 | simple-entity-call 66 | . 67 | simple-inaccessible 68 | .... 69 | simple-interface-object 70 | ........XXXXX 71 | simple-override 72 | .. 73 | simple-requires-provides 74 | ............ 75 | typename 76 | ...... 77 | unavailable-override 78 | .. 79 | union-interface-distributed 80 | ....... 81 | union-intersection 82 | ........ 83 | 84 | --- 85 | Total: 179 86 | Passed: 164 87 | Failed: 15 -------------------------------------------------------------------------------- /gateways/grafbase-gateway/run.sh: -------------------------------------------------------------------------------- 1 | npm start supergraph -- --cwd ./gateways/grafbase-gateway --test $1 2 | __GRAFBASE_RUST_LOG=engine_v2=trace ./grafbase-gateway --schema supergraph.graphql --listen-address 127.0.0.1:4000 -c grafbase.toml --log trace -------------------------------------------------------------------------------- /gateways/hive-gateway/.gitignore: -------------------------------------------------------------------------------- 1 | /supergraph.graphql 2 | /hive-gateway -------------------------------------------------------------------------------- /gateways/hive-gateway/gateway.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hive Gateway", 3 | "repository": "https://github.com/graphql-hive/gateway", 4 | "website": "https://the-guild.dev/graphql/hive/docs/gateway", 5 | "graphql": "http://127.0.0.1:4000/graphql", 6 | "health": "http://127.0.0.1:4000/healthcheck" 7 | } 8 | -------------------------------------------------------------------------------- /gateways/hive-gateway/install.sh: -------------------------------------------------------------------------------- 1 | # https://github.com/graphql-hive/gateway/releases 2 | curl -sSL https://graphql-hive.com/install-gateway.sh | sh -s "1.15.0" 3 | -------------------------------------------------------------------------------- /gateways/hive-gateway/results.txt: -------------------------------------------------------------------------------- 1 | abstract-types 2 | ................. 3 | child-type-mismatch 4 | .... 5 | circular-reference-interface 6 | .. 7 | complex-entity-call 8 | . 9 | corrupted-supergraph-node-id 10 | .......... 11 | enum-intersection 12 | ..... 13 | fed1-external-extends 14 | .... 15 | fed1-external-extends-resolvable 16 | . 17 | fed1-external-extension 18 | .... 19 | fed2-external-extends 20 | .... 21 | fed2-external-extension 22 | .... 23 | include-skip 24 | .... 25 | input-object-intersection 26 | ... 27 | interface-object-with-requires 28 | ....... 29 | keys-mashup 30 | . 31 | mutations 32 | ... 33 | mysterious-external 34 | .. 35 | nested-provides 36 | .. 37 | node 38 | . 39 | non-resolvable-interface-object 40 | ....... 41 | null-keys 42 | . 43 | override-type-interface 44 | .... 45 | override-with-requires 46 | .... 47 | parent-entity-call 48 | . 49 | parent-entity-call-complex 50 | . 51 | provides-on-interface 52 | .. 53 | provides-on-union 54 | .. 55 | requires-interface 56 | ..... 57 | requires-requires 58 | ..... 59 | requires-with-argument 60 | ..... 61 | requires-with-fragments 62 | ...... 63 | shared-root 64 | .. 65 | simple-entity-call 66 | . 67 | simple-inaccessible 68 | .... 69 | simple-interface-object 70 | ............. 71 | simple-override 72 | .. 73 | simple-requires-provides 74 | ............ 75 | typename 76 | ...... 77 | unavailable-override 78 | .. 79 | union-interface-distributed 80 | ....... 81 | union-intersection 82 | ........ 83 | 84 | --- 85 | Total: 179 86 | Passed: 179 87 | Failed: 0 -------------------------------------------------------------------------------- /gateways/hive-gateway/run.sh: -------------------------------------------------------------------------------- 1 | npm start supergraph -- --cwd ./gateways/hive-gateway --test $1 2 | ./hive-gateway supergraph supergraph.graphql --port 4000 --fork 1 3 | -------------------------------------------------------------------------------- /gateways/inigo-gateway/.gitignore: -------------------------------------------------------------------------------- 1 | /subgraphs/*.graphql 2 | /inigo 3 | /inigo_gateway 4 | /supergraph.graphql 5 | /local.yaml 6 | /subgraphs.json -------------------------------------------------------------------------------- /gateways/inigo-gateway/gateway.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Inigo Gateway", 3 | "repository": "https://github.com/inigolabs", 4 | "website": "https://inigo.io", 5 | "graphql": "http://127.0.0.1:4000/graphql", 6 | "health": "http://127.0.0.1:4000/health" 7 | } 8 | -------------------------------------------------------------------------------- /gateways/inigo-gateway/gateway.yaml: -------------------------------------------------------------------------------- 1 | listen_port: 4000 2 | graphql_route: /graphql 3 | 4 | health_check: 5 | enable: true 6 | path: /health 7 | -------------------------------------------------------------------------------- /gateways/inigo-gateway/inigo.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync, readFileSync, readdirSync, unlinkSync } from "node:fs"; 2 | 3 | for (const file of readdirSync("subgraphs")) { 4 | if (file.endsWith(".graphql")) { 5 | unlinkSync(`subgraphs/${file}`); 6 | } 7 | } 8 | 9 | const subgraphs: Array<{ 10 | name: string; 11 | url: string; 12 | sdl: string; 13 | }> = JSON.parse(readFileSync("subgraphs.json", "utf-8")); 14 | 15 | let yaml = ` 16 | kind: Gateway 17 | name: audit 18 | label: dev 19 | spec: 20 | composition: ApolloFederation_v2 21 | services: 22 | `; 23 | 24 | for (const subgraph of subgraphs) { 25 | writeFileSync(`subgraphs/${subgraph.name}.graphql`, subgraph.sdl, "utf-8"); 26 | 27 | yaml += ` 28 | - name: ${subgraph.name} 29 | url: ${subgraph.url} 30 | schema_files: 31 | - ./subgraphs/${subgraph.name}.graphql 32 | `; 33 | } 34 | 35 | writeFileSync("local.yaml", yaml, "utf-8"); 36 | -------------------------------------------------------------------------------- /gateways/inigo-gateway/results.txt: -------------------------------------------------------------------------------- 1 | abstract-types 2 | ...............XX 3 | child-type-mismatch 4 | XXX. 5 | circular-reference-interface 6 | .. 7 | complex-entity-call 8 | X 9 | corrupted-supergraph-node-id 10 | .XXXX..... 11 | enum-intersection 12 | XXXXX 13 | fed1-external-extends 14 | .... 15 | fed1-external-extends-resolvable 16 | X 17 | fed1-external-extension 18 | .... 19 | fed2-external-extends 20 | .... 21 | fed2-external-extension 22 | .... 23 | include-skip 24 | .... 25 | input-object-intersection 26 | .XX 27 | interface-object-with-requires 28 | XXXXXXX 29 | keys-mashup 30 | X 31 | mutations 32 | ..X 33 | mysterious-external 34 | .. 35 | nested-provides 36 | XX 37 | node 38 | . 39 | non-resolvable-interface-object 40 | .....XX 41 | null-keys 42 | X 43 | override-type-interface 44 | XX.. 45 | override-with-requires 46 | .... 47 | parent-entity-call 48 | . 49 | parent-entity-call-complex 50 | X 51 | provides-on-interface 52 | XX 53 | provides-on-union 54 | XX 55 | requires-interface 56 | XXXXX 57 | requires-requires 58 | ..... 59 | requires-with-argument 60 | XXXXX 61 | requires-with-fragments 62 | XXXXXX 63 | shared-root 64 | XX 65 | simple-entity-call 66 | X 67 | simple-inaccessible 68 | ..X. 69 | simple-interface-object 70 | XXXXXXXXXXXXX 71 | simple-override 72 | X. 73 | simple-requires-provides 74 | ............ 75 | typename 76 | XXXXXX 77 | unavailable-override 78 | X. 79 | union-interface-distributed 80 | XX..... 81 | union-intersection 82 | XXXXXXXX 83 | 84 | --- 85 | Total: 179 86 | Passed: 89 87 | Failed: 90 -------------------------------------------------------------------------------- /gateways/inigo-gateway/run.sh: -------------------------------------------------------------------------------- 1 | npm start subgraphs -- --cwd ./gateways/inigo-gateway --test $1 2 | npx tsx ./inigo.js 3 | 4 | ./inigo compose local.yaml > supergraph.graphql 5 | 6 | ./inigo_gateway --schema supergraph.graphql --config gateway.yaml -------------------------------------------------------------------------------- /gateways/inigo-gateway/subgraphs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/federation-gateway-audit/6848cc3c43f6eb26fd777a2ee5f2ec9dfff0a5e6/gateways/inigo-gateway/subgraphs/.gitkeep -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | BASE_URL := $(or $(BASE_URL), "http://localhost:4200") 2 | TEST_SUITE := $(or $(TEST_SUITE), "") 3 | REPORTER := $(or $(REPORTER), "dot") 4 | GATEWAYS_DIR := $(wildcard ./gateways/*) 5 | GATEWAY_IDS := $(notdir $(GATEWAYS_DIR)) 6 | 7 | define TEST_GATEWAY 8 | test-$(1): 9 | npm start -- test --cwd ./gateways/$(1) --run-script ./run.sh --reporter $(REPORTER) --graphql $(shell jq -r .graphql ./gateways/$(1)/gateway.json) --healthcheck $(shell jq -r .health ./gateways/$(1)/gateway.json) 10 | endef 11 | 12 | define TEST_SUITE_GATEWAY 13 | test-suite-$(1): 14 | npm start -- test-suite --test $(TEST_SUITE) --cwd ./gateways/$(1) --run-script ./run.sh --graphql $(shell jq -r .graphql ./gateways/$(1)/gateway.json) --healthcheck $(shell jq -r .health ./gateways/$(1)/gateway.json) 15 | endef 16 | 17 | define RUN_GATEWAY 18 | run-$(1): 19 | npm start -- start --test $(TEST_SUITE) --cwd ./gateways/$(1) --run-script ./run.sh --graphql $(shell jq -r .graphql ./gateways/$(1)/gateway.json) --healthcheck $(shell jq -r .health ./gateways/$(1)/gateway.json) 20 | endef 21 | 22 | # Install all dependencies of the project and the gateways 23 | install: 24 | npm install 25 | @for dir in ./gateways/*; do \ 26 | if [ -d $$dir ]; then \ 27 | echo "Installing $$dir"; \ 28 | (cd $$dir && ./install.sh); \ 29 | echo "\n"; \ 30 | fi; \ 31 | done 32 | 33 | # Start only the subgraphs, no gateway 34 | subgraphs: 35 | npm start -- serve 36 | 37 | # Generate the test and run targets for each gateway 38 | $(foreach gateway,$(GATEWAY_IDS),$(eval $(call TEST_GATEWAY,$(gateway)))) 39 | $(foreach gateway,$(GATEWAY_IDS),$(eval $(call RUN_GATEWAY,$(gateway)))) 40 | $(foreach gateway,$(GATEWAY_IDS),$(eval $(call TEST_SUITE_GATEWAY,$(gateway)))) 41 | 42 | # Run the tests for all gateways 43 | test-all: $(addprefix test-,$(GATEWAY_IDS)) summary 44 | 45 | # Update the report 46 | summary: 47 | npm run summary -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | "github>the-guild-org/shared-config:renovate" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/env.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | 3 | export const env = config().parsed ?? { 4 | PUNISH_FOR_POOR_PLANS: "1", 5 | }; 6 | 7 | export interface Env { 8 | PUNISH_FOR_POOR_PLANS?: "1" | "0"; 9 | } 10 | 11 | export function shouldPunishForPoorPlans(context: { env: Env }) { 12 | return context.env.PUNISH_FOR_POOR_PLANS === "1"; 13 | } 14 | -------------------------------------------------------------------------------- /src/test-suites/abstract-types/agency.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { agencies } from "./data.js"; 3 | 4 | export default createSubgraph("agency", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | type Agency @key(fields: "id") { 10 | id: ID! 11 | companyName: String 12 | } 13 | 14 | type Group @key(fields: "id") { 15 | id: ID! 16 | name: String 17 | } 18 | 19 | extend union PublisherType = Agency | Group 20 | `, 21 | resolvers: { 22 | Agency: { 23 | __resolveReference(key: { id: string }) { 24 | return agencies.find((a) => a.id === key.id); 25 | }, 26 | }, 27 | Group: { 28 | __resolveReference(key: { id: string }) { 29 | return { id: key.id, name: "Group " + key.id }; 30 | }, 31 | }, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /src/test-suites/abstract-types/books.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { books } from "./data.js"; 3 | 4 | export default createSubgraph("books", { 5 | typeDefs: /* GraphQL */ ` 6 | schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable", "@external", "@requires"] 10 | ) { 11 | query: Query 12 | } 13 | 14 | type Book @key(fields: "id") { 15 | id: ID! 16 | title: String 17 | } 18 | 19 | type Query { 20 | books: [Book] 21 | } 22 | `, 23 | resolvers: { 24 | Query: { 25 | books() { 26 | return books.map((book) => ({ 27 | __typename: "Book", 28 | id: book.id, 29 | title: book.title, 30 | })); 31 | }, 32 | }, 33 | Book: { 34 | __resolveReference(key: { id: string }) { 35 | const book = books.find((book) => book.id === key.id); 36 | 37 | return book 38 | ? { 39 | __typename: "Book", 40 | id: book.id, 41 | title: book.title, 42 | } 43 | : null; 44 | }, 45 | }, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/test-suites/abstract-types/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import inventorySubgraph from "./inventory.subgraph.js"; 3 | import booksSubgraph from "./books.subgraph.js"; 4 | import usersSubgraph from "./users.subgraph.js"; 5 | import productsSubgraph from "./products.subgraph.js"; 6 | import reviewsSubgraph from "./reviews.subgraph.js"; 7 | import magazinesSubgraph from "./magazines.subgraph.js"; 8 | import agencySubgraph from "./agency.subgraph.js"; 9 | import test from "./test.js"; 10 | 11 | export default serve( 12 | "abstract-types", 13 | [ 14 | agencySubgraph, 15 | inventorySubgraph, 16 | booksSubgraph, 17 | usersSubgraph, 18 | reviewsSubgraph, 19 | productsSubgraph, 20 | magazinesSubgraph, 21 | ], 22 | test, 23 | ); 24 | -------------------------------------------------------------------------------- /src/test-suites/abstract-types/magazines.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { magazines } from "./data.js"; 3 | 4 | export default createSubgraph("magazines", { 5 | typeDefs: /* GraphQL */ ` 6 | schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) { 11 | query: Query 12 | } 13 | 14 | type Magazine @key(fields: "id") { 15 | id: ID! 16 | title: String 17 | } 18 | 19 | type Query { 20 | magazines: [Magazine] 21 | } 22 | `, 23 | resolvers: { 24 | Query: { 25 | magazines() { 26 | return magazines.map((magazine) => ({ 27 | __typename: magazine.__typename, 28 | id: magazine.id, 29 | title: magazine.title, 30 | })); 31 | }, 32 | }, 33 | Magazine: { 34 | __resolveReference(key: { id: string }) { 35 | const magazine = magazines.find((magazine) => magazine.id === key.id); 36 | 37 | return magazine 38 | ? { 39 | __typename: magazine.__typename, 40 | id: magazine.id, 41 | title: magazine.title, 42 | } 43 | : null; 44 | }, 45 | }, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/test-suites/abstract-types/users.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products, users } from "./data.js"; 3 | 4 | export default createSubgraph("users", { 5 | typeDefs: /* GraphQL */ ` 6 | type User @key(fields: "email") { 7 | email: ID! 8 | name: String 9 | totalProductsCreated: Int 10 | } 11 | `, 12 | resolvers: { 13 | User: { 14 | __resolveReference(key: { email: string }) { 15 | const user = users.find((user) => user.email === key.email); 16 | 17 | return user 18 | ? { 19 | __typename: "User", 20 | id: user.id, 21 | email: user.email, 22 | name: user.name, 23 | } 24 | : null; 25 | }, 26 | totalProductsCreated(user: { id: string }) { 27 | return products.filter((p) => p.createdBy === user.id).length; 28 | }, 29 | }, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /src/test-suites/child-type-mismatch/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@shareable"] 10 | ) 11 | 12 | type User { 13 | id: ID @shareable 14 | } 15 | 16 | type Query { 17 | users: [User!]! 18 | } 19 | `, 20 | resolvers: { 21 | Query: { 22 | users() { 23 | return users.map((u) => ({ __typename: "User", id: u.id })); 24 | }, 25 | }, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /src/test-suites/child-type-mismatch/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | function accounts() { 5 | return [ 6 | ...users.map((user) => ({ __typename: "User", ...user })), 7 | { __typename: "Admin", id: "a1", name: "a1-name" }, 8 | ]; 9 | } 10 | 11 | export default createSubgraph("b", { 12 | typeDefs: /* GraphQL */ ` 13 | extend schema 14 | @link( 15 | url: "https://specs.apollo.dev/federation/v2.3" 16 | import: ["@shareable", "@key"] 17 | ) 18 | 19 | union Account = User | Admin 20 | 21 | type User @key(fields: "id") { 22 | id: ID! 23 | name: String 24 | similarAccounts: [Account!]! 25 | } 26 | 27 | type Admin { 28 | id: ID 29 | name: String @shareable 30 | similarAccounts: [Account!]! 31 | } 32 | 33 | type Query { 34 | accounts: [Account!]! 35 | } 36 | `, 37 | resolvers: { 38 | Query: { 39 | accounts, 40 | }, 41 | User: { 42 | __resolveReference(key: { id: string }) { 43 | return users.find((user) => user.id === key.id); 44 | }, 45 | similarAccounts: accounts, 46 | }, 47 | Admin: { 48 | similarAccounts: accounts, 49 | }, 50 | }, 51 | }); 52 | -------------------------------------------------------------------------------- /src/test-suites/child-type-mismatch/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | name: "u1-name", 5 | }, 6 | ]; 7 | -------------------------------------------------------------------------------- /src/test-suites/child-type-mismatch/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("child-type-mismatch", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/circular-reference-interface/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { books } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@external", "@provides"] 10 | ) 11 | 12 | type Query { 13 | product: Product 14 | } 15 | 16 | interface Product { 17 | samePriceProduct: Product 18 | } 19 | 20 | type Book implements Product @key(fields: "id") { 21 | id: ID! 22 | samePriceProduct: Book @provides(fields: "price") 23 | price: Float @external 24 | } 25 | `, 26 | resolvers: { 27 | Query: { 28 | product() { 29 | const book = books[0]; 30 | return { 31 | __typename: book.__typename, 32 | id: book.id, 33 | price: book.price, 34 | }; 35 | }, 36 | }, 37 | Book: { 38 | samePriceProduct(book: { id: string; price: number }) { 39 | const samePriceBook = books.find( 40 | (b) => b.price === book.price && b.id !== book.id, 41 | ); 42 | 43 | if (!samePriceBook) { 44 | return null; 45 | } 46 | 47 | return { 48 | __typename: samePriceBook.__typename, 49 | id: samePriceBook.id, 50 | price: samePriceBook.price, 51 | }; 52 | }, 53 | }, 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /src/test-suites/circular-reference-interface/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { books } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Book @key(fields: "id") { 13 | id: ID! 14 | price: Float @shareable 15 | } 16 | `, 17 | 18 | resolvers: { 19 | Book: { 20 | __resolveReference(key: { id: string }) { 21 | const book = books.find((b) => b.id === key.id); 22 | 23 | if (!book) { 24 | return null; 25 | } 26 | 27 | return { 28 | __typename: book.__typename, 29 | id: book.id, 30 | price: book.price, 31 | }; 32 | }, 33 | }, 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /src/test-suites/circular-reference-interface/data.ts: -------------------------------------------------------------------------------- 1 | export const books = [ 2 | { 3 | __typename: "Book", 4 | id: "1", 5 | price: 10.99, 6 | }, 7 | { 8 | __typename: "Book", 9 | id: "2", 10 | price: 5.99, 11 | }, 12 | { 13 | __typename: "Book", 14 | id: "3", 15 | price: 10.99, 16 | }, 17 | ]; 18 | -------------------------------------------------------------------------------- /src/test-suites/circular-reference-interface/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("circular-reference-interface", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/circular-reference-interface/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | product { 8 | samePriceProduct { 9 | ... on Product { 10 | samePriceProduct { 11 | __typename 12 | } 13 | } 14 | } 15 | } 16 | } 17 | `, 18 | { 19 | data: { 20 | product: { 21 | samePriceProduct: { 22 | samePriceProduct: { 23 | __typename: "Book", 24 | }, 25 | }, 26 | }, 27 | }, 28 | }, 29 | ), 30 | createTest( 31 | /* GraphQL */ ` 32 | query { 33 | product { 34 | __typename 35 | samePriceProduct { 36 | __typename 37 | ... on Book { 38 | id 39 | } 40 | samePriceProduct { 41 | __typename 42 | ... on Book { 43 | id 44 | } 45 | } 46 | } 47 | ... on Book { 48 | __typename 49 | id 50 | price 51 | samePriceProduct { 52 | id 53 | price 54 | } 55 | } 56 | } 57 | } 58 | `, 59 | { 60 | data: { 61 | product: { 62 | __typename: "Book", 63 | samePriceProduct: { 64 | __typename: "Book", 65 | id: "3", 66 | samePriceProduct: { 67 | __typename: "Book", 68 | id: "1", 69 | }, 70 | price: 10.99, 71 | }, 72 | id: "1", 73 | price: 10.99, 74 | }, 75 | }, 76 | }, 77 | ), 78 | ]; 79 | -------------------------------------------------------------------------------- /src/test-suites/complex-entity-call/data.ts: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | id: "1", 4 | pid: "p1", 5 | categoryId: "c1", 6 | price: 100, 7 | }, 8 | { 9 | id: "2", 10 | pid: "p2", 11 | categoryId: "c2", 12 | price: 200, 13 | }, 14 | ]; 15 | 16 | export const categories = [ 17 | { 18 | id: "c1", 19 | tag: "t1", 20 | mainProduct: "1", 21 | }, 22 | { 23 | id: "c2", 24 | tag: "t2", 25 | mainProduct: "2", 26 | }, 27 | ]; 28 | -------------------------------------------------------------------------------- /src/test-suites/complex-entity-call/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import link from "./link.subgraph.js"; 3 | import list from "./list.subgraph.js"; 4 | import price from "./price.subgraph.js"; 5 | import products from "./products.subgraph.js"; 6 | import test from "./test.js"; 7 | 8 | export default serve( 9 | "complex-entity-call", 10 | [link, list, price, products], 11 | test, 12 | ); 13 | -------------------------------------------------------------------------------- /src/test-suites/complex-entity-call/link.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("link", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) 8 | 9 | type Product @key(fields: "id") @key(fields: "id pid") { 10 | id: String! 11 | pid: String! 12 | } 13 | `, 14 | resolvers: { 15 | Product: { 16 | __resolveReference( 17 | key: 18 | | { 19 | id: string; 20 | pid: string; 21 | } 22 | | { 23 | id: string; 24 | }, 25 | ) { 26 | if ("pid" in key) { 27 | return products.find( 28 | (product) => product.id === key.id && product.pid === key.pid, 29 | ); 30 | } 31 | 32 | return products.find((product) => product.id === key.id); 33 | }, 34 | }, 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /src/test-suites/complex-entity-call/list.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("list", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.0" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type ProductList @key(fields: "products{id pid}") { 13 | products: [Product!]! 14 | first: Product @shareable 15 | selected: Product @shareable 16 | } 17 | 18 | type Product @key(fields: "id pid") { 19 | id: String! 20 | pid: String 21 | } 22 | `, 23 | resolvers: { 24 | ProductList: { 25 | __resolveReference(key: { products: { id: string; pid: string }[] }) { 26 | const prods = products.filter((p) => 27 | key.products.some((k) => k.id === p.id && k.pid === p.pid), 28 | ); 29 | return { 30 | products: prods, 31 | first: prods[0], 32 | selected: prods[1], 33 | }; 34 | }, 35 | }, 36 | Product: { 37 | __resolveReference(key: { id: string; pid: string }) { 38 | return products.find( 39 | (product) => product.id === key.id && product.pid === key.pid, 40 | ); 41 | }, 42 | }, 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /src/test-suites/complex-entity-call/products.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { categories, products } from "./data.js"; 3 | 4 | export default createSubgraph("products", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.0" 9 | import: ["@key", "@external", "@extends", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | topProducts: ProductList! 14 | } 15 | 16 | type ProductList @key(fields: "products{id}") { 17 | products: [Product!]! 18 | } 19 | 20 | type Product @extends @key(fields: "id") { 21 | id: String! @external 22 | category: Category @shareable 23 | } 24 | 25 | type Category @key(fields: "id") { 26 | mainProduct: Product! @shareable 27 | id: String! 28 | tag: String @shareable 29 | } 30 | `, 31 | resolvers: { 32 | Query: { 33 | topProducts() { 34 | return { 35 | products, 36 | }; 37 | }, 38 | }, 39 | ProductList: { 40 | __resolveReference(key: { products: { id: string }[] }) { 41 | return { 42 | products: products.filter((p) => 43 | key.products.some((k) => k.id === p.id), 44 | ), 45 | }; 46 | }, 47 | }, 48 | Product: { 49 | __resolveReference(key: { id: string }) { 50 | return products.find((product) => product.id === key.id); 51 | }, 52 | category(product: (typeof products)[number]) { 53 | return categories.find((c) => c.id === product.categoryId); 54 | }, 55 | }, 56 | Category: { 57 | __resolveReference(key: { id: string }) { 58 | return categories.find((c) => c.id === key.id); 59 | }, 60 | mainProduct(category: (typeof categories)[number]) { 61 | return products.find((p) => p.id === category.mainProduct); 62 | }, 63 | }, 64 | }, 65 | }); 66 | -------------------------------------------------------------------------------- /src/test-suites/complex-entity-call/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | topProducts { 8 | products { 9 | id 10 | pid 11 | price { 12 | price 13 | } 14 | category { 15 | mainProduct { 16 | id 17 | } 18 | id 19 | tag 20 | } 21 | } 22 | selected { 23 | id 24 | } 25 | first { 26 | id 27 | } 28 | } 29 | } 30 | `, 31 | { 32 | data: { 33 | topProducts: { 34 | products: [ 35 | { 36 | id: "1", 37 | pid: "p1", 38 | price: { 39 | price: 100, 40 | }, 41 | category: { 42 | mainProduct: { 43 | id: "1", 44 | }, 45 | id: "c1", 46 | tag: "t1", 47 | }, 48 | }, 49 | { 50 | id: "2", 51 | pid: "p2", 52 | price: { 53 | price: 200, 54 | }, 55 | category: { 56 | mainProduct: { 57 | id: "2", 58 | }, 59 | id: "c2", 60 | tag: "t2", 61 | }, 62 | }, 63 | ], 64 | selected: { 65 | id: "2", 66 | }, 67 | first: { 68 | id: "1", 69 | }, 70 | }, 71 | }, 72 | }, 73 | ), 74 | ]; 75 | -------------------------------------------------------------------------------- /src/test-suites/corrupted-supergraph-node-id/data.ts: -------------------------------------------------------------------------------- 1 | export const accounts = [ 2 | { 3 | __typename: "Account", 4 | id: "a1", 5 | username: "a1-username", 6 | }, 7 | ]; 8 | 9 | export const chats = [ 10 | { 11 | __typename: "Chat", 12 | id: "c1", 13 | accountId: "a1", 14 | text: "c1-text", 15 | }, 16 | ]; 17 | -------------------------------------------------------------------------------- /src/test-suites/corrupted-supergraph-node-id/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("corrupted-supergraph-node-id", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/enum-intersection/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | users: [User] 14 | } 15 | 16 | type User @key(fields: "id") { 17 | id: ID 18 | type: UserType @shareable 19 | } 20 | 21 | enum UserType { 22 | REGULAR 23 | } 24 | `, 25 | resolvers: { 26 | Query: { 27 | users() { 28 | return users; 29 | }, 30 | }, 31 | User: { 32 | __resolveReference(key: { id: string }) { 33 | const user = users.find((u) => u.id === key.id); 34 | 35 | if (!user) { 36 | return null; 37 | } 38 | 39 | return { 40 | id: user.id, 41 | type: user.type, 42 | }; 43 | }, 44 | }, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /src/test-suites/enum-intersection/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@inaccessible", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | usersByType(type: UserType!): [User!] 14 | usersB: [User!] 15 | } 16 | 17 | extend type User @key(fields: "id") { 18 | id: ID 19 | type: UserType @shareable 20 | } 21 | 22 | enum UserType { 23 | ANONYMOUS @inaccessible 24 | REGULAR 25 | } 26 | `, 27 | resolvers: { 28 | Query: { 29 | usersByType(_: {}, { type }: { type: string }) { 30 | return users.filter((u) => u.type === type); 31 | }, 32 | usersB() { 33 | return users; 34 | }, 35 | }, 36 | User: { 37 | __resolveReference(key: { id: string }) { 38 | const user = users.find((u) => u.id === key.id); 39 | 40 | if (!user) { 41 | return null; 42 | } 43 | 44 | return { 45 | id: user.id, 46 | type: user.type, 47 | }; 48 | }, 49 | }, 50 | }, 51 | }); 52 | -------------------------------------------------------------------------------- /src/test-suites/enum-intersection/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | type: "REGULAR", 5 | }, 6 | { 7 | id: "u2", 8 | type: "ANONYMOUS", 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /src/test-suites/enum-intersection/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("enum-intersection", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extends-resolvable/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | type Query { 7 | productInA: Product 8 | } 9 | 10 | type Product @key(fields: "id") { 11 | id: ID! 12 | name: String 13 | pid: ID 14 | } 15 | `, 16 | resolvers: { 17 | Query: { 18 | productInA() { 19 | return { 20 | id: products[0].id, 21 | name: products[0].name, 22 | pid: products[0].pid, 23 | }; 24 | }, 25 | }, 26 | Product: { 27 | __resolveReference(key: { id: string }) { 28 | if (products[0].id !== key.id) { 29 | return null; 30 | } 31 | 32 | return { 33 | id: products[0].id, 34 | name: products[0].name, 35 | pid: products[0].pid, 36 | }; 37 | }, 38 | }, 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extends-resolvable/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | type Query { 7 | productInB: Product 8 | } 9 | 10 | extend type Product @key(fields: "id name") @key(fields: "upc") { 11 | id: ID @external 12 | name: String @external 13 | upc: String @external 14 | price: Float! 15 | } 16 | `, 17 | resolvers: { 18 | Query: { 19 | productInB: () => { 20 | return { 21 | id: products[0].id, 22 | name: products[0].name, 23 | upc: products[0].upc, 24 | price: products[0].price, 25 | }; 26 | }, 27 | }, 28 | Product: { 29 | __resolveReference: ( 30 | key: 31 | | { id: string; name: string } 32 | | { 33 | upc: string; 34 | }, 35 | ) => { 36 | const product = 37 | "id" in key 38 | ? products.find((p) => p.id === key.id && p.name === key.name) 39 | : products.find((p) => p.upc === key.upc); 40 | 41 | return product 42 | ? { 43 | id: product.id, 44 | name: product.name, 45 | upc: product.upc, 46 | price: product.price, 47 | } 48 | : null; 49 | }, 50 | }, 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extends-resolvable/data.ts: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | id: "p1", 4 | pid: "p1-pid", 5 | price: 12.3, 6 | upc: "upc1", 7 | name: "p1-name", 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extends-resolvable/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import tests from "./test.js"; 5 | 6 | export default serve("fed1-external-extends-resolvable", [a, b], tests); 7 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extends-resolvable/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | productInA { 8 | id 9 | pid 10 | price 11 | upc 12 | name 13 | } 14 | } 15 | `, 16 | { 17 | data: { 18 | productInA: { 19 | id: "p1", 20 | pid: "p1-pid", 21 | price: 12.3, 22 | upc: "upc1", 23 | name: "p1-name", 24 | }, 25 | }, 26 | }, 27 | ), 28 | ]; 29 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extends/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | type Query { 7 | randomUser: User 8 | providedRandomUser: User @provides(fields: "name") 9 | } 10 | 11 | type User @key(fields: "id") @extends { 12 | id: ID! @external 13 | name: String! @external 14 | rid: ID 15 | } 16 | `, 17 | resolvers: { 18 | Query: { 19 | randomUser() { 20 | return { 21 | id: users[0].id, 22 | rid: users[0].rid, 23 | }; 24 | }, 25 | providedRandomUser() { 26 | return { 27 | id: users[0].id, 28 | rid: users[0].rid, 29 | name: users[0].name, 30 | }; 31 | }, 32 | }, 33 | User: { 34 | __resolveReference(key: { id: string }) { 35 | const user = users.find((u) => u.id === key.id); 36 | 37 | if (!user) { 38 | return null; 39 | } 40 | 41 | return { 42 | id: user.id, 43 | rid: user.rid, 44 | }; 45 | }, 46 | name(user: { name: string | undefined }) { 47 | return user.name ?? "never"; 48 | }, 49 | }, 50 | }, 51 | }); 52 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extends/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | type Query { 7 | userById(id: ID): User 8 | } 9 | 10 | type User @key(fields: "id") { 11 | id: ID! 12 | name: String! 13 | nickname: String 14 | } 15 | `, 16 | resolvers: { 17 | Query: { 18 | userById: (_: {}, { id }: { id: string }) => { 19 | const user = users.find((u) => u.id === id); 20 | 21 | if (!user) { 22 | return null; 23 | } 24 | 25 | return { 26 | id: user.id, 27 | name: user.name, 28 | nickname: user.nickname, 29 | }; 30 | }, 31 | }, 32 | User: { 33 | __resolveReference: (key: { id: string }) => { 34 | const user = users.find((u) => u.id === key.id); 35 | 36 | if (!user) { 37 | return null; 38 | } 39 | 40 | return { 41 | id: user.id, 42 | name: user.name, 43 | nickname: user.nickname, 44 | }; 45 | }, 46 | }, 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extends/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | rid: "u1-rid", 5 | name: "u1-name", 6 | nickname: "u1-nickname", 7 | }, 8 | { 9 | id: "u2", 10 | rid: "u2-rid", 11 | name: "u2-name", 12 | nickname: "u2-nickname", 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extends/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("fed1-external-extends", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extends/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | randomUser { 8 | id 9 | name 10 | } 11 | userById(id: "u2") { 12 | id 13 | name 14 | nickname 15 | } 16 | } 17 | `, 18 | { 19 | data: { 20 | randomUser: { 21 | id: "u1", 22 | name: "u1-name", 23 | }, 24 | userById: { 25 | id: "u2", 26 | name: "u2-name", 27 | nickname: "u2-nickname", 28 | }, 29 | }, 30 | } 31 | ), 32 | createTest( 33 | /* GraphQL */ ` 34 | query { 35 | randomUser { 36 | id 37 | rid 38 | } 39 | } 40 | `, 41 | { 42 | data: { 43 | randomUser: { 44 | id: "u1", 45 | rid: "u1-rid", 46 | }, 47 | }, 48 | } 49 | ), 50 | createTest( 51 | /* GraphQL */ ` 52 | query { 53 | randomUser { 54 | id 55 | rid 56 | name 57 | } 58 | } 59 | `, 60 | { 61 | data: { 62 | randomUser: { 63 | id: "u1", 64 | rid: "u1-rid", 65 | name: "u1-name", 66 | }, 67 | }, 68 | } 69 | ), 70 | createTest( 71 | /* GraphQL */ ` 72 | query { 73 | providedRandomUser { 74 | id 75 | rid 76 | name 77 | } 78 | } 79 | `, 80 | { 81 | data: { 82 | providedRandomUser: { 83 | id: "u1", 84 | rid: "u1-rid", 85 | name: "u1-name", 86 | }, 87 | }, 88 | } 89 | ), 90 | ]; 91 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extension/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | type Query { 7 | randomUser: User 8 | providedRandomUser: User @provides(fields: "name") 9 | } 10 | 11 | extend type User @key(fields: "id") { 12 | id: ID! @external 13 | name: String! @external 14 | rid: ID 15 | } 16 | `, 17 | resolvers: { 18 | Query: { 19 | randomUser() { 20 | return { 21 | id: users[0].id, 22 | rid: users[0].rid, 23 | }; 24 | }, 25 | providedRandomUser() { 26 | return { 27 | id: users[0].id, 28 | rid: users[0].rid, 29 | name: users[0].name, 30 | }; 31 | }, 32 | }, 33 | User: { 34 | __resolveReference(key: { id: string }) { 35 | const user = users.find((u) => u.id === key.id); 36 | 37 | if (!user) { 38 | return null; 39 | } 40 | 41 | return { 42 | id: user.id, 43 | rid: user.rid, 44 | }; 45 | }, 46 | name(user: { name: string | undefined }) { 47 | return user.name ?? "never"; 48 | }, 49 | }, 50 | }, 51 | }); 52 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extension/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | type Query { 7 | userById(id: ID): User 8 | } 9 | 10 | type User @key(fields: "id") { 11 | id: ID! 12 | name: String! 13 | nickname: String 14 | } 15 | `, 16 | resolvers: { 17 | Query: { 18 | userById: (_: {}, { id }: { id: string }) => { 19 | const user = users.find((u) => u.id === id); 20 | 21 | if (!user) { 22 | return null; 23 | } 24 | 25 | return { 26 | id: user.id, 27 | name: user.name, 28 | nickname: user.nickname, 29 | }; 30 | }, 31 | }, 32 | User: { 33 | __resolveReference: (key: { id: string }) => { 34 | const user = users.find((u) => u.id === key.id); 35 | 36 | if (!user) { 37 | return null; 38 | } 39 | 40 | return { 41 | id: user.id, 42 | name: user.name, 43 | nickname: user.nickname, 44 | }; 45 | }, 46 | }, 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extension/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | rid: "u1-rid", 5 | name: "u1-name", 6 | nickname: "u1-nickname", 7 | }, 8 | { 9 | id: "u2", 10 | rid: "u2-rid", 11 | name: "u2-name", 12 | nickname: "u2-nickname", 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extension/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("fed1-external-extension", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/fed1-external-extension/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | randomUser { 8 | id 9 | name 10 | } 11 | userById(id: "u2") { 12 | id 13 | name 14 | nickname 15 | } 16 | } 17 | `, 18 | { 19 | data: { 20 | randomUser: { 21 | id: "u1", 22 | name: "u1-name", 23 | }, 24 | userById: { 25 | id: "u2", 26 | name: "u2-name", 27 | nickname: "u2-nickname", 28 | }, 29 | }, 30 | } 31 | ), 32 | createTest( 33 | /* GraphQL */ ` 34 | query { 35 | randomUser { 36 | id 37 | rid 38 | } 39 | } 40 | `, 41 | { 42 | data: { 43 | randomUser: { 44 | id: "u1", 45 | rid: "u1-rid", 46 | }, 47 | }, 48 | } 49 | ), 50 | createTest( 51 | /* GraphQL */ ` 52 | query { 53 | randomUser { 54 | id 55 | rid 56 | name 57 | } 58 | } 59 | `, 60 | { 61 | data: { 62 | randomUser: { 63 | id: "u1", 64 | rid: "u1-rid", 65 | name: "u1-name", 66 | }, 67 | }, 68 | } 69 | ), 70 | createTest( 71 | /* GraphQL */ ` 72 | query { 73 | providedRandomUser { 74 | id 75 | rid 76 | name 77 | } 78 | } 79 | `, 80 | { 81 | data: { 82 | providedRandomUser: { 83 | id: "u1", 84 | rid: "u1-rid", 85 | name: "u1-name", 86 | }, 87 | }, 88 | } 89 | ), 90 | ]; 91 | -------------------------------------------------------------------------------- /src/test-suites/fed2-external-extends/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@external", "@extends", "@provides"] 10 | ) 11 | 12 | type Query { 13 | randomUser: User 14 | providedRandomUser: User @provides(fields: "name") 15 | } 16 | 17 | type User @key(fields: "id") @extends { 18 | id: ID! @external 19 | name: String! @external 20 | rid: ID 21 | } 22 | `, 23 | resolvers: { 24 | Query: { 25 | randomUser() { 26 | return { 27 | id: users[0].id, 28 | rid: users[0].rid, 29 | }; 30 | }, 31 | providedRandomUser() { 32 | return { 33 | id: users[0].id, 34 | rid: users[0].rid, 35 | name: users[0].name, 36 | }; 37 | }, 38 | }, 39 | User: { 40 | __resolveReference(key: { id: string }) { 41 | const user = users.find((u) => u.id === key.id); 42 | 43 | if (!user) { 44 | return null; 45 | } 46 | 47 | return { 48 | id: user.id, 49 | rid: user.rid, 50 | }; 51 | }, 52 | name(user: { name: string | undefined }) { 53 | return user.name ?? "never"; 54 | }, 55 | }, 56 | }, 57 | }); 58 | -------------------------------------------------------------------------------- /src/test-suites/fed2-external-extends/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | userById(id: ID): User 14 | } 15 | 16 | type User @key(fields: "id") { 17 | id: ID! 18 | name: String! @shareable 19 | nickname: String 20 | } 21 | `, 22 | resolvers: { 23 | Query: { 24 | userById: (_: {}, { id }: { id: string }) => { 25 | const user = users.find((u) => u.id === id); 26 | 27 | if (!user) { 28 | return null; 29 | } 30 | 31 | return { 32 | id: user.id, 33 | name: user.name, 34 | nickname: user.nickname, 35 | }; 36 | }, 37 | }, 38 | User: { 39 | __resolveReference: (key: { id: string }) => { 40 | const user = users.find((u) => u.id === key.id); 41 | 42 | if (!user) { 43 | return null; 44 | } 45 | 46 | return { 47 | id: user.id, 48 | name: user.name, 49 | nickname: user.nickname, 50 | }; 51 | }, 52 | }, 53 | }, 54 | }); 55 | -------------------------------------------------------------------------------- /src/test-suites/fed2-external-extends/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | rid: "u1-rid", 5 | name: "u1-name", 6 | nickname: "u1-nickname", 7 | }, 8 | { 9 | id: "u2", 10 | rid: "u2-rid", 11 | name: "u2-name", 12 | nickname: "u2-nickname", 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /src/test-suites/fed2-external-extends/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("fed2-external-extends", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/fed2-external-extends/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | randomUser { 8 | id 9 | name 10 | } 11 | userById(id: "u2") { 12 | id 13 | name 14 | nickname 15 | } 16 | } 17 | `, 18 | { 19 | data: { 20 | randomUser: { 21 | id: "u1", 22 | name: "u1-name", 23 | }, 24 | userById: { 25 | id: "u2", 26 | name: "u2-name", 27 | nickname: "u2-nickname", 28 | }, 29 | }, 30 | } 31 | ), 32 | createTest( 33 | /* GraphQL */ ` 34 | query { 35 | randomUser { 36 | id 37 | rid 38 | } 39 | } 40 | `, 41 | { 42 | data: { 43 | randomUser: { 44 | id: "u1", 45 | rid: "u1-rid", 46 | }, 47 | }, 48 | } 49 | ), 50 | createTest( 51 | /* GraphQL */ ` 52 | query { 53 | randomUser { 54 | id 55 | rid 56 | name 57 | } 58 | } 59 | `, 60 | { 61 | data: { 62 | randomUser: { 63 | id: "u1", 64 | rid: "u1-rid", 65 | name: "u1-name", 66 | }, 67 | }, 68 | } 69 | ), 70 | createTest( 71 | /* GraphQL */ ` 72 | query { 73 | providedRandomUser { 74 | id 75 | rid 76 | name 77 | } 78 | } 79 | `, 80 | { 81 | data: { 82 | providedRandomUser: { 83 | id: "u1", 84 | rid: "u1-rid", 85 | name: "u1-name", 86 | }, 87 | }, 88 | } 89 | ), 90 | ]; 91 | -------------------------------------------------------------------------------- /src/test-suites/fed2-external-extension/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@external", "@provides"] 10 | ) 11 | 12 | type Query { 13 | randomUser: User 14 | providedRandomUser: User @provides(fields: "name") 15 | } 16 | 17 | extend type User @key(fields: "id") { 18 | id: ID! @external 19 | name: String! @external 20 | rid: ID 21 | } 22 | `, 23 | resolvers: { 24 | Query: { 25 | randomUser() { 26 | return { 27 | id: users[0].id, 28 | rid: users[0].rid, 29 | }; 30 | }, 31 | providedRandomUser() { 32 | return { 33 | id: users[0].id, 34 | rid: users[0].rid, 35 | name: users[0].name, 36 | }; 37 | }, 38 | }, 39 | User: { 40 | __resolveReference(key: { id: string }) { 41 | const user = users.find((u) => u.id === key.id); 42 | 43 | if (!user) { 44 | return null; 45 | } 46 | 47 | return { 48 | id: user.id, 49 | rid: user.rid, 50 | }; 51 | }, 52 | name(user: { name: string | undefined }) { 53 | return user.name ?? "never"; 54 | }, 55 | }, 56 | }, 57 | }); 58 | -------------------------------------------------------------------------------- /src/test-suites/fed2-external-extension/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | userById(id: ID): User 14 | } 15 | 16 | type User @key(fields: "id") { 17 | id: ID! 18 | name: String! @shareable 19 | nickname: String 20 | } 21 | `, 22 | resolvers: { 23 | Query: { 24 | userById: (_: {}, { id }: { id: string }) => { 25 | const user = users.find((u) => u.id === id); 26 | 27 | if (!user) { 28 | return null; 29 | } 30 | 31 | return { 32 | id: user.id, 33 | name: user.name, 34 | nickname: user.nickname, 35 | }; 36 | }, 37 | }, 38 | User: { 39 | __resolveReference: (key: { id: string }) => { 40 | const user = users.find((u) => u.id === key.id); 41 | 42 | if (!user) { 43 | return null; 44 | } 45 | 46 | return { 47 | id: user.id, 48 | name: user.name, 49 | nickname: user.nickname, 50 | }; 51 | }, 52 | }, 53 | }, 54 | }); 55 | -------------------------------------------------------------------------------- /src/test-suites/fed2-external-extension/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | rid: "u1-rid", 5 | name: "u1-name", 6 | nickname: "u1-nickname", 7 | }, 8 | { 9 | id: "u2", 10 | rid: "u2-rid", 11 | name: "u2-name", 12 | nickname: "u2-nickname", 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /src/test-suites/fed2-external-extension/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("fed2-external-extension", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/fed2-external-extension/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | randomUser { 8 | id 9 | name 10 | } 11 | userById(id: "u2") { 12 | id 13 | name 14 | nickname 15 | } 16 | } 17 | `, 18 | { 19 | data: { 20 | randomUser: { 21 | id: "u1", 22 | name: "u1-name", 23 | }, 24 | userById: { 25 | id: "u2", 26 | name: "u2-name", 27 | nickname: "u2-nickname", 28 | }, 29 | }, 30 | } 31 | ), 32 | createTest( 33 | /* GraphQL */ ` 34 | query { 35 | randomUser { 36 | id 37 | rid 38 | } 39 | } 40 | `, 41 | { 42 | data: { 43 | randomUser: { 44 | id: "u1", 45 | rid: "u1-rid", 46 | }, 47 | }, 48 | } 49 | ), 50 | createTest( 51 | /* GraphQL */ ` 52 | query { 53 | randomUser { 54 | id 55 | rid 56 | name 57 | } 58 | } 59 | `, 60 | { 61 | data: { 62 | randomUser: { 63 | id: "u1", 64 | rid: "u1-rid", 65 | name: "u1-name", 66 | }, 67 | }, 68 | } 69 | ), 70 | createTest( 71 | /* GraphQL */ ` 72 | query { 73 | providedRandomUser { 74 | id 75 | rid 76 | name 77 | } 78 | } 79 | `, 80 | { 81 | data: { 82 | providedRandomUser: { 83 | id: "u1", 84 | rid: "u1-rid", 85 | name: "u1-name", 86 | }, 87 | }, 88 | } 89 | ), 90 | ]; 91 | -------------------------------------------------------------------------------- /src/test-suites/include-skip/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | type Query { 10 | product: Product 11 | } 12 | 13 | type Product @key(fields: "id") { 14 | id: ID! 15 | price: Float! 16 | } 17 | `, 18 | resolvers: { 19 | Query: { 20 | product() { 21 | return { 22 | id: products[0].id, 23 | price: products[0].price, 24 | }; 25 | }, 26 | }, 27 | Product: { 28 | __resolveReference(key: { id: string }) { 29 | const product = products.find((product) => product.id === key.id); 30 | 31 | if (!product) { 32 | return null; 33 | } 34 | 35 | return { 36 | id: product.id, 37 | price: product.price, 38 | }; 39 | }, 40 | }, 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /src/test-suites/include-skip/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@external", "@requires"] 10 | ) 11 | 12 | type Product @key(fields: "id") { 13 | id: ID! 14 | price: Float! @external 15 | isExpensive: Boolean! @requires(fields: "price") 16 | } 17 | `, 18 | resolvers: { 19 | Product: { 20 | __resolveReference(key: { id: string } | { id: string; price: number }) { 21 | const product = products.find((product) => product.id === key.id); 22 | 23 | if (!product) { 24 | return null; 25 | } 26 | 27 | if ("price" in key) { 28 | return { 29 | id: product.id, 30 | price: key.price, 31 | }; 32 | } 33 | 34 | return { 35 | id: product.id, 36 | }; 37 | }, 38 | isExpensive(product: { price?: number }) { 39 | if (!product.price) { 40 | throw new Error("Price is missing"); 41 | } 42 | 43 | return product.price > 500; 44 | }, 45 | }, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/test-suites/include-skip/data.ts: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | id: "p1", 4 | price: 699.99, 5 | }, 6 | ]; 7 | -------------------------------------------------------------------------------- /src/test-suites/include-skip/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import test from "./test.js"; 6 | 7 | export default serve("include-skip", [a, b, c], test); 8 | -------------------------------------------------------------------------------- /src/test-suites/include-skip/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query ($bool: Boolean = false) { 7 | product { 8 | price 9 | neverCalledInclude @include(if: $bool) 10 | } 11 | } 12 | `, 13 | { 14 | data: { 15 | product: { 16 | price: 699.99, 17 | }, 18 | }, 19 | }, 20 | ), 21 | createTest( 22 | /* GraphQL */ ` 23 | query ($bool: Boolean = true) { 24 | product { 25 | price 26 | neverCalledSkip @skip(if: $bool) 27 | } 28 | } 29 | `, 30 | { 31 | data: { 32 | product: { 33 | price: 699.99, 34 | }, 35 | }, 36 | }, 37 | ), 38 | createTest( 39 | /* GraphQL */ ` 40 | query ($bool: Boolean = true) { 41 | product { 42 | price 43 | include @include(if: $bool) 44 | } 45 | } 46 | `, 47 | { 48 | data: { 49 | product: { 50 | price: 699.99, 51 | include: true, 52 | }, 53 | }, 54 | }, 55 | ), 56 | createTest( 57 | /* GraphQL */ ` 58 | query ($bool: Boolean = false) { 59 | product { 60 | price 61 | skip @skip(if: $bool) 62 | } 63 | } 64 | `, 65 | { 66 | data: { 67 | product: { 68 | price: 699.99, 69 | skip: true, 70 | }, 71 | }, 72 | }, 73 | ), 74 | ]; 75 | -------------------------------------------------------------------------------- /src/test-suites/input-object-intersection/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | input UsersFilter { 13 | first: Int! 14 | } 15 | 16 | type User @key(fields: "id") { 17 | id: ID! 18 | name: String! @shareable 19 | } 20 | 21 | type Query { 22 | usersInA(filter: UsersFilter!): [User!] 23 | } 24 | `, 25 | resolvers: { 26 | Query: { 27 | usersInA(_: {}, { filter }: { filter: { first: number } }) { 28 | if ("offset" in filter) { 29 | return []; 30 | } 31 | 32 | return users; 33 | }, 34 | }, 35 | User: { 36 | __resolveReference(key: { id: string }) { 37 | const user = users.find((user) => user.id === key.id); 38 | 39 | if (!user) { 40 | return null; 41 | } 42 | 43 | return user; 44 | }, 45 | }, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/test-suites/input-object-intersection/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | input UsersFilter { 13 | offset: Int 14 | first: Int! 15 | } 16 | 17 | type User @key(fields: "id") { 18 | id: ID! 19 | name: String! @shareable 20 | } 21 | 22 | type Query { 23 | usersInB(filter: UsersFilter!): [User!] 24 | } 25 | `, 26 | resolvers: { 27 | Query: { 28 | usersInB( 29 | _: {}, 30 | { filter }: { filter: { offset?: number; first: number } }, 31 | ) { 32 | if ("offset" in filter) { 33 | return []; 34 | } 35 | 36 | return users; 37 | }, 38 | }, 39 | User: { 40 | __resolveReference(key: { id: string }) { 41 | const user = users.find((user) => user.id === key.id); 42 | 43 | if (!user) { 44 | return null; 45 | } 46 | 47 | return user; 48 | }, 49 | }, 50 | }, 51 | }); 52 | -------------------------------------------------------------------------------- /src/test-suites/input-object-intersection/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | name: "u1-name", 5 | }, 6 | { 7 | id: "u2", 8 | name: "u2-name", 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /src/test-suites/input-object-intersection/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("input-object-intersection", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/input-object-intersection/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | usersInA(filter: { first: 1 }) { 8 | id 9 | } 10 | } 11 | `, 12 | { 13 | data: { 14 | usersInA: [ 15 | { 16 | id: "u1", 17 | }, 18 | { 19 | id: "u2", 20 | }, 21 | ], 22 | }, 23 | }, 24 | ), 25 | createTest( 26 | /* GraphQL */ ` 27 | query { 28 | usersInA(filter: { first: 1, offset: 2 }) { 29 | id 30 | } 31 | } 32 | `, 33 | { 34 | errors: true, 35 | }, 36 | ), 37 | createTest( 38 | /* GraphQL */ ` 39 | query { 40 | usersInB(filter: { first: 1, offset: 2 }) { 41 | id 42 | } 43 | } 44 | `, 45 | { 46 | errors: true, 47 | }, 48 | ), 49 | ]; 50 | -------------------------------------------------------------------------------- /src/test-suites/interface-object-with-requires/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | type Query { 10 | users: [NodeWithName!]! 11 | } 12 | 13 | interface NodeWithName @key(fields: "id") { 14 | id: ID! 15 | name: String 16 | } 17 | 18 | type User implements NodeWithName @key(fields: "id") { 19 | id: ID! 20 | name: String 21 | age: Int 22 | } 23 | `, 24 | resolvers: { 25 | NodeWithName: { 26 | __resolveReference(key: { id: string }) { 27 | const node = users.find((u) => u.id === key.id); 28 | 29 | if (!node) { 30 | return null; 31 | } 32 | 33 | return { 34 | __typename: "User", 35 | id: node.id, 36 | name: node.name, 37 | age: node.age, 38 | }; 39 | }, 40 | }, 41 | User: { 42 | __resolveReference(key: { id: string }) { 43 | const user = users.find((u) => u.id === key.id); 44 | 45 | if (!user) { 46 | return null; 47 | } 48 | 49 | return { 50 | __typename: "User", 51 | id: user.id, 52 | name: user.name, 53 | age: user.age, 54 | }; 55 | }, 56 | }, 57 | Query: { 58 | users() { 59 | return users.map((u) => ({ 60 | __typename: "User", 61 | id: u.id, 62 | name: u.name, 63 | age: u.age, 64 | })); 65 | }, 66 | }, 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /src/test-suites/interface-object-with-requires/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@interfaceObject", "@external", "@requires"] 10 | ) 11 | 12 | type Query { 13 | anotherUsers: [NodeWithName] 14 | } 15 | 16 | type NodeWithName @key(fields: "id") @interfaceObject { 17 | id: ID! 18 | name: String @external 19 | username: String @requires(fields: "name") 20 | } 21 | `, 22 | resolvers: { 23 | NodeWithName: { 24 | __resolveReference(key: { id: string } | { id: string; name: string }) { 25 | const node = users.find((u) => u.id === key.id); 26 | 27 | if (!node) { 28 | return null; 29 | } 30 | 31 | return { 32 | __typename: "User", 33 | id: node.id, 34 | name: "name" in key ? key.name : undefined, 35 | }; 36 | }, 37 | username(node: { id: string; name: string }) { 38 | const user = users.find((u) => u.id === node.id); 39 | 40 | if (!user) { 41 | return null; 42 | } 43 | 44 | if (!node.name) { 45 | throw new Error("Requires field 'name' not provided"); 46 | } 47 | 48 | return user.username; 49 | }, 50 | }, 51 | Query: { 52 | anotherUsers() { 53 | return users.map((u) => ({ 54 | __typename: "User", 55 | id: u.id, 56 | username: u.username, 57 | })); 58 | }, 59 | }, 60 | }, 61 | }); 62 | -------------------------------------------------------------------------------- /src/test-suites/interface-object-with-requires/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | name: "u1-name", 5 | username: "u1-username", 6 | age: 11, 7 | }, 8 | { 9 | id: "u2", 10 | name: "u2-name", 11 | username: "u2-username", 12 | age: 22, 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /src/test-suites/interface-object-with-requires/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("interface-object-with-requires", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/keys-mashup/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { data } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.5", import: ["@key"]) 8 | 9 | type A 10 | @key(fields: "id", resolvable: true) 11 | @key(fields: "pId", resolvable: false) 12 | @key(fields: "compositeId { one two }", resolvable: false) 13 | @key(fields: "id compositeId { two three }", resolvable: false) { 14 | id: ID! 15 | pId: ID! 16 | compositeId: CompositeID! 17 | name: String! 18 | } 19 | 20 | type CompositeID { 21 | one: ID! 22 | two: ID! 23 | three: ID! 24 | } 25 | `, 26 | resolvers: { 27 | A: { 28 | __resolveReference: (reference: { id: string }) => { 29 | // The only key that is resolvable is "id" 30 | const obj = data.a[reference.id]; 31 | 32 | if (!obj) { 33 | return null; 34 | } 35 | 36 | return { 37 | __typename: "A", 38 | id: obj.id, 39 | pId: obj.pId, 40 | compositeId: obj.compositeId, 41 | name: obj.name, 42 | }; 43 | }, 44 | }, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /src/test-suites/keys-mashup/data.ts: -------------------------------------------------------------------------------- 1 | export const data: { 2 | a: Record< 3 | string, 4 | { 5 | id: string; 6 | pId: string; 7 | name: string; 8 | compositeId: { 9 | one: string; 10 | two: string; 11 | three: string; 12 | }; 13 | } 14 | >; 15 | b: Record; 16 | } = { 17 | a: { 18 | "1": { 19 | id: "1", 20 | pId: "a.1.pId", 21 | name: "a.1", 22 | compositeId: { 23 | one: "a.1.compositeId.one", 24 | two: "a.1.compositeId.two", 25 | three: "a.1.compositeId.three", 26 | }, 27 | }, 28 | "2": { 29 | id: "2", 30 | pId: "a.2.pId", 31 | name: "a.2", 32 | compositeId: { 33 | one: "a.2.compositeId.one", 34 | two: "a.2.compositeId.two", 35 | three: "a.2.compositeId.three", 36 | }, 37 | }, 38 | }, 39 | b: { 40 | "100": { id: "100", a: ["1"] }, 41 | "200": { id: "200", a: ["1", "2"] }, 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /src/test-suites/keys-mashup/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("keys-mashup", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/keys-mashup/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | b { 8 | id 9 | a { 10 | id 11 | name 12 | nameInB 13 | } 14 | } 15 | } 16 | `, 17 | { 18 | data: { 19 | b: { 20 | id: "100", 21 | a: [ 22 | { 23 | id: "1", 24 | name: "a.1", 25 | nameInB: "b.a.nameInB a.1", 26 | }, 27 | ], 28 | }, 29 | }, 30 | }, 31 | ), 32 | ]; 33 | -------------------------------------------------------------------------------- /src/test-suites/mutations/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { deleteNumber, deleteProduct, getProducts } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@external", "@requires"] 10 | ) 11 | 12 | type Product @key(fields: "id") { 13 | id: ID! 14 | price: Float! @external 15 | isExpensive: Boolean! @requires(fields: "price") 16 | isAvailable: Boolean! 17 | } 18 | 19 | type Mutation { 20 | delete(requestId: String!): Int! 21 | } 22 | `, 23 | resolvers: { 24 | Mutation: { 25 | delete( 26 | _: {}, 27 | args: { 28 | requestId: string; 29 | }, 30 | ) { 31 | return deleteNumber(args.requestId); 32 | }, 33 | }, 34 | Product: { 35 | async __resolveReference(key: { id: string; price?: number }) { 36 | const product = (await getProducts()).find( 37 | (product) => product.id === key.id, 38 | ); 39 | 40 | if (!product) { 41 | return null; 42 | } 43 | 44 | await deleteProduct(product.id); 45 | 46 | if (typeof product.price === "number") { 47 | return { 48 | id: product.id, 49 | isAvailable: true, 50 | price: product.price, 51 | }; 52 | } 53 | 54 | return { 55 | id: product.id, 56 | isAvailable: false, 57 | }; 58 | }, 59 | isExpensive(product: { price?: number }) { 60 | if (typeof product.price !== "number") { 61 | throw new Error("Price is not available"); 62 | } 63 | 64 | return product.price > 100; 65 | }, 66 | }, 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /src/test-suites/mutations/c.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { addNumber } from "./data.js"; 3 | 4 | export default createSubgraph("c", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | type Mutation { 10 | add(num: Int!, requestId: String!): Int! 11 | } 12 | `, 13 | resolvers: { 14 | Mutation: { 15 | add( 16 | _: {}, 17 | args: { 18 | num: number; 19 | requestId: string; 20 | }, 21 | ) { 22 | return addNumber(args.num, args.requestId); 23 | }, 24 | }, 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /src/test-suites/mutations/data.ts: -------------------------------------------------------------------------------- 1 | interface Product { 2 | id: string; 3 | name: string; 4 | price: number; 5 | } 6 | 7 | let products: Product[] = []; 8 | const numbers: Record = {}; 9 | 10 | export async function getProducts(): Promise { 11 | return products; 12 | } 13 | 14 | export async function addProduct(name: string, price: number) { 15 | const newProduct = { 16 | id: "p-added-" + products.length, 17 | name: name, 18 | price: price, 19 | }; 20 | products.push(newProduct); 21 | return newProduct; 22 | } 23 | 24 | export async function deleteProduct(id: string) { 25 | const after = products.filter((p) => p.id !== id); 26 | products = after; 27 | } 28 | 29 | export function initProducts() { 30 | const product = { 31 | id: "p1", 32 | name: "p1-name", 33 | price: 9.99, 34 | }; 35 | 36 | if (products.some((p) => p.id === product.id)) { 37 | return; 38 | } 39 | 40 | products.push(product); 41 | } 42 | 43 | function getNumber(requestId: string) { 44 | const num = numbers[requestId]; 45 | return num ?? 0; 46 | } 47 | 48 | export function addNumber(num: number, requestId: string) { 49 | const existingNumber = getNumber(requestId); 50 | const sum = existingNumber + num; 51 | numbers[requestId] = sum; 52 | return sum; 53 | } 54 | 55 | export function multiplyNumber(by: number, requestId: string) { 56 | const existingNumber = getNumber(requestId); 57 | const result = existingNumber * by; 58 | numbers[requestId] = result; 59 | return result; 60 | } 61 | 62 | export function deleteNumber(requestId: string) { 63 | const num = getNumber(requestId); 64 | if (typeof numbers[requestId] === "number") { 65 | delete numbers[requestId]; 66 | } 67 | return num; 68 | } 69 | -------------------------------------------------------------------------------- /src/test-suites/mutations/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import test from "./test.js"; 6 | 7 | export default serve("mutations", [a, b, c], test); 8 | -------------------------------------------------------------------------------- /src/test-suites/mutations/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default () => { 4 | const randomId = Math.random().toString(16).substr(2); 5 | 6 | return [ 7 | createTest( 8 | /* GraphQL */ ` 9 | mutation { 10 | addProduct(input: { name: "new", price: 599.99 }) { 11 | name 12 | price 13 | isExpensive 14 | isAvailable 15 | } 16 | } 17 | `, 18 | { 19 | data: { 20 | addProduct: { 21 | name: "new", 22 | price: 599.99, 23 | isExpensive: true, 24 | isAvailable: true, 25 | }, 26 | }, 27 | }, 28 | ), 29 | createTest( 30 | /* GraphQL */ ` 31 | query { 32 | product(id: "p1") { 33 | id 34 | name 35 | price 36 | isExpensive 37 | isAvailable 38 | } 39 | } 40 | `, 41 | { 42 | data: { 43 | product: { 44 | id: "p1", 45 | name: "p1-name", 46 | price: 9.99, 47 | isExpensive: false, 48 | isAvailable: true, 49 | }, 50 | }, 51 | }, 52 | ), 53 | // Test correct order of execution 54 | // It obviously does not solve a problem with shared state and race conditions, 55 | // but at least it reduces the risk a bit 56 | createTest( 57 | /* GraphQL */ ` 58 | mutation { 59 | five: add(num: 5, requestId: "${randomId}") 60 | ten: multiply(by: 2, requestId: "${randomId}") 61 | twelve: add(num: 2, requestId: "${randomId}") 62 | final: delete(requestId: "${randomId}") 63 | } 64 | `, 65 | { 66 | data: { 67 | five: 5, 68 | ten: 10, 69 | twelve: 12, 70 | final: 12, 71 | }, 72 | }, 73 | ), 74 | ]; 75 | }; 76 | -------------------------------------------------------------------------------- /src/test-suites/mysterious-external/data.ts: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | id: "1", 4 | name: "name-1", 5 | price: 100, 6 | }, 7 | { 8 | id: "2", 9 | name: "name-2", 10 | price: 200, 11 | }, 12 | ]; 13 | -------------------------------------------------------------------------------- /src/test-suites/mysterious-external/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import price from "./price.subgraph.js"; 3 | import product from "./product.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("mysterious-external", [price, product], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/mysterious-external/price.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("price", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@external"] 10 | ) 11 | 12 | # Composition will fail if it's not an extension (I have no idea why this is the case) 13 | extend type Product @key(fields: "id") { 14 | id: ID! @external 15 | price: Float 16 | } 17 | 18 | type Query { 19 | cheapestProduct: Product 20 | } 21 | `, 22 | resolvers: { 23 | Product: { 24 | __resolveReference(key: { id: string }) { 25 | const product = products.find((product) => product.id === key.id); 26 | 27 | if (!product) { 28 | return null; 29 | } 30 | 31 | return { 32 | id: product.id, 33 | price: product.price, 34 | }; 35 | }, 36 | }, 37 | Query: { 38 | cheapestProduct() { 39 | let cheapest: (typeof products)[number] | null = null; 40 | 41 | for (const product of products) { 42 | if (cheapest) { 43 | if (product.price < cheapest.price) { 44 | cheapest = product; 45 | } 46 | } else { 47 | cheapest = product; 48 | } 49 | } 50 | 51 | return cheapest; 52 | }, 53 | }, 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /src/test-suites/mysterious-external/product.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("product", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@external"] 10 | ) 11 | 12 | type Product @key(fields: "id") { 13 | id: ID! 14 | name: String! 15 | } 16 | 17 | type Query { 18 | products: [Product!]! 19 | } 20 | `, 21 | resolvers: { 22 | Product: { 23 | __resolveReference(key: { id: string }) { 24 | const product = products.find((product) => product.id === key.id); 25 | 26 | if (!product) { 27 | return null; 28 | } 29 | 30 | return { 31 | id: product.id, 32 | name: product.name, 33 | }; 34 | }, 35 | }, 36 | Query: { 37 | products() { 38 | return products.map((p) => ({ 39 | id: p.id, 40 | name: p.name, 41 | })); 42 | }, 43 | }, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /src/test-suites/mysterious-external/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | cheapestProduct { 8 | id 9 | price 10 | name 11 | } 12 | } 13 | `, 14 | { 15 | data: { 16 | cheapestProduct: { 17 | id: "1", 18 | price: 100, 19 | name: "name-1", 20 | }, 21 | }, 22 | }, 23 | ), 24 | createTest( 25 | /* GraphQL */ ` 26 | query { 27 | products { 28 | name 29 | price 30 | id 31 | } 32 | } 33 | `, 34 | { 35 | data: { 36 | products: [ 37 | { 38 | name: "name-1", 39 | price: 100, 40 | id: "1", 41 | }, 42 | { 43 | name: "name-2", 44 | price: 200, 45 | id: "2", 46 | }, 47 | ], 48 | }, 49 | }, 50 | ), 51 | ]; 52 | -------------------------------------------------------------------------------- /src/test-suites/nested-provides/all-products.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { shouldPunishForPoorPlans } from "../../env.js"; 2 | import { createSubgraph } from "../../subgraph.js"; 3 | import { products } from "./data.js"; 4 | 5 | // I deliberately name it all-products to make sure it's first in the list of subgraphs (in case the order matters) 6 | export default createSubgraph("all-products", { 7 | typeDefs: /* GraphQL */ ` 8 | extend schema 9 | @link( 10 | url: "https://specs.apollo.dev/federation/v2.3" 11 | import: ["@key", "@shareable"] 12 | ) 13 | 14 | type Product @key(fields: "id") { 15 | id: ID! 16 | } 17 | `, 18 | resolvers: { 19 | Product: { 20 | __resolveReference(key: { id: string }, context: any) { 21 | if (shouldPunishForPoorPlans(context)) { 22 | throw new Error("You should be using the categories subgraph!"); 23 | } 24 | 25 | const product = products.find((p) => p.id === key.id); 26 | 27 | if (!product) { 28 | return null; 29 | } 30 | 31 | return { 32 | id: product.id, 33 | }; 34 | }, 35 | }, 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /src/test-suites/nested-provides/data.ts: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | id: "p1", 4 | categories: ["c1", "c2"], 5 | }, 6 | { 7 | id: "p2", 8 | categories: ["c3", "c2"], 9 | }, 10 | ]; 11 | 12 | export const categories = [ 13 | { 14 | id: "c1", 15 | name: "Category 1", 16 | subCategories: ["c2"], 17 | }, 18 | { 19 | id: "c2", 20 | name: "Category 2", 21 | subCategories: ["c3"], 22 | }, 23 | { 24 | id: "c3", 25 | name: "Category 3", 26 | subCategories: ["c1"], 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /src/test-suites/nested-provides/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import allProducts from "./all-products.subgraph.js"; 3 | import category from "./category.subgraph.js"; 4 | import subcategories from "./subcategories.subgraph.js"; 5 | import test from "./test.js"; 6 | 7 | export default serve( 8 | "nested-provides", 9 | [allProducts, category, subcategories], 10 | test, 11 | ); 12 | -------------------------------------------------------------------------------- /src/test-suites/node/data.ts: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | id: "p-1", 4 | name: "Product 1", 5 | price: 10.0, 6 | }, 7 | { 8 | id: "p-2", 9 | name: "Product 2", 10 | price: 20.0, 11 | }, 12 | ]; 13 | 14 | export const categories = [ 15 | { 16 | id: "pc-1", 17 | name: "Category 1", 18 | }, 19 | { 20 | id: "c-2", 21 | name: "Category 2", 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /src/test-suites/node/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import node from "./node.subgraph.js"; 3 | import nodeTwo from "./node-two.js"; 4 | import types from "./types.subgraph.js"; 5 | import tests from "./test.js"; 6 | 7 | export default serve("node", [node, nodeTwo, types], tests); 8 | -------------------------------------------------------------------------------- /src/test-suites/node/node-two.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { categories, products } from "./data.js"; 3 | 4 | export default createSubgraph("node-two", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | interface Node { 10 | id: ID! 11 | } 12 | 13 | type Product implements Node @key(fields: "id") { 14 | id: ID! 15 | } 16 | 17 | type Category implements Node @key(fields: "id") { 18 | id: ID! 19 | } 20 | `, 21 | resolvers: { 22 | Product: { 23 | __resolveReference(key: { id: string }) { 24 | const product = products.find((p) => p.id === key.id); 25 | 26 | if (!product) { 27 | return null; 28 | } 29 | 30 | return { 31 | id: product.id, 32 | }; 33 | }, 34 | }, 35 | Category: { 36 | __resolveReference(key: { id: string }) { 37 | const cat = categories.find((c) => c.id === key.id); 38 | 39 | if (!cat) { 40 | return null; 41 | } 42 | 43 | return { 44 | id: cat.id, 45 | }; 46 | }, 47 | }, 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /src/test-suites/node/node.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { categories, products } from "./data.js"; 3 | 4 | export default createSubgraph("node", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | type Query { 10 | productNode: Node 11 | categoryNode: Node 12 | } 13 | 14 | interface Node { 15 | id: ID! 16 | } 17 | 18 | type Product implements Node @key(fields: "id") { 19 | id: ID! 20 | } 21 | 22 | type Category implements Node @key(fields: "id") { 23 | id: ID! 24 | } 25 | `, 26 | resolvers: { 27 | Query: { 28 | productNode() { 29 | return { 30 | __typename: "Product", 31 | id: products[0].id, 32 | }; 33 | }, 34 | categoryNode() { 35 | return { 36 | __typename: "Category", 37 | id: categories[0].id, 38 | }; 39 | }, 40 | }, 41 | Product: { 42 | __resolveReference(key: { id: string }) { 43 | const product = products.find((p) => p.id === key.id); 44 | 45 | if (!product) { 46 | return null; 47 | } 48 | 49 | return { 50 | id: product.id, 51 | }; 52 | }, 53 | }, 54 | Category: { 55 | __resolveReference(key: { id: string }) { 56 | const cat = categories.find((c) => c.id === key.id); 57 | 58 | if (!cat) { 59 | return null; 60 | } 61 | 62 | return { 63 | id: cat.id, 64 | }; 65 | }, 66 | }, 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /src/test-suites/node/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | { 7 | productNode { 8 | ... on Product { 9 | id 10 | name 11 | __typename 12 | price 13 | } 14 | } 15 | } 16 | `, 17 | { 18 | data: { 19 | productNode: { 20 | id: "p-1", 21 | name: "Product 1", 22 | __typename: "Product", 23 | price: 10, 24 | }, 25 | }, 26 | }, 27 | ), 28 | ]; 29 | -------------------------------------------------------------------------------- /src/test-suites/node/types.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { categories, products } from "./data.js"; 3 | 4 | export default createSubgraph("types", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | interface Node { 13 | id: ID! 14 | } 15 | 16 | type Product implements Node @key(fields: "id") @shareable { 17 | id: ID! 18 | name: String! 19 | price: Float! 20 | } 21 | 22 | type Category implements Node @key(fields: "id") { 23 | id: ID! 24 | name: String! 25 | } 26 | `, 27 | resolvers: { 28 | Product: { 29 | __resolveReference(key: { id: string }) { 30 | const product = products.find((p) => p.id === key.id); 31 | 32 | if (!product) { 33 | return null; 34 | } 35 | 36 | return { 37 | id: product.id, 38 | name: product.name, 39 | price: product.price, 40 | }; 41 | }, 42 | }, 43 | Category: { 44 | __resolveReference(key: { id: string }) { 45 | const cat = categories.find((c) => c.id === key.id); 46 | 47 | if (!cat) { 48 | return null; 49 | } 50 | 51 | return { 52 | id: cat.id, 53 | name: cat.name, 54 | }; 55 | }, 56 | }, 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /src/test-suites/non-resolvable-interface-object/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@interfaceObject"] 10 | ) 11 | 12 | interface Node @key(fields: "id") { 13 | id: ID! 14 | } 15 | 16 | type Product @key(fields: "id", resolvable: false) @interfaceObject { 17 | id: ID! 18 | } 19 | 20 | type Query { 21 | a: Node 22 | product: Product! 23 | } 24 | `, 25 | resolvers: { 26 | Query: { 27 | a() { 28 | return { __typename: "Node", id: "n1" }; 29 | }, 30 | product() { 31 | return { __typename: "Product", id: products[0].id }; 32 | }, 33 | }, 34 | Node: { 35 | __resolveReference() { 36 | throw new Error("Not resolvable as it is an interface object"); 37 | }, 38 | }, 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /src/test-suites/non-resolvable-interface-object/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@interfaceObject"] 10 | ) 11 | 12 | type Node @key(fields: "id", resolvable: false) @interfaceObject { 13 | id: ID! 14 | field: String 15 | } 16 | 17 | interface Product @key(fields: "id") { 18 | id: ID! 19 | } 20 | 21 | type Bread implements Product @key(fields: "id") { 22 | id: ID! 23 | name: String! 24 | } 25 | 26 | type Query { 27 | b: Node 28 | } 29 | `, 30 | resolvers: { 31 | Query: { 32 | b() { 33 | return { __typename: "Node", id: "n1" }; 34 | }, 35 | }, 36 | Node: { 37 | __resolveReference() { 38 | throw new Error("Not resolvable"); 39 | }, 40 | field() { 41 | return "foo"; 42 | }, 43 | }, 44 | Product: { 45 | __resolveReference(key: { id: string }) { 46 | return products.find((p) => p.id === key.id); 47 | }, 48 | }, 49 | Bread: { 50 | __resolveReference(key: { id: string }) { 51 | return products.find((p) => p.id === key.id); 52 | }, 53 | }, 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /src/test-suites/non-resolvable-interface-object/data.ts: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | __typename: "Bread", 4 | id: "p1", 5 | name: "Bread", 6 | }, 7 | ]; 8 | -------------------------------------------------------------------------------- /src/test-suites/non-resolvable-interface-object/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("non-resolvable-interface-object", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/null-keys/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { books } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key"] 10 | ) 11 | 12 | type Query { 13 | bookContainers: [BookContainer] 14 | } 15 | type BookContainer { 16 | book: Book 17 | } 18 | type Book @key(fields: "upc") { 19 | upc: ID! 20 | } 21 | `, 22 | resolvers: { 23 | Query: { 24 | bookContainers() { 25 | return books.map((book) => ({ book: { upc: book.upc } })); 26 | } 27 | }, 28 | Book: { 29 | __resolveReference(reference: { upc: String; }) { 30 | if (reference != null) { 31 | let book = books.find((book) => book.upc === reference.upc); 32 | if (book != null && book.upc !== null) { 33 | return { 34 | __typename: "Book", 35 | upc: book.upc 36 | }; 37 | } 38 | } 39 | throw new Error("Invalid reference"); 40 | }, 41 | } 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /src/test-suites/null-keys/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { books } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key"] 10 | ) 11 | 12 | type Book @key(fields: "id") @key(fields: "upc") { 13 | id: ID! 14 | upc: ID! 15 | } 16 | `, 17 | resolvers: { 18 | Book: { 19 | __resolveReference(reference: { id: String; } | { upc: String; }) { 20 | if (reference != null) { 21 | let book: { id: string; upc: string; } | undefined; 22 | if ('id' in reference) { 23 | book = books.find((book) => book.id === reference.id); 24 | } 25 | if ('upc' in reference) { 26 | book = books.find((book) => book.upc === reference.upc); 27 | } 28 | if (book != null) { 29 | // `a` has `Book` entities with upc: `b1, b2, b3`, but `b` has `Book` entities with only `b1` and `b2`. 30 | // `b3` is not available and this subgraph is the only possible step to get to the other subgraph (need of `id` field) to resolve the author. 31 | if (book.id === '3') { 32 | return null; 33 | } 34 | return { 35 | __typename: "Book", 36 | id: book.id, 37 | upc: book.upc 38 | }; 39 | } 40 | } 41 | throw new Error("Invalid reference"); 42 | } 43 | } 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /src/test-suites/null-keys/c.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { books } from "./data.js"; 3 | 4 | export default createSubgraph("c", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key"] 10 | ) 11 | 12 | type Book @key(fields: "id") { 13 | id: ID! 14 | author: Author 15 | } 16 | 17 | type Author { 18 | id: ID! 19 | name: String 20 | } 21 | `, 22 | resolvers: { 23 | Book: { 24 | __resolveReference(reference: { id: String; } | { upc: String; }) { 25 | if (reference != null) { 26 | let book: { id: string; author: { id: string; name: string; } } | undefined; 27 | if ('id' in reference && reference.id !== null) { 28 | book = books.find((book) => book.id === reference.id); 29 | } 30 | if ('upc' in reference && reference.upc !== null) { 31 | book = books.find((book) => book.upc === reference.upc); 32 | } 33 | if (book != null) { 34 | return { 35 | __typename: "Book", 36 | id: book.id, 37 | author: { 38 | __typename: "Author", 39 | id: book.author.id, 40 | name: book.author.name 41 | } 42 | }; 43 | } 44 | } 45 | throw new Error("Invalid reference"); 46 | } 47 | } 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /src/test-suites/null-keys/data.ts: -------------------------------------------------------------------------------- 1 | export const books = [ 2 | { 3 | __typename: "Book", 4 | id: "1", 5 | upc: "b1", 6 | author: { 7 | __typename: "Author", 8 | id: "a1", 9 | name: "Alice" 10 | } 11 | }, 12 | { 13 | __typename: "Book", 14 | id: "2", 15 | upc: "b2", 16 | author: { 17 | __typename: "Author", 18 | id: "a2", 19 | name: "Bob" 20 | } 21 | }, 22 | { 23 | __typename: "Book", 24 | id: "3", 25 | upc: "b3", 26 | author: { 27 | __typename: "Author", 28 | id: "a3", 29 | name: "Jack" 30 | } 31 | } 32 | ] -------------------------------------------------------------------------------- /src/test-suites/null-keys/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import test from "./test.js"; 6 | 7 | export default serve("null-keys", [a, b, c], test); 8 | -------------------------------------------------------------------------------- /src/test-suites/null-keys/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | bookContainers { 8 | book { 9 | upc 10 | author { 11 | name 12 | } 13 | } 14 | } 15 | } 16 | `, 17 | { 18 | data: { 19 | bookContainers: [ 20 | { 21 | book: { 22 | upc: "b1", 23 | author: { 24 | name: "Alice" 25 | } 26 | } 27 | }, 28 | { 29 | book: { 30 | upc: "b2", 31 | author: { 32 | name: "Bob" 33 | } 34 | } 35 | }, 36 | { 37 | book: { 38 | upc: "b3", 39 | author: null, 40 | } 41 | } 42 | ] 43 | }, 44 | }, 45 | ), 46 | ]; 47 | -------------------------------------------------------------------------------- /src/test-suites/override-type-interface/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { imagePosts } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | interface Post { 10 | id: ID! 11 | createdAt: String! 12 | } 13 | 14 | type ImagePost implements Post @key(fields: "id") { 15 | id: ID! 16 | createdAt: String! 17 | } 18 | 19 | type Query { 20 | feed: [Post] 21 | } 22 | `, 23 | resolvers: { 24 | Query: { 25 | feed() { 26 | return imagePosts; 27 | }, 28 | }, 29 | ImagePost: { 30 | __resolveReference(key: { id: string }) { 31 | const post = imagePosts.find((p) => p.id === key.id); 32 | 33 | if (!post) { 34 | return null; 35 | } 36 | 37 | return { 38 | id: post.id, 39 | createdAt: post.createdAt, 40 | }; 41 | }, 42 | createdAt() { 43 | return "NEVER"; 44 | }, 45 | }, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/test-suites/override-type-interface/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { imagePosts, textPosts } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable", "@override"] 10 | ) 11 | 12 | interface Post { 13 | id: ID! 14 | createdAt: String! 15 | } 16 | 17 | type TextPost implements Post @key(fields: "id") { 18 | id: ID! 19 | createdAt: String! 20 | body: String! 21 | } 22 | 23 | interface AnotherPost { 24 | id: ID! 25 | createdAt: String! 26 | } 27 | 28 | type ImagePost implements AnotherPost @key(fields: "id") { 29 | id: ID! 30 | createdAt: String! @override(from: "a") 31 | } 32 | 33 | type Query { 34 | anotherFeed: [AnotherPost] 35 | } 36 | `, 37 | resolvers: { 38 | Query: { 39 | anotherFeed() { 40 | return imagePosts; 41 | }, 42 | }, 43 | TextPost: { 44 | __resolveReference(key: { id: string }) { 45 | const post = textPosts.find((p) => p.id === key.id); 46 | 47 | if (!post) { 48 | return null; 49 | } 50 | 51 | return { 52 | id: post.id, 53 | createdAt: post.createdAt, 54 | body: post.body, 55 | }; 56 | }, 57 | }, 58 | ImagePost: { 59 | __resolveReference(key: { id: string }) { 60 | const post = imagePosts.find((p) => p.id === key.id); 61 | 62 | if (!post) { 63 | return null; 64 | } 65 | 66 | return { 67 | id: post.id, 68 | createdAt: post.createdAt, 69 | }; 70 | }, 71 | }, 72 | }, 73 | }); 74 | -------------------------------------------------------------------------------- /src/test-suites/override-type-interface/data.ts: -------------------------------------------------------------------------------- 1 | export const imagePosts = [ 2 | { 3 | __typename: "ImagePost", 4 | id: "i1", 5 | createdAt: "i1-createdAt", 6 | }, 7 | { 8 | __typename: "ImagePost", 9 | id: "i2", 10 | createdAt: "i2-createdAt", 11 | }, 12 | ]; 13 | 14 | export const textPosts = [ 15 | { 16 | __typename: "TextPost", 17 | id: "t1", 18 | createdAt: "t1-createdAt", 19 | body: "t1-body", 20 | }, 21 | { 22 | __typename: "TextPost", 23 | id: "t2", 24 | createdAt: "t2-createdAt", 25 | body: "t2-body", 26 | }, 27 | ]; 28 | -------------------------------------------------------------------------------- /src/test-suites/override-type-interface/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("override-type-interface", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/override-type-interface/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | feed { 8 | id 9 | createdAt 10 | } 11 | } 12 | `, 13 | { 14 | data: { 15 | feed: [ 16 | { 17 | id: "i1", 18 | createdAt: "i1-createdAt", 19 | }, 20 | { 21 | id: "i2", 22 | createdAt: "i2-createdAt", 23 | }, 24 | ], 25 | }, 26 | }, 27 | ), 28 | createTest( 29 | /* GraphQL */ ` 30 | query { 31 | feed { 32 | ... on TextPost { 33 | id 34 | body 35 | } 36 | } 37 | } 38 | `, 39 | { 40 | data: { 41 | feed: [{}, {}], 42 | }, 43 | }, 44 | ), 45 | createTest( 46 | /* GraphQL */ ` 47 | query { 48 | anotherFeed { 49 | createdAt 50 | } 51 | } 52 | `, 53 | { 54 | data: { 55 | anotherFeed: [ 56 | { 57 | createdAt: "i1-createdAt", 58 | }, 59 | { 60 | createdAt: "i2-createdAt", 61 | }, 62 | ], 63 | }, 64 | }, 65 | ), 66 | createTest( 67 | /* GraphQL */ ` 68 | { 69 | anotherFeed { 70 | createdAt 71 | id 72 | ... on ImagePost { 73 | createdAt 74 | id 75 | } 76 | } 77 | } 78 | `, 79 | { 80 | data: { 81 | anotherFeed: [ 82 | { 83 | createdAt: "i1-createdAt", 84 | id: "i1", 85 | }, 86 | { 87 | createdAt: "i2-createdAt", 88 | id: "i2", 89 | }, 90 | ], 91 | }, 92 | }, 93 | ), 94 | ]; 95 | -------------------------------------------------------------------------------- /src/test-suites/override-with-requires/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@requires", "@external"] 10 | ) 11 | 12 | type User @key(fields: "id") { 13 | id: ID! 14 | name: String! @external 15 | aName: String! @requires(fields: "name") 16 | } 17 | 18 | type Query { 19 | userInA: User 20 | } 21 | `, 22 | resolvers: { 23 | Query: { 24 | userInA() { 25 | return users[0]; 26 | }, 27 | }, 28 | User: { 29 | __resolveReference(key: { id: string } | { id: string; name: string }) { 30 | const user = users.find((u) => u.id === key.id); 31 | 32 | if (!user) { 33 | return null; 34 | } 35 | 36 | if ("name" in key) { 37 | return { 38 | id: user.id, 39 | name: user.name, 40 | }; 41 | } 42 | 43 | return { 44 | id: user.id, 45 | }; 46 | }, 47 | aName(user: { name: string }) { 48 | return `a__${user.name}`; 49 | }, 50 | name() { 51 | return "NEVER"; 52 | }, 53 | }, 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /src/test-suites/override-with-requires/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@override"] 10 | ) 11 | 12 | type User @key(fields: "id") { 13 | id: ID! 14 | name: String! @override(from: "c") 15 | } 16 | 17 | type Query { 18 | userInB: User 19 | } 20 | `, 21 | resolvers: { 22 | Query: { 23 | userInB() { 24 | return users[1]; 25 | }, 26 | }, 27 | User: { 28 | __resolveReference(key: { id: string }) { 29 | const user = users.find((u) => u.id === key.id); 30 | 31 | if (!user) { 32 | return null; 33 | } 34 | 35 | return { 36 | id: user.id, 37 | name: user.name, 38 | }; 39 | }, 40 | }, 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /src/test-suites/override-with-requires/c.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("c", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@requires", "@external"] 10 | ) 11 | 12 | type User @key(fields: "id") { 13 | id: ID! 14 | name: String! @external 15 | cName: String! @requires(fields: "name") 16 | } 17 | 18 | type Query { 19 | userInC: User 20 | } 21 | `, 22 | resolvers: { 23 | Query: { 24 | userInC() { 25 | return users[2]; 26 | }, 27 | }, 28 | User: { 29 | __resolveReference(key: { id: string } | { id: string; name: string }) { 30 | const user = users.find((u) => u.id === key.id); 31 | 32 | if (!user) { 33 | return null; 34 | } 35 | 36 | if ("name" in key) { 37 | return { 38 | id: user.id, 39 | name: user.name, 40 | }; 41 | } 42 | 43 | return { 44 | id: user.id, 45 | }; 46 | }, 47 | name() { 48 | return "NEVER"; 49 | }, 50 | cName(user: { name: string }) { 51 | return `c__${user.name}`; 52 | }, 53 | }, 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /src/test-suites/override-with-requires/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | name: "u1-name", 5 | }, 6 | { 7 | id: "u2", 8 | name: "u2-name", 9 | }, 10 | { 11 | id: "u3", 12 | name: "u3-name", 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /src/test-suites/override-with-requires/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import test from "./test.js"; 6 | 7 | export default serve("override-with-requires", [a, b, c], test); 8 | -------------------------------------------------------------------------------- /src/test-suites/override-with-requires/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | { 7 | userInA { 8 | id 9 | name 10 | aName 11 | cName 12 | } 13 | userInB { 14 | id 15 | name 16 | aName 17 | cName 18 | } 19 | userInC { 20 | id 21 | name 22 | aName 23 | cName 24 | } 25 | } 26 | `, 27 | { 28 | data: { 29 | userInA: { 30 | id: "u1", 31 | name: "u1-name", 32 | aName: "a__u1-name", 33 | cName: "c__u1-name", 34 | }, 35 | userInB: { 36 | id: "u2", 37 | name: "u2-name", 38 | aName: "a__u2-name", 39 | cName: "c__u2-name", 40 | }, 41 | userInC: { 42 | id: "u3", 43 | name: "u3-name", 44 | aName: "a__u3-name", 45 | cName: "c__u3-name", 46 | }, 47 | }, 48 | }, 49 | ), 50 | createTest( 51 | /* GraphQL */ ` 52 | query { 53 | userInC { 54 | cName 55 | } 56 | } 57 | `, 58 | { 59 | data: { 60 | userInC: { 61 | cName: "c__u3-name", 62 | }, 63 | }, 64 | }, 65 | ), 66 | createTest( 67 | /* GraphQL */ ` 68 | query { 69 | userInA { 70 | cName 71 | } 72 | } 73 | `, 74 | { 75 | data: { 76 | userInA: { 77 | cName: "c__u1-name", 78 | }, 79 | }, 80 | }, 81 | ), 82 | createTest( 83 | /* GraphQL */ ` 84 | query { 85 | userInA { 86 | aName 87 | } 88 | } 89 | `, 90 | { 91 | data: { 92 | userInA: { 93 | aName: "a__u1-name", 94 | }, 95 | }, 96 | }, 97 | ), 98 | ]; 99 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call-complex/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | 3 | export default createSubgraph("a", { 4 | typeDefs: /* GraphQL */ ` 5 | extend schema 6 | @link( 7 | url: "https://specs.apollo.dev/federation/v2.3" 8 | import: ["@key", "@external", "@shareable"] 9 | ) 10 | 11 | type Product @key(fields: "id") { 12 | id: ID @external 13 | category: Category @shareable 14 | } 15 | type Category { 16 | details: String 17 | } 18 | `, 19 | resolvers: { 20 | Product: { 21 | __resolveReference(product: { id: string }) { 22 | return { 23 | id: product.id, 24 | category: { details: `Details for Product#${product.id}` }, 25 | }; 26 | }, 27 | }, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call-complex/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | 3 | export default createSubgraph("b", { 4 | typeDefs: /* GraphQL */ ` 5 | extend schema 6 | @link( 7 | url: "https://specs.apollo.dev/federation/v2.3" 8 | import: ["@key", "@external", "@shareable"] 9 | ) 10 | 11 | type Product @key(fields: "id") { 12 | id: ID @external 13 | category: Category @shareable 14 | } 15 | 16 | type Category { 17 | id: ID @shareable 18 | } 19 | `, 20 | resolvers: { 21 | Product: { 22 | __resolveReference(product: { id: string }) { 23 | return { 24 | id: product.id, 25 | category: { 26 | id: 3, 27 | }, 28 | }; 29 | }, 30 | }, 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call-complex/c.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | 3 | export default createSubgraph("c", { 4 | typeDefs: /* GraphQL */ ` 5 | extend schema 6 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 7 | type Category @key(fields: "id") { 8 | id: ID 9 | name: String 10 | } 11 | `, 12 | resolvers: { 13 | Category: { 14 | __resolveReference(category: { id: string }) { 15 | return { 16 | id: category.id, 17 | name: `Category#${category.id}`, 18 | }; 19 | }, 20 | }, 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call-complex/d.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | 3 | export default createSubgraph("d", { 4 | typeDefs: /* GraphQL */ ` 5 | extend schema 6 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 7 | 8 | type Query { 9 | productFromD(id: ID!): Product 10 | } 11 | 12 | type Product @key(fields: "id") { 13 | id: ID 14 | name: String 15 | } 16 | `, 17 | resolvers: { 18 | Product: { 19 | __resolveReference(product: { id: string }) { 20 | return { 21 | id: product.id, 22 | name: `Product#${product.id}`, 23 | }; 24 | }, 25 | }, 26 | Query: { 27 | productFromD(_: never, { id }: { id: string }) { 28 | return { 29 | id, 30 | name: `Product#${id}`, 31 | }; 32 | }, 33 | }, 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call-complex/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import d from "./d.subgraph.js"; 6 | import test from "./test.js"; 7 | 8 | export default serve("parent-entity-call-complex", [a, b, c, d], test); 9 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call-complex/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | productFromD(id: "1") { 8 | id 9 | name 10 | category { 11 | id 12 | name 13 | details 14 | } 15 | } 16 | } 17 | `, 18 | { 19 | data: { 20 | productFromD: { 21 | id: "1", 22 | name: "Product#1", 23 | category: { 24 | id: "3", 25 | name: "Category#3", 26 | details: "Details for Product#1", 27 | }, 28 | }, 29 | }, 30 | }, 31 | ), 32 | ]; 33 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { categories, products } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Product @key(fields: "id pid") { 13 | id: ID! 14 | pid: ID! 15 | category: Category @shareable 16 | } 17 | 18 | type Category @key(fields: "id") { 19 | id: ID! 20 | name: String! @shareable 21 | } 22 | `, 23 | resolvers: { 24 | Product: { 25 | __resolveReference(key: { id: string; pid: string }) { 26 | const product = products.find( 27 | (p) => p.id === key.id && p.pid === key.pid, 28 | ); 29 | 30 | if (!product) { 31 | return null; 32 | } 33 | 34 | return { 35 | id: product.id, 36 | pid: product.pid, 37 | categoryId: product.categoryId, 38 | }; 39 | }, 40 | category(product: { categoryId: string }) { 41 | const category = categories.find((c) => c.id === product.categoryId); 42 | 43 | if (!category) { 44 | return null; 45 | } 46 | 47 | return { 48 | id: category.id, 49 | name: category.name, 50 | }; 51 | }, 52 | }, 53 | Category: { 54 | __resolveReference(key: { id: string }) { 55 | const category = categories.find((c) => c.id === key.id); 56 | 57 | if (!category) { 58 | return null; 59 | } 60 | 61 | return { 62 | id: category.id, 63 | name: category.name, 64 | }; 65 | }, 66 | }, 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call/c.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { categories, products } from "./data.js"; 3 | 4 | export default createSubgraph("c", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Category { 13 | details: CategoryDetails 14 | } 15 | 16 | type Product @key(fields: "id pid") { 17 | id: ID! 18 | pid: ID! 19 | category: Category @shareable 20 | } 21 | 22 | type CategoryDetails { 23 | products: Int 24 | } 25 | `, 26 | resolvers: { 27 | Product: { 28 | __resolveReference(key: { id: string; pid: string }) { 29 | const product = products.find( 30 | (p) => p.id === key.id && p.pid === key.pid, 31 | ); 32 | 33 | if (!product) { 34 | return null; 35 | } 36 | 37 | return { 38 | id: product.id, 39 | pid: product.pid, 40 | categoryId: product.categoryId, 41 | }; 42 | }, 43 | category(product: { categoryId: string }) { 44 | const category = categories.find((c) => c.id === product.categoryId); 45 | 46 | if (!category) { 47 | return null; 48 | } 49 | 50 | return { 51 | details: category.details, 52 | }; 53 | }, 54 | }, 55 | }, 56 | }); 57 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call/data.ts: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | id: "p1", 4 | pid: "p1-pid", 5 | categoryId: "c1", 6 | }, 7 | { 8 | id: "p2", 9 | pid: "p2-pid", 10 | categoryId: "c2", 11 | }, 12 | { 13 | id: "p3", 14 | pid: "p3-pid", 15 | categoryId: "c1", 16 | }, 17 | ]; 18 | 19 | export const categories = [ 20 | { 21 | id: "c1", 22 | name: "c1-name", 23 | details: { 24 | products: products.filter((p) => p.categoryId === "c1").length, 25 | }, 26 | }, 27 | { 28 | id: "c2", 29 | name: "c2-name", 30 | details: { 31 | products: products.filter((p) => p.categoryId === "c2").length, 32 | }, 33 | }, 34 | ]; 35 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import test from "./test.js"; 6 | 7 | export default serve("parent-entity-call", [a, b, c], test); 8 | -------------------------------------------------------------------------------- /src/test-suites/parent-entity-call/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | products { 8 | id 9 | category { 10 | id 11 | details { 12 | products 13 | } 14 | } 15 | } 16 | } 17 | `, 18 | { 19 | data: { 20 | products: [ 21 | { 22 | id: "p1", 23 | category: { 24 | id: "c1", 25 | details: { 26 | products: 2, 27 | }, 28 | }, 29 | }, 30 | { 31 | id: "p2", 32 | category: { 33 | id: "c2", 34 | details: { 35 | products: 1, 36 | }, 37 | }, 38 | }, 39 | { 40 | id: "p3", 41 | category: { 42 | id: "c1", 43 | details: { 44 | products: 2, 45 | }, 46 | }, 47 | }, 48 | ], 49 | }, 50 | }, 51 | ), 52 | ]; 53 | -------------------------------------------------------------------------------- /src/test-suites/provides-on-interface/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { animals, medias } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable", "@provides", "@external"] 10 | ) 11 | 12 | type Query { 13 | media: Media @shareable @provides(fields: "animals { id name }") 14 | } 15 | 16 | interface Media { 17 | id: ID! 18 | animals: [Animal] 19 | } 20 | 21 | interface Animal { 22 | id: ID! 23 | name: String 24 | } 25 | 26 | type Book implements Media { 27 | id: ID! @shareable 28 | animals: [Animal] @external 29 | } 30 | 31 | type Dog implements Animal { 32 | id: ID! @external 33 | name: String @external 34 | } 35 | 36 | type Cat implements Animal { 37 | id: ID! @external 38 | name: String @external 39 | } 40 | `, 41 | resolvers: { 42 | Query: { 43 | media() { 44 | const media = medias[0]; 45 | return { 46 | __typename: media.__typename, 47 | id: media.id, 48 | animals: media.animals.map((animalId) => { 49 | const animal = animals.find((a) => a.id === animalId); 50 | 51 | if (!animal) { 52 | return null; 53 | } 54 | 55 | return { 56 | __typename: animal.__typename, 57 | id: animal.id, 58 | name: animal.name, 59 | }; 60 | }), 61 | }; 62 | }, 63 | }, 64 | }, 65 | }); 66 | -------------------------------------------------------------------------------- /src/test-suites/provides-on-interface/data.ts: -------------------------------------------------------------------------------- 1 | export const pubishPoorPlans = true; 2 | 3 | export const medias = [ 4 | { 5 | __typename: "Book", 6 | id: "m1", 7 | animals: ["a1", "a2"], 8 | }, 9 | ]; 10 | 11 | export const animals = [ 12 | { 13 | __typename: "Dog", 14 | id: "a1", 15 | name: "Fido", 16 | age: 3, 17 | }, 18 | { 19 | __typename: "Cat", 20 | id: "a2", 21 | name: "Whiskers", 22 | age: 6, 23 | }, 24 | ]; 25 | -------------------------------------------------------------------------------- /src/test-suites/provides-on-interface/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import test from "./test.js"; 6 | 7 | export default serve("provides-on-interface", [a, b, c], test); 8 | -------------------------------------------------------------------------------- /src/test-suites/provides-on-interface/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | media { 8 | id 9 | animals { 10 | id 11 | name 12 | } 13 | } 14 | } 15 | `, 16 | { 17 | data: { 18 | media: { 19 | id: "m1", 20 | animals: [ 21 | { 22 | id: "a1", 23 | name: "Fido", 24 | }, 25 | { 26 | id: "a2", 27 | name: "Whiskers", 28 | }, 29 | ], 30 | }, 31 | }, 32 | }, 33 | ), 34 | createTest( 35 | /* GraphQL */ ` 36 | query { 37 | media { 38 | id 39 | animals { 40 | id 41 | name 42 | ... on Cat { 43 | age 44 | } 45 | } 46 | } 47 | } 48 | `, 49 | { 50 | data: { 51 | media: { 52 | id: "m1", 53 | animals: [ 54 | { 55 | id: "a1", 56 | name: "Fido", 57 | }, 58 | { 59 | id: "a2", 60 | name: "Whiskers", 61 | age: 6, 62 | }, 63 | ], 64 | }, 65 | }, 66 | }, 67 | ), 68 | ]; 69 | -------------------------------------------------------------------------------- /src/test-suites/provides-on-union/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { shouldPunishForPoorPlans } from "../../env.js"; 2 | import { createSubgraph } from "../../subgraph.js"; 3 | import { medias } from "./data.js"; 4 | 5 | export default createSubgraph("a", { 6 | typeDefs: /* GraphQL */ ` 7 | extend schema 8 | @link( 9 | url: "https://specs.apollo.dev/federation/v2.3" 10 | import: ["@key", "@shareable", "@external"] 11 | ) 12 | 13 | type Query { 14 | media: [Media] @shareable 15 | } 16 | 17 | union Media = Book | Movie 18 | 19 | type Book @key(fields: "id") { 20 | id: ID! 21 | } 22 | 23 | type Movie @key(fields: "id") { 24 | id: ID! 25 | } 26 | `, 27 | resolvers: { 28 | Query: { 29 | media(_p: unknown, _a: unknown, context: any) { 30 | if (shouldPunishForPoorPlans(context)) { 31 | throw new Error("You should not be using the 'a' subgraph!"); 32 | } 33 | 34 | return medias.map((media) => ({ 35 | __typename: media.__typename, 36 | id: media.id, 37 | })); 38 | }, 39 | }, 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /src/test-suites/provides-on-union/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { medias } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable", "@provides", "@external"] 10 | ) 11 | 12 | type Query { 13 | media: [Media] @shareable @provides(fields: "... on Book { title }") 14 | } 15 | 16 | union Media = Book | Movie 17 | 18 | type Book @key(fields: "id") { 19 | id: ID! 20 | title: String @external 21 | } 22 | 23 | type Movie @key(fields: "id") { 24 | id: ID! 25 | } 26 | `, 27 | resolvers: { 28 | Query: { 29 | media() { 30 | return medias.map((media) => { 31 | if (media.__typename === "Book") { 32 | return { 33 | __typename: media.__typename, 34 | id: media.id, 35 | title: media.title, 36 | }; 37 | } 38 | 39 | return { 40 | __typename: media.__typename, 41 | id: media.id, 42 | }; 43 | }); 44 | }, 45 | }, 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /src/test-suites/provides-on-union/c.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { shouldPunishForPoorPlans } from "../../env.js"; 2 | import { createSubgraph } from "../../subgraph.js"; 3 | import { medias } from "./data.js"; 4 | 5 | export default createSubgraph("c", { 6 | typeDefs: /* GraphQL */ ` 7 | extend schema 8 | @link( 9 | url: "https://specs.apollo.dev/federation/v2.3" 10 | import: ["@key", "@shareable"] 11 | ) 12 | 13 | type Book @key(fields: "id") { 14 | id: ID! 15 | title: String @shareable 16 | } 17 | 18 | type Movie @key(fields: "id") { 19 | id: ID! 20 | title: String @shareable 21 | } 22 | `, 23 | resolvers: { 24 | Book: { 25 | __resolveReference(key: { id: string }, context: any) { 26 | if (shouldPunishForPoorPlans(context)) { 27 | throw new Error("You should be using the 'b' subgraph!"); 28 | } 29 | 30 | const media = medias.find((media) => media.id === key.id); 31 | 32 | if (!media) { 33 | return null; 34 | } 35 | 36 | return { 37 | __typename: media.__typename, 38 | id: media.id, 39 | title: media.title, 40 | }; 41 | }, 42 | }, 43 | Movie: { 44 | __resolveReference(key: { id: string }) { 45 | const media = medias.find((media) => media.id === key.id); 46 | 47 | if (!media) { 48 | return null; 49 | } 50 | 51 | return { 52 | __typename: media.__typename, 53 | id: media.id, 54 | title: media.title, 55 | }; 56 | }, 57 | }, 58 | }, 59 | }); 60 | -------------------------------------------------------------------------------- /src/test-suites/provides-on-union/data.ts: -------------------------------------------------------------------------------- 1 | export const medias = [ 2 | { 3 | __typename: "Book", 4 | id: "m1", 5 | title: "Book 1", 6 | }, 7 | { 8 | __typename: "Movie", 9 | id: "m2", 10 | title: "Movie 1", 11 | }, 12 | ]; 13 | -------------------------------------------------------------------------------- /src/test-suites/provides-on-union/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import test from "./test.js"; 6 | 7 | export default serve("provides-on-union", [a, b, c], test); 8 | -------------------------------------------------------------------------------- /src/test-suites/provides-on-union/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | media { 8 | ... on Book { 9 | id 10 | title 11 | } 12 | ... on Movie { 13 | id 14 | } 15 | } 16 | } 17 | `, 18 | { 19 | data: { 20 | media: [ 21 | { 22 | id: "m1", 23 | title: "Book 1", 24 | }, 25 | { 26 | id: "m2", 27 | }, 28 | ], 29 | }, 30 | }, 31 | ), 32 | createTest( 33 | /* GraphQL */ ` 34 | query { 35 | media { 36 | ... on Book { 37 | id 38 | title 39 | } 40 | ... on Movie { 41 | id 42 | title 43 | } 44 | } 45 | } 46 | `, 47 | { 48 | data: { 49 | media: [ 50 | { 51 | id: "m1", 52 | title: "Book 1", 53 | }, 54 | { 55 | id: "m2", 56 | title: "Movie 1", 57 | }, 58 | ], 59 | }, 60 | }, 61 | ), 62 | ]; 63 | -------------------------------------------------------------------------------- /src/test-suites/requires-interface/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | name: "u1-name", 5 | address: "a1", 6 | }, 7 | { 8 | id: "u2", 9 | name: "u2-name", 10 | address: "a2", 11 | }, 12 | ]; 13 | 14 | export const addresses = [ 15 | { 16 | __typename: "HomeAddress", 17 | id: "a1", 18 | city: "a1-city", 19 | country: "a1-country", 20 | }, 21 | { 22 | __typename: "WorkAddress", 23 | id: "a2", 24 | city: "a2-city", 25 | country: "a2-country", 26 | }, 27 | ]; 28 | -------------------------------------------------------------------------------- /src/test-suites/requires-interface/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("requires-interface", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/requires-interface/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | a { 8 | city 9 | } 10 | } 11 | `, 12 | { 13 | data: { 14 | a: { 15 | city: "a1-city", 16 | }, 17 | }, 18 | }, 19 | ), 20 | createTest( 21 | /* GraphQL */ ` 22 | query { 23 | b { 24 | city 25 | } 26 | } 27 | `, 28 | { 29 | data: { 30 | b: { 31 | city: "a2-city", 32 | }, 33 | }, 34 | }, 35 | ), 36 | createTest( 37 | /* GraphQL */ ` 38 | query { 39 | a { 40 | country 41 | } 42 | } 43 | `, 44 | { 45 | data: { 46 | a: { 47 | country: null, 48 | }, 49 | }, 50 | }, 51 | ), 52 | createTest( 53 | /* GraphQL */ ` 54 | query { 55 | a { 56 | address { 57 | __typename 58 | id 59 | } 60 | } 61 | } 62 | `, 63 | { 64 | data: { 65 | a: { 66 | address: { 67 | __typename: "HomeAddress", 68 | id: "a1", 69 | }, 70 | }, 71 | }, 72 | }, 73 | ), 74 | createTest( 75 | /* GraphQL */ ` 76 | query { 77 | b { 78 | address { 79 | __typename 80 | id 81 | } 82 | } 83 | } 84 | `, 85 | { 86 | data: { 87 | b: { 88 | address: { 89 | __typename: "WorkAddress", 90 | id: "a2", 91 | }, 92 | }, 93 | }, 94 | }, 95 | ), 96 | ]; 97 | -------------------------------------------------------------------------------- /src/test-suites/requires-requires/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@inaccessible"] 10 | ) 11 | 12 | type Product @key(fields: "id") { 13 | id: ID! 14 | price: Float! @inaccessible 15 | } 16 | `, 17 | resolvers: { 18 | Product: { 19 | __resolveReference(key: { id: string }) { 20 | const product = products.find((product) => product.id === key.id); 21 | 22 | if (!product) { 23 | return null; 24 | } 25 | 26 | return { 27 | id: product.id, 28 | price: product.price, 29 | }; 30 | }, 31 | }, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /src/test-suites/requires-requires/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | type Query { 10 | product: Product 11 | } 12 | 13 | type Product @key(fields: "id") { 14 | id: ID! 15 | hasDiscount: Boolean! 16 | } 17 | `, 18 | resolvers: { 19 | Query: { 20 | product() { 21 | return { 22 | id: products[0].id, 23 | hasDiscount: products[0].hasDiscount, 24 | }; 25 | }, 26 | }, 27 | Product: { 28 | __resolveReference(key: { id: string }) { 29 | const product = products.find((product) => product.id === key.id); 30 | 31 | if (!product) { 32 | return null; 33 | } 34 | 35 | return { 36 | id: product.id, 37 | hasDiscount: product.hasDiscount, 38 | }; 39 | }, 40 | }, 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /src/test-suites/requires-requires/data.ts: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | id: "p1", 4 | price: 699.99, 5 | hasDiscount: true, 6 | }, 7 | ]; 8 | -------------------------------------------------------------------------------- /src/test-suites/requires-requires/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import d from "./d.subgraph.js"; 6 | import test from "./test.js"; 7 | 8 | export default serve("requires-requires", [a, b, c, d], test); 9 | -------------------------------------------------------------------------------- /src/test-suites/requires-requires/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | product { 8 | canAfford 9 | } 10 | } 11 | `, 12 | { 13 | data: { 14 | product: { 15 | canAfford: false, 16 | }, 17 | }, 18 | }, 19 | ), 20 | createTest( 21 | /* GraphQL */ ` 22 | query { 23 | product { 24 | isExpensive 25 | } 26 | } 27 | `, 28 | { 29 | data: { 30 | product: { 31 | isExpensive: true, 32 | }, 33 | }, 34 | }, 35 | ), 36 | createTest( 37 | /* GraphQL */ ` 38 | query { 39 | product { 40 | isExpensive 41 | canAfford 42 | } 43 | } 44 | `, 45 | { 46 | data: { 47 | product: { 48 | isExpensive: true, 49 | canAfford: false, 50 | }, 51 | }, 52 | }, 53 | ), 54 | createTest( 55 | /* GraphQL */ ` 56 | query { 57 | product { 58 | canAffordWithDiscount 59 | } 60 | } 61 | `, 62 | { 63 | data: { 64 | product: { 65 | canAffordWithDiscount: true, 66 | }, 67 | }, 68 | }, 69 | ), 70 | createTest( 71 | /* GraphQL */ ` 72 | query { 73 | product { 74 | canAfford 75 | canAffordWithDiscount 76 | } 77 | } 78 | `, 79 | { 80 | data: { 81 | product: { 82 | canAfford: false, 83 | canAffordWithDiscount: true, 84 | }, 85 | }, 86 | }, 87 | ), 88 | ]; 89 | -------------------------------------------------------------------------------- /src/test-suites/requires-with-argument/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | type Query { 10 | products: [Product] 11 | } 12 | 13 | type Product @key(fields: "upc") { 14 | upc: String! 15 | name: String 16 | price(currency: String!): Int 17 | weight: Int 18 | category: Category 19 | } 20 | 21 | type Category { 22 | averagePrice(currency: String!): Int 23 | } 24 | `, 25 | resolvers: { 26 | Query: { 27 | products() { 28 | return products.map((p) => ({ 29 | upc: p.upc, 30 | name: p.name, 31 | price: p.price, 32 | weight: p.weight, 33 | category: p.category, 34 | })); 35 | }, 36 | }, 37 | Product: { 38 | __resolveReference(key: { upc: string }) { 39 | const product = products.find((p) => p.upc === key.upc); 40 | 41 | if (!product) { 42 | return null; 43 | } 44 | 45 | return { 46 | upc: product.upc, 47 | name: product.name, 48 | price: product.price, 49 | weight: product.weight, 50 | category: product.category, 51 | }; 52 | }, 53 | }, 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /src/test-suites/requires-with-argument/c.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { comments, posts } from "./data.js"; 3 | 4 | export default createSubgraph("c", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@external", "@requires"] 10 | ) 11 | 12 | type Query { 13 | feed: [Post] 14 | } 15 | 16 | type Post @key(fields: "id") { 17 | id: ID! 18 | } 19 | 20 | type Comment @key(fields: "id") { 21 | id: ID! 22 | authorId: ID 23 | body: String! 24 | } 25 | `, 26 | resolvers: { 27 | Query: { 28 | feed() { 29 | return posts.map((post) => ({ id: post.id })); 30 | }, 31 | }, 32 | Post: { 33 | __resolveReference(key: { id: string }) { 34 | const post = posts.find((post) => post.id === key.id); 35 | 36 | if (!post) { 37 | return null; 38 | } 39 | 40 | return { 41 | id: post.id, 42 | }; 43 | }, 44 | comments(post: { id: string }, { limit }: { limit: number }) { 45 | return comments 46 | .filter((comment) => comment.postId === post.id) 47 | .slice(0, limit) 48 | .map((c) => ({ 49 | id: c.id, 50 | authorId: c.authorId, 51 | body: c.body, 52 | })); 53 | }, 54 | }, 55 | Comment: { 56 | __resolveReference(key: { id: string }) { 57 | const comment = comments.find((c) => c.id === key.id); 58 | 59 | if (!comment) { 60 | return null; 61 | } 62 | 63 | return { 64 | id: comment.id, 65 | authorId: comment.authorId, 66 | body: comment.body, 67 | }; 68 | }, 69 | }, 70 | }, 71 | }); 72 | -------------------------------------------------------------------------------- /src/test-suites/requires-with-argument/data.ts: -------------------------------------------------------------------------------- 1 | export const products = [ 2 | { 3 | upc: "p1", 4 | name: "p-name-1", 5 | price: 11, 6 | weight: 1, 7 | category: { 8 | averagePrice: 11, 9 | }, 10 | }, 11 | { 12 | upc: "p2", 13 | name: "p-name-2", 14 | price: 22, 15 | weight: 2, 16 | category: { 17 | averagePrice: 22, 18 | }, 19 | }, 20 | ]; 21 | 22 | export const posts = [ 23 | { 24 | id: "p1", 25 | body: "p1-body", 26 | }, 27 | { 28 | id: "p2", 29 | body: "p2-body", 30 | }, 31 | ]; 32 | 33 | export const comments = [ 34 | { 35 | id: "c1", 36 | postId: "p1", 37 | authorId: "a2", 38 | body: "c1-body", 39 | }, 40 | { 41 | id: "c2", 42 | postId: "p1", 43 | authorId: "a2", 44 | body: "c2-body", 45 | }, 46 | { 47 | id: "c3", 48 | postId: "p1", 49 | authorId: "a2", 50 | body: "c3-body", 51 | }, 52 | { 53 | id: "c4", 54 | postId: "p2", 55 | authorId: "a1", 56 | body: "c4-body", 57 | }, 58 | { 59 | id: "c5", 60 | postId: "p2", 61 | authorId: "a1", 62 | body: "c5-body", 63 | }, 64 | { 65 | id: "c6", 66 | postId: "p2", 67 | authorId: "a1", 68 | body: "c6-body", 69 | }, 70 | ]; 71 | 72 | export const authors = [ 73 | { 74 | id: "a1", 75 | name: "a1-name", 76 | }, 77 | { 78 | id: "a2", 79 | name: "a2-name", 80 | }, 81 | ]; 82 | -------------------------------------------------------------------------------- /src/test-suites/requires-with-argument/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import d from "./d.subgraph.js"; 6 | import test from "./test.js"; 7 | 8 | export default serve("requires-with-argument", [a, b, c, d], test); 9 | -------------------------------------------------------------------------------- /src/test-suites/requires-with-fragments/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { bazs, entities, quxs } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.5" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Query @shareable { 13 | a: Entity 14 | } 15 | 16 | type Entity @key(fields: "id") { 17 | id: ID! 18 | data: Foo 19 | } 20 | 21 | interface Foo { 22 | foo: String! 23 | } 24 | 25 | interface Bar implements Foo { 26 | foo: String! 27 | bar: String! 28 | } 29 | 30 | type Baz implements Foo & Bar @shareable { 31 | foo: String! 32 | bar: String! 33 | baz: String! 34 | } 35 | 36 | type Qux implements Foo & Bar @shareable { 37 | foo: String! 38 | bar: String! 39 | qux: String! 40 | } 41 | `, 42 | resolvers: { 43 | Query: { 44 | a() { 45 | return entities[1]; 46 | }, 47 | }, 48 | Entity: { 49 | __resolveReference(key: { id: string }) { 50 | const entity = entities.find((e) => e.id === key.id); 51 | 52 | if (!entity) { 53 | return null; 54 | } 55 | 56 | return entity; 57 | }, 58 | data(entity: { id: string; data: string }) { 59 | const data = entities.find((e) => e.id === entity.id)?.data; 60 | 61 | if (!data) { 62 | return null; 63 | } 64 | 65 | const baz = bazs.find((b) => b.id === data); 66 | 67 | if (baz) { 68 | return baz; 69 | } 70 | 71 | const qux = quxs.find((q) => q.id === data); 72 | 73 | if (qux) { 74 | return qux; 75 | } 76 | 77 | throw new Error("Invalid data"); 78 | }, 79 | }, 80 | }, 81 | }); 82 | -------------------------------------------------------------------------------- /src/test-suites/requires-with-fragments/data.ts: -------------------------------------------------------------------------------- 1 | export const entities = [ 2 | { 3 | id: "e1", 4 | data: "b1", 5 | }, 6 | { 7 | id: "e2", 8 | data: "q1", 9 | }, 10 | ]; 11 | 12 | export const bazs = [ 13 | { 14 | __typename: "Baz", 15 | id: "b1", 16 | foo: "b1-foo", 17 | bar: "b1-bar", 18 | baz: "b1-baz", 19 | }, 20 | ]; 21 | 22 | export const quxs = [ 23 | { 24 | __typename: "Qux", 25 | id: "q1", 26 | foo: "q1-foo", 27 | bar: "q1-bar", 28 | qux: "q1-qux", 29 | }, 30 | ]; 31 | -------------------------------------------------------------------------------- /src/test-suites/requires-with-fragments/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("requires-with-fragments", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/shared-root/category.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { product } from "./data.js"; 3 | 4 | export default createSubgraph("category", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | product: Product! @shareable 14 | products: [Product!]! @shareable 15 | } 16 | 17 | type Product { 18 | id: ID! @shareable 19 | category: Category! 20 | } 21 | 22 | type Category { 23 | id: ID! 24 | name: String! 25 | } 26 | `, 27 | resolvers: { 28 | Query: { 29 | product() { 30 | return { 31 | id: product.id, 32 | category: product.category, 33 | }; 34 | }, 35 | products() { 36 | return [ 37 | { 38 | id: product.id, 39 | category: product.category, 40 | }, 41 | ]; 42 | }, 43 | }, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /src/test-suites/shared-root/data.ts: -------------------------------------------------------------------------------- 1 | export const product = { 2 | id: "1", 3 | name: { 4 | id: "1", 5 | brand: "Brand 1", 6 | model: "Model 1", 7 | }, 8 | category: { 9 | id: "1", 10 | name: "Category 1", 11 | }, 12 | price: { 13 | id: "1", 14 | amount: 1000, 15 | currency: "USD", 16 | }, 17 | address: { 18 | id: "1", 19 | street: "123 Main St", 20 | city: "Springfield", 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/test-suites/shared-root/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import name from "./name.subgraph.js"; 3 | import price from "./price.subgraph.js"; 4 | import category from "./category.subgraph.js"; 5 | import test from "./test.js"; 6 | 7 | export default serve("shared-root", [name, price, category], test); 8 | -------------------------------------------------------------------------------- /src/test-suites/shared-root/name.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { product } from "./data.js"; 3 | 4 | export default createSubgraph("name", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | product: Product! @shareable 14 | products: [Product!]! @shareable 15 | } 16 | 17 | type Product { 18 | id: ID! @shareable 19 | name: Name! 20 | } 21 | 22 | type Name { 23 | id: ID! 24 | brand: String! 25 | model: String! 26 | } 27 | `, 28 | resolvers: { 29 | Query: { 30 | product() { 31 | return { 32 | id: product.id, 33 | name: product.name, 34 | }; 35 | }, 36 | products() { 37 | return [ 38 | { 39 | id: product.id, 40 | name: product.name, 41 | }, 42 | ]; 43 | }, 44 | }, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /src/test-suites/shared-root/price.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { product } from "./data.js"; 3 | 4 | export default createSubgraph("price", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | product: Product! @shareable 14 | products: [Product!]! @shareable 15 | } 16 | 17 | type Product { 18 | id: ID! @shareable 19 | price: Price! 20 | } 21 | 22 | type Price { 23 | id: ID! 24 | amount: Int! 25 | currency: String! 26 | } 27 | `, 28 | resolvers: { 29 | Query: { 30 | product() { 31 | return { 32 | id: product.id, 33 | price: product.price, 34 | }; 35 | }, 36 | products() { 37 | return [ 38 | { 39 | id: product.id, 40 | price: product.price, 41 | }, 42 | ]; 43 | }, 44 | }, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /src/test-suites/shared-root/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | product { 8 | id 9 | name { 10 | id 11 | brand 12 | model 13 | } 14 | category { 15 | id 16 | name 17 | } 18 | price { 19 | id 20 | amount 21 | currency 22 | } 23 | } 24 | } 25 | `, 26 | { 27 | data: { 28 | product: { 29 | id: "1", 30 | name: { 31 | id: "1", 32 | brand: "Brand 1", 33 | model: "Model 1", 34 | }, 35 | price: { 36 | id: "1", 37 | amount: 1000, 38 | currency: "USD", 39 | }, 40 | category: { 41 | id: "1", 42 | name: "Category 1", 43 | } 44 | }, 45 | }, 46 | }, 47 | ), 48 | createTest( 49 | /* GraphQL */ ` 50 | query { 51 | products { 52 | id 53 | name { 54 | id 55 | brand 56 | model 57 | } 58 | category { 59 | id 60 | name 61 | } 62 | price { 63 | id 64 | amount 65 | currency 66 | } 67 | } 68 | } 69 | `, 70 | { 71 | data: { 72 | products: [ 73 | { 74 | id: "1", 75 | name: { 76 | id: "1", 77 | brand: "Brand 1", 78 | model: "Model 1", 79 | }, 80 | price: { 81 | id: "1", 82 | amount: 1000, 83 | currency: "USD", 84 | }, 85 | category: { 86 | id: "1", 87 | name: "Category 1", 88 | } 89 | }, 90 | ], 91 | }, 92 | }, 93 | ), 94 | ]; 95 | -------------------------------------------------------------------------------- /src/test-suites/simple-entity-call/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "1", 4 | email: "user1@gmail.com", 5 | nickname: "user1", 6 | }, 7 | { 8 | id: "2", 9 | email: "user2@gmail.com", 10 | nickname: "user2", 11 | }, 12 | ]; 13 | -------------------------------------------------------------------------------- /src/test-suites/simple-entity-call/email.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("email", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) 8 | 9 | type Query { 10 | user: User 11 | } 12 | 13 | type User @key(fields: "id") { 14 | id: ID! 15 | email: String! 16 | } 17 | `, 18 | resolvers: { 19 | Query: { 20 | user() { 21 | return { 22 | id: users[0].id, 23 | email: users[0].email, 24 | }; 25 | }, 26 | }, 27 | User: { 28 | __resolveReference(key: { id: string }) { 29 | const user = users.find((u) => u.id === key.id); 30 | 31 | if (!user) { 32 | return null; 33 | } 34 | 35 | return { 36 | id: user.id, 37 | email: user.email, 38 | }; 39 | }, 40 | }, 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /src/test-suites/simple-entity-call/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import email from "./email.subgraph.js"; 3 | import nickname from "./nickname.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("simple-entity-call", [email, nickname], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/simple-entity-call/nickname.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("nickname", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.0" 9 | import: ["@key", "@external"] 10 | ) 11 | 12 | type User @key(fields: "email") { 13 | email: String! @external 14 | nickname: String! 15 | } 16 | `, 17 | resolvers: { 18 | User: { 19 | __resolveReference(key: { email: string }) { 20 | const user = users.find((u) => u.email === key.email); 21 | 22 | if (!user) { 23 | return null; 24 | } 25 | 26 | return { 27 | nickname: user.nickname, 28 | }; 29 | }, 30 | }, 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /src/test-suites/simple-entity-call/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default [ 5 | createTest( 6 | /* GraphQL */ ` 7 | query { 8 | user { 9 | id 10 | nickname 11 | } 12 | } 13 | `, 14 | { 15 | data: { 16 | user: { 17 | id: users[0].id, 18 | nickname: users[0].nickname, 19 | }, 20 | }, 21 | }, 22 | ), 23 | ]; 24 | -------------------------------------------------------------------------------- /src/test-suites/simple-inaccessible/age.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("age", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@inaccessible", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | usersInAge: [User!]! @shareable 14 | } 15 | 16 | type User @key(fields: "id") { 17 | id: ID 18 | age: Int 19 | } 20 | `, 21 | resolvers: { 22 | Query: { 23 | usersInAge() { 24 | return users.map((u) => ({ 25 | id: u.id, 26 | age: u.age, 27 | })); 28 | }, 29 | }, 30 | User: { 31 | __resolveReference(key: { id: string }) { 32 | const user = users.find((u) => u.id === key.id); 33 | 34 | if (!user) { 35 | return null; 36 | } 37 | 38 | return { 39 | id: user.id, 40 | age: user.age, 41 | }; 42 | }, 43 | }, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /src/test-suites/simple-inaccessible/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | age: 11, 5 | friends: ["u2"], 6 | }, 7 | { 8 | id: "u2", 9 | age: 22, 10 | friends: ["u1"], 11 | }, 12 | ]; 13 | -------------------------------------------------------------------------------- /src/test-suites/simple-inaccessible/friends.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("friends", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@inaccessible", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | usersInFriends: [User!]! 14 | } 15 | 16 | type User @key(fields: "id") { 17 | id: ID 18 | friends(type: FriendType = FAMILY @inaccessible): [User!]! 19 | type: FriendType 20 | } 21 | 22 | enum FriendType { 23 | FAMILY @inaccessible 24 | FRIEND 25 | } 26 | `, 27 | resolvers: { 28 | Query: { 29 | usersInFriends() { 30 | return users.map((u) => ({ 31 | id: u.id, 32 | friends: u.friends, 33 | })); 34 | }, 35 | }, 36 | User: { 37 | __resolveReference(key: { id: string }) { 38 | const user = users.find((u) => u.id === key.id); 39 | 40 | if (!user) { 41 | return null; 42 | } 43 | 44 | return { 45 | id: user.id, 46 | friends: user.friends, 47 | }; 48 | }, 49 | friends(user: { id: string; friends: string[] }) { 50 | return user.friends.map((id) => { 51 | const friend = users.find((u) => u.id === id); 52 | 53 | if (!friend) { 54 | return null; 55 | } 56 | 57 | return { 58 | id: friend.id, 59 | friends: friend.friends, 60 | }; 61 | }); 62 | }, 63 | type() { 64 | return "FAMILY"; 65 | }, 66 | }, 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /src/test-suites/simple-inaccessible/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import friends from "./friends.subgraph.js"; 3 | import age from "./age.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("simple-inaccessible", [friends, age], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/simple-interface-object/c.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { accounts } from "./data.js"; 3 | 4 | export default createSubgraph("c", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@interfaceObject", "@shareable"] 10 | ) 11 | 12 | type Account @key(fields: "id") @interfaceObject { 13 | id: ID! 14 | isActive: Boolean! @shareable 15 | } 16 | `, 17 | resolvers: { 18 | Account: { 19 | __resolveReference(key: { __typename: string; id: string }) { 20 | const account = accounts.find((account) => account.id === key.id); 21 | 22 | if (!account) { 23 | return null; 24 | } 25 | 26 | return { 27 | // I deliberately return a wrong __typename here to make sure it's not used by the gateway 28 | __typename: "Never", 29 | id: account.id, 30 | }; 31 | }, 32 | isActive(account: { id: string }) { 33 | return false; 34 | }, 35 | }, 36 | Query: { 37 | accounts() { 38 | return accounts.map((user) => { 39 | return { 40 | // I deliberately return a wrong __typename here to make sure it's not used by the gateway 41 | __typename: "Never", 42 | id: user.id, 43 | }; 44 | }); 45 | }, 46 | }, 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /src/test-suites/simple-interface-object/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | name: "u1-name", 5 | username: "u1-username", 6 | age: 11, 7 | }, 8 | { 9 | id: "u2", 10 | name: "u2-name", 11 | username: "u2-username", 12 | age: 22, 13 | }, 14 | ]; 15 | 16 | export const accounts = [ 17 | { 18 | __typename: "Admin", 19 | id: "u1", 20 | name: "Alice", 21 | isMain: false, 22 | isActive: true, 23 | }, 24 | { 25 | __typename: "Admin", 26 | id: "u2", 27 | name: "Bob", 28 | isMain: true, 29 | isActive: true, 30 | }, 31 | { 32 | __typename: "Regular", 33 | id: "u3", 34 | name: "Charlie", 35 | isMain: false, 36 | isActive: true, 37 | }, 38 | ]; 39 | -------------------------------------------------------------------------------- /src/test-suites/simple-interface-object/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import c from "./c.subgraph.js"; 5 | import test from "./test.js"; 6 | 7 | export default serve("simple-interface-object", [a, b, c], test); 8 | -------------------------------------------------------------------------------- /src/test-suites/simple-override/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { posts } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Post @key(fields: "id") { 13 | id: ID! 14 | createdAt: String! @shareable 15 | } 16 | 17 | type Query { 18 | feed: [Post] @shareable 19 | aFeed: [Post] 20 | } 21 | `, 22 | resolvers: { 23 | Query: { 24 | feed() { 25 | return posts; 26 | }, 27 | aFeed() { 28 | return [posts[1]]; 29 | }, 30 | }, 31 | Post: { 32 | __resolveReference(key: { id: string }) { 33 | const post = posts.find((p) => p.id === key.id); 34 | 35 | if (!post) { 36 | return null; 37 | } 38 | 39 | return { 40 | id: post.id, 41 | createdAt: post.createdAt, 42 | }; 43 | }, 44 | createdAt() { 45 | return "NEVER"; 46 | }, 47 | }, 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /src/test-suites/simple-override/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { posts } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@override", "@shareable"] 10 | ) 11 | 12 | type Post @key(fields: "id") { 13 | id: ID! 14 | createdAt: String! @override(from: "a") @shareable 15 | } 16 | 17 | type Query { 18 | feed: [Post] @shareable 19 | bFeed: [Post] 20 | } 21 | `, 22 | resolvers: { 23 | Query: { 24 | feed() { 25 | return posts; 26 | }, 27 | bFeed() { 28 | return [posts[0]]; 29 | }, 30 | }, 31 | Post: { 32 | __resolveReference(key: { id: string }) { 33 | const post = posts.find((p) => p.id === key.id); 34 | 35 | if (!post) { 36 | return null; 37 | } 38 | 39 | return { 40 | id: post.id, 41 | createdAt: post.createdAt, 42 | }; 43 | }, 44 | }, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /src/test-suites/simple-override/data.ts: -------------------------------------------------------------------------------- 1 | export const posts = [ 2 | { 3 | id: "p1", 4 | createdAt: "p1-createdAt", 5 | }, 6 | { 7 | id: "p2", 8 | createdAt: "p2-createdAt", 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /src/test-suites/simple-override/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("simple-override", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/simple-override/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | feed { 8 | createdAt 9 | } 10 | } 11 | `, 12 | { 13 | data: { 14 | feed: [ 15 | { 16 | createdAt: "p1-createdAt", 17 | }, 18 | { 19 | createdAt: "p2-createdAt", 20 | }, 21 | ], 22 | }, 23 | }, 24 | ), 25 | createTest( 26 | /* GraphQL */ ` 27 | query { 28 | aFeed { 29 | createdAt 30 | } 31 | bFeed { 32 | createdAt 33 | } 34 | } 35 | `, 36 | { 37 | data: { 38 | aFeed: [ 39 | { 40 | createdAt: "p2-createdAt", 41 | }, 42 | ], 43 | bFeed: [ 44 | { 45 | createdAt: "p1-createdAt", 46 | }, 47 | ], 48 | }, 49 | }, 50 | ), 51 | ]; 52 | -------------------------------------------------------------------------------- /src/test-suites/simple-requires-provides/accounts.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("accounts", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Query { 13 | me: User 14 | } 15 | 16 | type User @key(fields: "id") { 17 | id: ID! 18 | name: String 19 | username: String @shareable 20 | } 21 | `, 22 | resolvers: { 23 | Query: { 24 | me() { 25 | return { 26 | id: users[0].id, 27 | name: users[0].name, 28 | username: users[0].username, 29 | }; 30 | }, 31 | }, 32 | User: { 33 | __resolveReference(key: { id: string }) { 34 | const user = users.find((u) => u.id === key.id); 35 | 36 | if (!user) { 37 | return null; 38 | } 39 | 40 | return { 41 | id: user.id, 42 | name: user.name, 43 | username: user.username, 44 | }; 45 | }, 46 | }, 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /src/test-suites/simple-requires-provides/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | id: "u1", 4 | username: "u-username-1", 5 | name: "u-name-1", 6 | }, 7 | { 8 | id: "u2", 9 | username: "u-username-2", 10 | name: "u-name-2", 11 | }, 12 | ]; 13 | 14 | export const products = [ 15 | { 16 | upc: "p1", 17 | name: "p-name-1", 18 | price: 11, 19 | weight: 1, 20 | }, 21 | { 22 | upc: "p2", 23 | name: "p-name-2", 24 | price: 22, 25 | weight: 2, 26 | }, 27 | ]; 28 | 29 | export const inStock = [products[0].upc]; 30 | 31 | export const reviews = [ 32 | { 33 | id: "r1", 34 | body: "r-body-1", 35 | authorId: users[0].id, 36 | productUpc: products[0].upc, 37 | }, 38 | { 39 | id: "r2", 40 | body: "r-body-2", 41 | authorId: users[0].id, 42 | productUpc: products[1].upc, 43 | }, 44 | ]; 45 | -------------------------------------------------------------------------------- /src/test-suites/simple-requires-provides/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import accounts from "./accounts.subgraph.js"; 3 | import inventory from "./inventory.subgraph.js"; 4 | import products from "./products.subgraph.js"; 5 | import reviews from "./reviews.subgraph.js"; 6 | import test from "./test.js"; 7 | 8 | export default serve( 9 | "simple-requires-provides", 10 | [accounts, inventory, products, reviews], 11 | test, 12 | ); 13 | -------------------------------------------------------------------------------- /src/test-suites/simple-requires-provides/inventory.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { inStock, products } from "./data.js"; 3 | 4 | export default createSubgraph("inventory", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@external", "@requires"] 10 | ) 11 | 12 | type Product @key(fields: "upc") { 13 | upc: String! 14 | weight: Int @external 15 | price: Int @external 16 | inStock: Boolean 17 | shippingEstimate: Int @requires(fields: "price weight") 18 | shippingEstimateTag: String @requires(fields: "price weight") 19 | } 20 | `, 21 | resolvers: { 22 | Product: { 23 | __resolveReference( 24 | key: { upc: string; price: number; weight: number } | { upc: string }, 25 | ) { 26 | const product = products.find((p) => p.upc === key.upc); 27 | 28 | if (!product) { 29 | return null; 30 | } 31 | 32 | if ("weight" in key && "price" in key) { 33 | return { 34 | upc: product.upc, 35 | weight: key.weight, 36 | price: key.price, 37 | }; 38 | } 39 | 40 | return { 41 | upc: product.upc, 42 | }; 43 | }, 44 | shippingEstimate(product: { price: number; weight: number }) { 45 | return product.price * product.weight * 10; 46 | }, 47 | shippingEstimateTag(product: { 48 | upc: string; 49 | price: number; 50 | weight: number; 51 | }) { 52 | return `#${product.upc}#${product.price * product.weight * 10}#`; 53 | }, 54 | inStock(product: { upc: string }) { 55 | return inStock.includes(product.upc); 56 | }, 57 | }, 58 | }, 59 | }); 60 | -------------------------------------------------------------------------------- /src/test-suites/simple-requires-provides/products.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("products", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | type Query { 10 | products: [Product] 11 | } 12 | 13 | type Product @key(fields: "upc") { 14 | upc: String! 15 | name: String 16 | price: Int 17 | weight: Int 18 | } 19 | `, 20 | resolvers: { 21 | Query: { 22 | products() { 23 | return products.map((p) => ({ 24 | upc: p.upc, 25 | name: p.name, 26 | price: p.price, 27 | weight: p.weight, 28 | })); 29 | }, 30 | }, 31 | Product: { 32 | __resolveReference(key: { upc: string }) { 33 | const product = products.find((p) => p.upc === key.upc); 34 | 35 | if (!product) { 36 | return null; 37 | } 38 | 39 | return { 40 | upc: product.upc, 41 | name: product.name, 42 | price: product.price, 43 | weight: product.weight, 44 | }; 45 | }, 46 | }, 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /src/test-suites/typename/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | type Query { 10 | union: Product 11 | interface: Node 12 | } 13 | 14 | union Product = Oven | Toaster 15 | 16 | interface Node { 17 | id: ID! 18 | } 19 | 20 | type Oven implements Node { 21 | id: ID! 22 | } 23 | 24 | type Toaster implements Node { 25 | id: ID! 26 | } 27 | 28 | interface User @key(fields: "id") { 29 | id: ID! 30 | } 31 | 32 | type Admin implements User @key(fields: "id") { 33 | id: ID! 34 | isMain: Boolean! 35 | } 36 | `, 37 | resolvers: { 38 | Query: { 39 | union() { 40 | return { id: "1", __typename: "Oven" }; 41 | }, 42 | interface() { 43 | return { id: "2", __typename: "Toaster" }; 44 | }, 45 | }, 46 | User: { 47 | __resolveReference(key: { id: string }) { 48 | const user = users.find((user) => user.id === key.id); 49 | 50 | if (!user) { 51 | return null; 52 | } 53 | 54 | return { 55 | __typename: user.__typename, 56 | id: user.id, 57 | }; 58 | }, 59 | }, 60 | Admin: { 61 | __resolveReference(key: { id: string }) { 62 | const user = users.find((user) => user.id === key.id); 63 | 64 | if (!user) { 65 | return null; 66 | } 67 | 68 | return { 69 | __typename: user.__typename, 70 | id: user.id, 71 | isMain: user.isMain, 72 | }; 73 | }, 74 | }, 75 | }, 76 | }); 77 | -------------------------------------------------------------------------------- /src/test-suites/typename/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { users } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@interfaceObject"] 10 | ) 11 | 12 | type Query { 13 | users: [User] 14 | } 15 | 16 | type User @key(fields: "id") @interfaceObject { 17 | id: ID! 18 | name: String! 19 | } 20 | `, 21 | resolvers: { 22 | Query: { 23 | users() { 24 | return users.map((user) => { 25 | return { 26 | // I deliberately return no __typename as it should not be resolved by the @interfaceObject 27 | id: user.id, 28 | }; 29 | }); 30 | }, 31 | }, 32 | User: { 33 | __resolveReference(key: { id: string }) { 34 | const user = users.find((user) => user.id === key.id); 35 | 36 | if (!user) { 37 | return null; 38 | } 39 | 40 | return { 41 | // I deliberately return no __typename as it should not be resolved by the @interfaceObject 42 | id: user.id, 43 | }; 44 | }, 45 | name(user: { id: string }) { 46 | return users.find((u) => u.id === user.id)?.name; 47 | }, 48 | }, 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /src/test-suites/typename/data.ts: -------------------------------------------------------------------------------- 1 | export const users = [ 2 | { 3 | __typename: "Admin", 4 | id: "u1", 5 | name: "Alice", 6 | isMain: false, 7 | }, 8 | { 9 | __typename: "Admin", 10 | id: "u2", 11 | name: "Bob", 12 | isMain: true, 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /src/test-suites/typename/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("typename", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/unavailable-override/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { posts } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@shareable"] 10 | ) 11 | 12 | type Post @key(fields: "id") { 13 | id: ID! 14 | createdAt: String! @shareable 15 | } 16 | 17 | type Query { 18 | feed: [Post] @shareable 19 | aFeed: [Post] 20 | } 21 | `, 22 | resolvers: { 23 | Query: { 24 | feed() { 25 | return posts; 26 | }, 27 | aFeed() { 28 | return [posts[1]]; 29 | }, 30 | }, 31 | Post: { 32 | __resolveReference(key: { id: string }) { 33 | const post = posts.find((p) => p.id === key.id); 34 | 35 | if (!post) { 36 | return null; 37 | } 38 | 39 | return { 40 | id: post.id, 41 | createdAt: post.createdAt, 42 | }; 43 | }, 44 | }, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /src/test-suites/unavailable-override/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { posts } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@key", "@override", "@shareable"] 10 | ) 11 | 12 | type Post @key(fields: "id") { 13 | id: ID! 14 | createdAt: String! @override(from: "non-existing") @shareable 15 | } 16 | 17 | type Query { 18 | feed: [Post] @shareable 19 | bFeed: [Post] 20 | } 21 | `, 22 | resolvers: { 23 | Query: { 24 | feed() { 25 | return posts; 26 | }, 27 | bFeed() { 28 | return [posts[0]]; 29 | }, 30 | }, 31 | Post: { 32 | __resolveReference(key: { id: string }) { 33 | const post = posts.find((p) => p.id === key.id); 34 | 35 | if (!post) { 36 | return null; 37 | } 38 | 39 | return { 40 | id: post.id, 41 | createdAt: post.createdAt, 42 | }; 43 | }, 44 | }, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /src/test-suites/unavailable-override/data.ts: -------------------------------------------------------------------------------- 1 | export const posts = [ 2 | { 3 | id: "p1", 4 | createdAt: "p1-createdAt", 5 | }, 6 | { 7 | id: "p2", 8 | createdAt: "p2-createdAt", 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /src/test-suites/unavailable-override/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("unavailable-override", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/unavailable-override/test.ts: -------------------------------------------------------------------------------- 1 | import { createTest } from "../../testkit.js"; 2 | 3 | export default [ 4 | createTest( 5 | /* GraphQL */ ` 6 | query { 7 | feed { 8 | createdAt 9 | } 10 | } 11 | `, 12 | { 13 | data: { 14 | feed: [ 15 | { 16 | createdAt: "p1-createdAt", 17 | }, 18 | { 19 | createdAt: "p2-createdAt", 20 | }, 21 | ], 22 | }, 23 | }, 24 | ), 25 | createTest( 26 | /* GraphQL */ ` 27 | query { 28 | aFeed { 29 | createdAt 30 | } 31 | bFeed { 32 | createdAt 33 | } 34 | } 35 | `, 36 | { 37 | data: { 38 | aFeed: [ 39 | { 40 | createdAt: "p2-createdAt", 41 | }, 42 | ], 43 | bFeed: [ 44 | { 45 | createdAt: "p1-createdAt", 46 | }, 47 | ], 48 | }, 49 | }, 50 | ), 51 | ]; 52 | -------------------------------------------------------------------------------- /src/test-suites/union-interface-distributed/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { products } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) 8 | 9 | interface Node { 10 | id: ID! 11 | } 12 | 13 | type Oven implements Node @key(fields: "id") { 14 | id: ID! 15 | warranty: Int 16 | } 17 | `, 18 | resolvers: { 19 | Oven: { 20 | __resolveReference(key: { id: string }) { 21 | const oven = products.find((p) => p.id === key.id); 22 | 23 | if (oven?.__typename === "Oven") { 24 | return { 25 | __typename: "Oven", 26 | id: oven.id, 27 | warranty: 1, 28 | }; 29 | } 30 | 31 | return null; 32 | }, 33 | }, 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /src/test-suites/union-interface-distributed/data.ts: -------------------------------------------------------------------------------- 1 | export const ovens = [ 2 | { 3 | __typename: "Oven", 4 | id: "oven1", 5 | warranty: 1, 6 | }, 7 | { 8 | __typename: "Oven", 9 | id: "oven2", 10 | warranty: 2, 11 | }, 12 | ]; 13 | export const toasters = [ 14 | { 15 | __typename: "Toaster", 16 | id: "toaster1", 17 | warranty: 3, 18 | }, 19 | { 20 | __typename: "Toaster", 21 | id: "toaster2", 22 | warranty: 4, 23 | }, 24 | ]; 25 | 26 | export const products = [...ovens, ...toasters]; 27 | -------------------------------------------------------------------------------- /src/test-suites/union-interface-distributed/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("union-interface-distributed", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/test-suites/union-intersection/README.md: -------------------------------------------------------------------------------- 1 | # Union Intersection 2 | 3 | Given two unions of the same name, resolve only the intersection of these unions. 4 | -------------------------------------------------------------------------------- /src/test-suites/union-intersection/a.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { media } from "./data.js"; 3 | 4 | export default createSubgraph("a", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@shareable"] 10 | ) 11 | 12 | union Media = Book | Song 13 | union ViewerMedia = Book | Song 14 | 15 | type Book { 16 | title: String! @shareable 17 | } 18 | 19 | type Song { 20 | title: String! @shareable 21 | } 22 | 23 | type Query { 24 | media: Media @shareable 25 | book: Book @shareable 26 | song: Media @shareable 27 | viewer: Viewer @shareable 28 | } 29 | 30 | type Viewer { 31 | media: ViewerMedia @shareable 32 | book: Book @shareable 33 | song: ViewerMedia @shareable 34 | } 35 | `, 36 | resolvers: { 37 | Query: { 38 | media: () => media, 39 | book: () => media, 40 | song: () => { 41 | return { 42 | __typename: "Song", 43 | title: "Song Title", 44 | }; 45 | }, 46 | viewer: () => { 47 | return { 48 | media: media, 49 | book: media, 50 | song: { 51 | __typename: "Song", 52 | title: "Song Title", 53 | }, 54 | }; 55 | }, 56 | }, 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /src/test-suites/union-intersection/b.subgraph.ts: -------------------------------------------------------------------------------- 1 | import { createSubgraph } from "../../subgraph.js"; 2 | import { media } from "./data.js"; 3 | 4 | export default createSubgraph("b", { 5 | typeDefs: /* GraphQL */ ` 6 | extend schema 7 | @link( 8 | url: "https://specs.apollo.dev/federation/v2.3" 9 | import: ["@shareable"] 10 | ) 11 | 12 | type Query { 13 | media: Media @shareable 14 | book: Media @shareable 15 | viewer: Viewer @shareable 16 | } 17 | 18 | union Media = Book | Movie 19 | union ViewerMedia = Book | Movie 20 | 21 | type Movie { 22 | title: String! @shareable 23 | } 24 | 25 | type Book { 26 | title: String! @shareable 27 | } 28 | 29 | type Viewer { 30 | media: ViewerMedia @shareable 31 | book: ViewerMedia @shareable 32 | } 33 | `, 34 | resolvers: { 35 | Query: { 36 | media: () => media, 37 | book: () => media, 38 | viewer: () => { 39 | return { 40 | media, 41 | book: media, 42 | }; 43 | }, 44 | }, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /src/test-suites/union-intersection/data.ts: -------------------------------------------------------------------------------- 1 | export const media = { 2 | __typename: "Book", 3 | title: "The Lord of the Rings", 4 | }; 5 | -------------------------------------------------------------------------------- /src/test-suites/union-intersection/index.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "../../supergraph.js"; 2 | import a from "./a.subgraph.js"; 3 | import b from "./b.subgraph.js"; 4 | import test from "./test.js"; 5 | 6 | export default serve("union-intersection", [a, b], test); 7 | -------------------------------------------------------------------------------- /src/testkit.ts: -------------------------------------------------------------------------------- 1 | export function createTest( 2 | query: string, 3 | expected: { 4 | data?: any; 5 | errors?: boolean; 6 | }, 7 | ) { 8 | return { 9 | query, 10 | expected, 11 | }; 12 | } 13 | 14 | export async function fetchTests(endpoint: string) { 15 | const response = await fetch(endpoint, { 16 | method: "GET", 17 | headers: { 18 | Accept: "application/json", 19 | }, 20 | }); 21 | 22 | if (response.status !== 200) { 23 | throw new Error( 24 | `Failed to fetch tests ${response.status} ${response.statusText}`, 25 | ); 26 | } 27 | 28 | const result = await response.json(); 29 | 30 | return result as Array>; 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "strict": true, 5 | "target": "ESNext", 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "sourceMap": true, 10 | "skipLibCheck": true, 11 | "declaration": true, 12 | "declarationMap": true 13 | }, 14 | "include": ["src"] 15 | } 16 | -------------------------------------------------------------------------------- /website/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Hive Gateway", 4 | "cases": { 5 | "total": 179, 6 | "passed": 179, 7 | "failed": 0 8 | }, 9 | "suites": { 10 | "total": 41, 11 | "passed": 41, 12 | "failed": 0 13 | } 14 | }, 15 | { 16 | "name": "Apollo Router", 17 | "cases": { 18 | "total": 179, 19 | "passed": 175, 20 | "failed": 4 21 | }, 22 | "suites": { 23 | "total": 41, 24 | "passed": 39, 25 | "failed": 2 26 | } 27 | }, 28 | { 29 | "name": "Apollo Gateway", 30 | "cases": { 31 | "total": 179, 32 | "passed": 174, 33 | "failed": 5 34 | }, 35 | "suites": { 36 | "total": 41, 37 | "passed": 38, 38 | "failed": 3 39 | } 40 | }, 41 | { 42 | "name": "Cosmo Router", 43 | "cases": { 44 | "total": 179, 45 | "passed": 170, 46 | "failed": 9 47 | }, 48 | "suites": { 49 | "total": 41, 50 | "passed": 36, 51 | "failed": 5 52 | } 53 | }, 54 | { 55 | "name": "Grafbase Gateway", 56 | "cases": { 57 | "total": 179, 58 | "passed": 164, 59 | "failed": 15 60 | }, 61 | "suites": { 62 | "total": 41, 63 | "passed": 36, 64 | "failed": 5 65 | } 66 | }, 67 | { 68 | "name": "Inigo Gateway", 69 | "cases": { 70 | "total": 179, 71 | "passed": 89, 72 | "failed": 90 73 | }, 74 | "suites": { 75 | "total": 41, 76 | "passed": 12, 77 | "failed": 29 78 | } 79 | } 80 | ] -------------------------------------------------------------------------------- /website/og-image-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/federation-gateway-audit/6848cc3c43f6eb26fd777a2ee5f2ec9dfff0a5e6/website/og-image-new.png -------------------------------------------------------------------------------- /website/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-hive/federation-gateway-audit/6848cc3c43f6eb26fd777a2ee5f2ec9dfff0a5e6/website/og-image.png -------------------------------------------------------------------------------- /website/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: --------------------------------------------------------------------------------