Skip to content

Commit c518fac

Browse files
committed
Live Queries with @live
1 parent 4630dbd commit c518fac

File tree

13 files changed

+448
-232
lines changed

13 files changed

+448
-232
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@graphprotocol/client-cli": patch
3+
---
4+
5+
### Dependencies Updates
6+
7+
- Updated dependency ([`@graphql-mesh/[email protected]` ↗︎](https://www.npmjs.com/package/@graphql-mesh/cli/v/0.75.7)) (was `0.75.6`, in `dependencies`)

.changeset/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"react-query-example",
1616
"nextjs-example",
1717
"cross-chain-sdk",
18-
"cross-chain-extension"
18+
"cross-chain-extension",
19+
"live-queries-example"
1920
],
2021
"access": "public",
2122
"baseBranch": "main",

.changeset/metal-books-live.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@graphprotocol/client-polling-live': major
3+
'@graphprotocol/client-cli': patch
4+
---
5+
6+
New Polling-based Live Queries plugin

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ examples/**/node_modules
1717

1818
*.tsbuildinfo
1919
coverage/
20-
.bob
20+
.bob
21+
.env
22+
.graphclient
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
sources:
2+
- name: Sushi
3+
handler:
4+
graphql:
5+
endpoint: https://api.thegraph.com/subgraphs/name/sushiswap/exchange
6+
7+
plugins:
8+
- pollingLive:
9+
defaultInterval: 1000
10+
11+
documents:
12+
- ./example-query.graphql
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
query ExampleQuery @live {
2+
transactions(first: 2, orderBy: timestamp, orderDirection: desc) {
3+
id
4+
blockNumber
5+
timestamp
6+
}
7+
}

examples/live-queries/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "live-queries-example",
3+
"private": true,
4+
"version": "0.0.0",
5+
"scripts": {
6+
"build-client": "graphclient build",
7+
"check": "tsc --pretty --noEmit",
8+
"start": "graphclient serve-dev"
9+
},
10+
"dependencies": {
11+
"@graphprotocol/client-cli": "2.1.1",
12+
"@graphprotocol/client-polling-live": "0.0.0",
13+
"graphql": "16.5.0"
14+
}
15+
}

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"access": "public"
5050
},
5151
"dependencies": {
52-
"@graphql-mesh/cli": "0.75.6",
52+
"@graphql-mesh/cli": "0.75.7",
5353
"@graphql-mesh/graphql": "0.29.7",
5454
"tslib": "2.4.0",
5555
"@graphprotocol/client-auto-pagination": "1.1.0",

packages/polling-live/package.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "@graphprotocol/client-polling-live",
3+
"version": "0.0.0",
4+
"description": "",
5+
"repository": {
6+
"type": "git",
7+
"url": "https://github.com/graphprotocol/graph-client.git",
8+
"directory": "packages/polling-live"
9+
},
10+
"scripts": {
11+
"prepack": "bob prepack",
12+
"check": "tsc --pretty --noEmit"
13+
},
14+
"keywords": [
15+
"thegraph",
16+
"graphql",
17+
"client"
18+
],
19+
"license": "MIT",
20+
"sideEffects": false,
21+
"main": "dist/cjs/index.js",
22+
"module": "dist/esm/index.js",
23+
"typings": "dist/typings/index.d.ts",
24+
"typescript": {
25+
"definition": "dist/typings/index.d.ts"
26+
},
27+
"exports": {
28+
".": {
29+
"require": {
30+
"types": "./dist/typings/index.d.cts",
31+
"default": "./dist/cjs/index.js"
32+
},
33+
"import": {
34+
"types": "./dist/typings/index.d.ts",
35+
"default": "./dist/esm/index.js"
36+
},
37+
"default": {
38+
"types": "./dist/typings/index.d.ts",
39+
"default": "./dist/esm/index.js"
40+
}
41+
},
42+
"./package.json": "./package.json"
43+
},
44+
"publishConfig": {
45+
"directory": "dist",
46+
"access": "public"
47+
},
48+
"dependencies": {
49+
"@repeaterjs/repeater": "^3.0.4",
50+
"tslib": "2.4.0"
51+
},
52+
"peerDependencies": {
53+
"graphql": "^15.2.0 || ^16.0.0",
54+
"@graphql-tools/merge": "^8.3.1",
55+
"@envelop/core": "^2.4.2"
56+
},
57+
"type": "module"
58+
}

packages/polling-live/src/index.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { isAsyncIterable, Plugin } from '@envelop/core'
2+
import { DirectiveNode, GraphQLError, Kind, visit } from 'graphql'
3+
import { Repeater } from '@repeaterjs/repeater'
4+
import { mergeSchemas } from '@graphql-tools/schema'
5+
6+
export default function usePollingLive({ config: { defaultInterval = 1000 } = {} } = {}): Plugin<{}> {
7+
return {
8+
onSchemaChange({ schema, replaceSchema }) {
9+
if (!schema.getDirective('live')) {
10+
replaceSchema(
11+
mergeSchemas({
12+
schemas: [schema],
13+
typeDefs: /* GraphQL */ `
14+
directive @live(interval: Int) on QUERY
15+
`,
16+
}),
17+
)
18+
}
19+
},
20+
onExecute({ args, executeFn, setExecuteFn }) {
21+
let liveDirectiveNode: DirectiveNode | undefined
22+
args.document = visit(args.document, {
23+
OperationDefinition(node) {
24+
if (args.operationName != null && node.name?.value !== args.operationName) {
25+
return
26+
}
27+
const directives: DirectiveNode[] = []
28+
if (node.directives && node.operation === 'query') {
29+
for (const directive of node.directives) {
30+
if (directive.name.value === 'live') {
31+
liveDirectiveNode = directive
32+
} else {
33+
directives.push(directive)
34+
}
35+
}
36+
return {
37+
...node,
38+
directives,
39+
}
40+
}
41+
return node
42+
},
43+
})
44+
if (liveDirectiveNode) {
45+
const intervalArgNode = liveDirectiveNode.arguments?.find((argNode) => argNode.name.value === 'interval')
46+
let intervalMs = defaultInterval
47+
if (intervalArgNode?.value?.kind === Kind.INT) {
48+
intervalMs = parseInt(intervalArgNode.value.value)
49+
}
50+
51+
setExecuteFn(
52+
(args) =>
53+
new Repeater((push, stop) => {
54+
let finished = false
55+
async function pump() {
56+
if (finished) {
57+
return
58+
}
59+
const result: any = await executeFn(args)
60+
if (isAsyncIterable(result)) {
61+
push({
62+
data: null,
63+
errors: [new GraphQLError('Execution returned AsyncIterable which is not supported!')],
64+
isLive: true,
65+
})
66+
stop()
67+
return
68+
}
69+
result.isLive = true
70+
if (finished) {
71+
return
72+
}
73+
push(result)
74+
setTimeout(pump, intervalMs)
75+
}
76+
pump()
77+
stop.then(() => {
78+
finished = true
79+
})
80+
}) as any,
81+
)
82+
}
83+
},
84+
}
85+
}

0 commit comments

Comments
 (0)