Skip to content

Commit

Permalink
Use new executor with defer/stream support
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Nov 1, 2022
1 parent 6563dbb commit 87a6c33
Show file tree
Hide file tree
Showing 19 changed files with 458 additions and 305 deletions.
5 changes: 5 additions & 0 deletions .changeset/thirty-lobsters-study.md
@@ -0,0 +1,5 @@
---
'graphql-yoga': minor
---

Engine and graphql-js version agnostic Defer/Stream support
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -40,7 +40,7 @@ jobs:
run: pnpm build

- name: Run Tests
run: pnpm test:integration
run: pnpm test:integration --ci

leaks:
name: leaks / nodejs v${{ matrix.node-version }}
Expand All @@ -63,7 +63,11 @@ jobs:
run: pnpm build

- name: Run Tests
run: pnpm test:leaks
uses: nick-fields/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
command: pnpm test:leaks --ci

esm:
runs-on: ubuntu-latest
Expand Down
@@ -0,0 +1,163 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Defer / Stream defer: defer 1`] = `
"---
Content-Type: application/json; charset=utf-8
Content-Length: 50
{"data":{"fastField":"I am speed"},"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 78
{"incremental":[{"data":{"slowField":"I am slow"},"path":[]}],"hasNext":false}
---
-----
"
`;

exports[`Defer / Stream stream: stream 1`] = `
"---
Content-Type: application/json; charset=utf-8
Content-Length: 39
{"data":{"alphabet":[]},"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 70
{"incremental":[{"items":["a"],"path":["alphabet",0]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 70
{"incremental":[{"items":["b"],"path":["alphabet",1]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 70
{"incremental":[{"items":["c"],"path":["alphabet",2]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 70
{"incremental":[{"items":["d"],"path":["alphabet",3]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 70
{"incremental":[{"items":["e"],"path":["alphabet",4]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 70
{"incremental":[{"items":["f"],"path":["alphabet",5]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 70
{"incremental":[{"items":["g"],"path":["alphabet",6]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 70
{"incremental":[{"items":["h"],"path":["alphabet",7]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 70
{"incremental":[{"items":["i"],"path":["alphabet",8]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 70
{"incremental":[{"items":["j"],"path":["alphabet",9]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["k"],"path":["alphabet",10]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["l"],"path":["alphabet",11]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["m"],"path":["alphabet",12]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["n"],"path":["alphabet",13]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["o"],"path":["alphabet",14]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["p"],"path":["alphabet",15]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["q"],"path":["alphabet",16]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["r"],"path":["alphabet",17]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["s"],"path":["alphabet",18]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["t"],"path":["alphabet",19]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["u"],"path":["alphabet",20]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["v"],"path":["alphabet",21]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["w"],"path":["alphabet",22]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["x"],"path":["alphabet",23]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["y"],"path":["alphabet",24]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 71
{"incremental":[{"items":["z"],"path":["alphabet",25]}],"hasNext":true}
---
Content-Type: application/json; charset=utf-8
Content-Length: 17
{"hasNext":false}
---
-----
"
`;
59 changes: 59 additions & 0 deletions examples/defer-stream/__integration-tests__/defer-stream.spec.ts
@@ -0,0 +1,59 @@
import { yoga } from '../src/yoga'

describe('Defer / Stream', () => {
it('stream', async () => {
const start = Date.now()
const response = await yoga.fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'multipart/mixed',
},
body: JSON.stringify({
query: /* GraphQL */ `
query StreamAlphabet {
alphabet(waitFor: 100) @stream
}
`,
}),
})
expect(response.status).toEqual(200)
const contentType = response.headers.get('Content-Type')
expect(contentType).toEqual('multipart/mixed; boundary="-"')
const responseText = await response.text()
const end = Date.now()
expect(responseText).toMatchSnapshot('stream')
const diff = end - start
expect(diff).toBeLessThan(2650)
expect(diff > 2550).toBeTruthy()
})
it('defer', async () => {
const start = Date.now()
const response = await yoga.fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'multipart/mixed',
},
body: JSON.stringify({
query: /* GraphQL */ `
query SlowAndFastFieldWithDefer {
... on Query @defer {
slowField(waitFor: 1500)
}
fastField
}
`,
}),
})
expect(response.status).toEqual(200)
const contentType = response.headers.get('Content-Type')
expect(contentType).toEqual('multipart/mixed; boundary="-"')
const responseText = await response.text()
const end = Date.now()
expect(responseText).toMatchSnapshot('defer')
const diff = end - start
expect(diff).toBeLessThan(1600)
expect(diff > 1450).toBeTruthy()
})
})
2 changes: 1 addition & 1 deletion examples/defer-stream/package.json
Expand Up @@ -8,7 +8,7 @@
},
"dependencies": {
"graphql-yoga": "3.0.0-next.8",
"graphql": "16.0.0-experimental-stream-defer.5"
"graphql": "16.6.0"
},
"devDependencies": {
"ts-node": "10.8.1",
Expand Down
132 changes: 1 addition & 131 deletions examples/defer-stream/src/index.ts
@@ -1,136 +1,6 @@
import { createSchema, createYoga } from 'graphql-yoga'
import { yoga } from './yoga.js'
import { createServer } from 'http'

const wait = (time: number) =>
new Promise((resolve) => setTimeout(resolve, time))

const typeDefs = /* GraphQL */ `
type Query {
"""
Resolves the alphabet slowly. 1 character per second
Maybe you want to @stream this field ;)
"""
alphabet: [String]
"""
A field that resolves fast.
"""
fastField: String!
"""
A field that resolves a bit slow.
Maybe you want to @defer this field ;)
"""
mediumSlowField: String!
"""
A field that resolves slowly.
Maybe you want to @defer this field ;)
"""
slowField: String!
}
`

const alphabet = [
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
]

const resolvers = {
Query: {
async *alphabet() {
for (const character of alphabet) {
yield character
await wait(1000)
}
},
fastField: async () => {
await wait(100)
return 'I am speed'
},
mediumSlowField: async () => {
await wait(2000)
return 'Just a bit late, right?'
},
slowField: async () => {
await wait(5000)
return 'I am slow'
},
},
}

const yoga = createYoga({
schema: createSchema({
typeDefs,
resolvers,
}),
graphiql: {
defaultQuery: /* GraphQL */ `
# Slow alphabet
query Alphabet {
alphabet
}
# Stream Alphabet
query StreamAlphabet {
alphabet @stream
}
# Quick field
query QuickFieldOnly {
fastField
}
# Slow field
query SlowFieldOnly {
slowField
}
# Slow and Fast field
query SlowAndFastField {
slowField
fastField
}
# Slow and Fast using defer
query SlowAndFastFieldWithDefer {
... on Query @defer {
slowField
}
fastField
}
`
.split('\n')
.map((line) => line.replace(' ', ''))
.join('\n'),
},
})

const server = createServer(yoga)
server.listen(4000, () => {
console.info('Server is running on http://localhost:4000/graphql')
Expand Down

0 comments on commit 87a6c33

Please sign in to comment.