Skip to content

Commit 41f4a54

Browse files
authored
GraphQL Yoga driver for NestJS GraphQL (#2525)
1 parent 3fe79d1 commit 41f4a54

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+3789
-39
lines changed

.changeset/happy-months-kiss.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-yoga/nestjs-federation': major
3+
---
4+
5+
GraphQL Yoga driver with Apollo Federation for NestJS GraphQL

.changeset/little-socks-drive.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@graphql-yoga/nestjs': major
3+
---
4+
5+
GraphQL Yoga driver for NestJS GraphQL.
6+
7+
### BREAKING CHANGES
8+
9+
- No more `subscriptionWithFilter` in YogaBaseDriver.
10+
- `YogaBaseDriver.yogaInstance` has been renamed to `YogaBaseDriver.yoga`
11+
- `YogaBaseDriver` has been renamed to `AbstractYogaDriver`
12+
- Drop `@envelop/apollo-server-errors`, if you want to use it - supply it to the plugins yourself
13+
- `graphql` is now a peer dependency
14+
- `graphql-yoga` is now a peer dependency
15+
- `installSubscriptionHandlers` driver option has been dropped, please use the `subscriptions`
16+
option
17+
- Apollo Federation v2 support
18+
- Apollo Federation driver has been moved to a separate package `@graphql-yoga/nestjs-federation`
19+
- Dropped support for `@nestjs/graphql@v10`, now at least v11 is required (https://github.com/nestjs/graphql/pull/2435)
20+
- Minimum Node.js engine is v14

.github/workflows/ci.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,49 @@ jobs:
254254
failOnWarning: true
255255
failOnRequired: true
256256
debug: true
257+
258+
nestjs-apollo-federation-compatibility:
259+
runs-on: ubuntu-latest
260+
steps:
261+
- name: Checkout Repository
262+
uses: actions/checkout@v3
263+
- name: Install Node
264+
uses: actions/setup-node@v3
265+
with:
266+
node-version: ${{ matrix.node-version }}
267+
- name: Install pnpm
268+
uses: pnpm/action-setup@v2.2.4
269+
with:
270+
version: 7
271+
- name: Get pnpm store path
272+
id: pnpm-store
273+
run: echo "PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
274+
- name: Cache pnpm
275+
uses: actions/cache@v3
276+
with:
277+
path: ${{ steps.pnpm-store.outputs.PATH }}
278+
key: ${{ runner.os }}-pnpm-store-graphql-v${{ matrix.graphql-version }}-${{ hashFiles('**/pnpm-lock.yaml') }}
279+
restore-keys: |
280+
${{ runner.os }}-pnpm-store-graphql-v${{ matrix.graphql-version }}-
281+
- name: Install Dependencies
282+
run: pnpm i
283+
- name: Build Packages
284+
run: pnpm build
285+
- name: Bundle NestJS Apollo Federation Subgraph Example
286+
run: pnpm --filter=example-nestjs-apollo-federation-compatibility build
287+
- name: Install Rover
288+
run: curl -sSL https://rover.apollo.dev/nix/v0.11.1 | sh
289+
- name: Add Rover to PATH
290+
run: echo "$HOME/.rover/bin" >> $GITHUB_PATH
291+
- name: Apollo Federation Subgraph Compatibility
292+
uses: apollographql/federation-subgraph-compatibility@v1
293+
with:
294+
workingDirectory: examples/nestjs-apollo-federation-compatibility
295+
compose: docker-compose.yaml
296+
schema: schema.graphql
297+
path: /graphql
298+
# no token = no comment
299+
# token: ${{ secrets.GITHUB_TOKEN }}
300+
failOnWarning: true
301+
failOnRequired: true
302+
debug: true

.pnpmfile.cjs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// WARNING: please make sure the versions are the same across all workspaces
2+
const singletons = [
3+
'@nestjs/core',
4+
'@nestjs/common',
5+
'@nestjs/graphql',
6+
'@apollo/subgraph',
7+
'@apollo/federation-subgraph-compatibility',
8+
]
9+
10+
function afterAllResolved(lockfile, context) {
11+
context.log('Enforcing single version for: ' + singletons.join(', '))
12+
13+
// find and choose one version for the singletons
14+
const singletonsMap = {}
15+
const danglingSingletons = []
16+
for (const pkg of Object.keys(lockfile.packages)) {
17+
const singlePkg = singletons.find((singlePkg) =>
18+
pkg.startsWith(`/${singlePkg}/`),
19+
)
20+
if (!singlePkg) {
21+
continue
22+
}
23+
if (singlePkg in singletonsMap) {
24+
danglingSingletons.push(pkg)
25+
continue
26+
}
27+
singletonsMap[singlePkg] = pkg.replace(`/${singlePkg}/`, '')
28+
}
29+
30+
// remove dangling singletons from lockfile
31+
for (const dangling of danglingSingletons) {
32+
delete lockfile.packages[dangling]
33+
}
34+
35+
// apply singleton versions
36+
;[lockfile.packages, lockfile.importers].forEach((list) => {
37+
for (const info of Object.values(list)) {
38+
const deps = info.dependencies
39+
const devDeps = info.devDependencies
40+
41+
for (const [pkg, ver] of Object.entries(singletonsMap)) {
42+
if (pkg in (deps || {})) {
43+
deps[pkg] = ver
44+
}
45+
if (pkg in (devDeps || {})) {
46+
devDeps[pkg] = ver
47+
}
48+
}
49+
}
50+
})
51+
52+
return lockfile
53+
}
54+
55+
module.exports = {
56+
hooks: {
57+
afterAllResolved,
58+
},
59+
}

examples/apollo-federation-compatibility/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"author": "Charly POLY",
1515
"license": "ISC",
1616
"dependencies": {
17-
"@apollo/subgraph": "2.4.0",
17+
"@apollo/subgraph": "^2.4.0",
1818
"@graphql-yoga/plugin-apollo-inline-trace": "1.7.3",
1919
"graphql": "16.6.0",
2020
"graphql-tag": "2.12.6",

examples/apollo-federation/service/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"check": "exit 0"
88
},
99
"dependencies": {
10-
"@apollo/subgraph": "2.4.0",
10+
"@apollo/subgraph": "^2.4.0",
1111
"graphql-yoga": "3.7.3",
1212
"graphql": "16.6.0"
1313
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
services:
2+
products:
3+
image: 'node:19'
4+
user: 'node'
5+
working_dir: /web
6+
environment:
7+
- NODE_ENV=production
8+
volumes:
9+
- ./dist:/web
10+
command: 'node index.js'
11+
ports:
12+
- 4001:4001
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "example-nestjs-apollo-federation-compatibility",
3+
"version": "1.0.0",
4+
"description": "Apollo Federation implemented with GraphQL Yoga running as a Nest.js driver.",
5+
"private": true,
6+
"scripts": {
7+
"build": "nest build && node scripts/bundle.js",
8+
"prebuild": "rimraf dist",
9+
"start": "nest start",
10+
"test": "fedtest docker --compose ./docker-compose.yaml --schema ./schema.graphql --path /graphql --port 4001 --debug --format markdown"
11+
},
12+
"devDependencies": {
13+
"@apollo/federation-subgraph-compatibility": "1.2.1",
14+
"@apollo/rover": "^0.13.0",
15+
"@graphql-yoga/nestjs-federation": "0.0.0",
16+
"@grpc/proto-loader": "^0.7.5",
17+
"@nestjs/cli": "^9.3.0",
18+
"@nestjs/common": "^9.3.12",
19+
"@nestjs/core": "^9.3.12",
20+
"@nestjs/graphql": "^11.0.4",
21+
"@nestjs/microservices": "^9.3.12",
22+
"@nestjs/platform-socket.io": "^9.3.12",
23+
"@nestjs/websockets": "^9.3.12",
24+
"amqp-connection-manager": "^4.1.11",
25+
"amqplib": "^0.10.3",
26+
"cache-manager": "^5.2.0",
27+
"class-transformer": "0.3.1",
28+
"class-validator": "^0.14.0",
29+
"esbuild": "0.17.12",
30+
"graphql": "^16.6.0",
31+
"kafkajs": "^2.2.4",
32+
"mqtt": "^4.3.7",
33+
"nats": "^2.13.1",
34+
"reflect-metadata": "^0.1.13",
35+
"rimraf": "^4.1.2",
36+
"rxjs": "^7.8.0",
37+
"ts-morph": "^17.0.1",
38+
"typescript": "^4.9.5"
39+
}
40+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
extend schema
2+
@link(
3+
url: "https://specs.apollo.dev/federation/v2.3"
4+
import: [
5+
"@composeDirective"
6+
"@extends"
7+
"@external"
8+
"@key"
9+
"@inaccessible"
10+
"@interfaceObject"
11+
"@override"
12+
"@provides"
13+
"@requires"
14+
"@shareable"
15+
"@tag"
16+
]
17+
)
18+
@link(url: "https://myspecs.dev/myCustomDirective/v1.0", import: ["@custom"])
19+
@composeDirective(name: "@custom")
20+
21+
directive @custom on OBJECT
22+
23+
type Product
24+
@custom
25+
@key(fields: "id")
26+
@key(fields: "sku package")
27+
@key(fields: "sku variation { id }") {
28+
id: ID!
29+
sku: String
30+
package: String
31+
variation: ProductVariation
32+
dimensions: ProductDimension
33+
createdBy: User @provides(fields: "totalProductsCreated")
34+
notes: String @tag(name: "internal")
35+
research: [ProductResearch!]!
36+
}
37+
38+
type DeprecatedProduct @key(fields: "sku package") {
39+
sku: String!
40+
package: String!
41+
reason: String
42+
createdBy: User
43+
}
44+
45+
type ProductVariation {
46+
id: ID!
47+
}
48+
49+
type ProductResearch @key(fields: "study { caseNumber }") {
50+
study: CaseStudy!
51+
outcome: String
52+
}
53+
54+
type CaseStudy {
55+
caseNumber: ID!
56+
description: String
57+
}
58+
59+
type ProductDimension @shareable {
60+
size: String
61+
weight: Float
62+
unit: String @inaccessible
63+
}
64+
65+
extend type Query {
66+
product(id: ID!): Product
67+
deprecatedProduct(sku: String!, package: String!): DeprecatedProduct
68+
@deprecated(reason: "Use product query instead")
69+
}
70+
71+
extend type User @key(fields: "email") {
72+
averageProductsCreatedPerYear: Int
73+
@requires(fields: "totalProductsCreated yearsOfEmployment")
74+
email: ID! @external
75+
name: String @override(from: "users")
76+
totalProductsCreated: Int @external
77+
yearsOfEmployment: Int! @external
78+
}
79+
80+
type Inventory @interfaceObject @key(fields: "id") {
81+
id: ID!
82+
deprecatedProducts: [DeprecatedProduct!]!
83+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* eslint-disable */
2+
const { build } = require('esbuild')
3+
const { copyFileSync } = require('fs')
4+
const { join } = require('path')
5+
6+
async function main() {
7+
await build({
8+
entryPoints: ['./dist/main.js'],
9+
outfile: 'dist/index.js',
10+
format: 'cjs',
11+
minify: false,
12+
bundle: true,
13+
platform: 'node',
14+
target: 'es2020',
15+
loader: { '.node': 'file' },
16+
})
17+
18+
console.info(`NestJS Apollo Subgraph test build done!`)
19+
20+
copyFileSync(
21+
join(__dirname, '../schema.graphql'),
22+
join(__dirname, '../dist/schema.graphql'),
23+
)
24+
}
25+
26+
main().catch((e) => {
27+
console.error(e)
28+
process.exit(1)
29+
})

0 commit comments

Comments
 (0)