@graffy/server 0.18.1-alpha.2 → 0.19.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,8 +12,6 @@
12
12
  * from the client-supplied options before use. Defaults to `[]` (strip all).
13
13
  * @returns
14
14
  */
15
- export default function server(store: GraffyStore, { auth, allowedOptions }?: {
16
- auth?: (operation: string, payload: any, options: any) => Promise<boolean>;
17
- allowedOptions?: string[];
15
+ export default function server(store: any, { auth, allowedOptions }?: {
16
+ allowedOptions?: any[];
18
17
  }): (req: any, res: any) => Promise<void>;
19
- export type GraffyStore = import("@graffy/core").default;
package/httpServer.js ADDED
@@ -0,0 +1,131 @@
1
+ import url from 'node:url';
2
+ import { decodeGraph, decodeQuery, pack, unpack } from '@graffy/common';
3
+ import debug from 'debug';
4
+ const log = debug('graffy:server:http');
5
+ /**
6
+ * @typedef {import('@graffy/core').default} GraffyStore
7
+ * @param {GraffyStore} store
8
+ * @param {object} [options]
9
+ * @param {(operation: string, payload: any, options: any) => Promise<boolean>} [options.auth]
10
+ * Optional callback to authorize each request. Receives the operation name,
11
+ * decoded payload, and the filtered options. Return `true` to allow, `false`
12
+ * (or a rejected promise) to reject with 401.
13
+ * @param {string[]} [options.allowedOptions]
14
+ * Allowlist of option keys that clients are permitted to pass through to
15
+ * `store.call` and the `auth` callback. Any key not in this list is stripped
16
+ * from the client-supplied options before use. Defaults to `[]` (strip all).
17
+ * @returns
18
+ */
19
+ export default function server(store, { auth, allowedOptions = [] } = {}) {
20
+ if (!store)
21
+ throw new Error('server.store_undef');
22
+ return async (req, res) => {
23
+ const parsed = url.parse(req.url, true);
24
+ const optParam = parsed.query.opts && String(parsed.query.opts);
25
+ const rawOptions = optParam && JSON.parse(decodeURIComponent(optParam));
26
+ const safeOptions = Object.fromEntries(Object.entries(rawOptions || {}).filter(([k]) => allowedOptions.includes(k)));
27
+ if (req.method === 'GET') {
28
+ try {
29
+ const qParam = parsed.query.q && String(parsed.query.q);
30
+ const query = qParam && unpack(JSON.parse(decodeURIComponent(qParam)));
31
+ if (req.headers.accept === 'text/event-stream') {
32
+ if (auth && !(await auth('watch', decodeQuery(query), safeOptions))) {
33
+ const body = 'unauthorized';
34
+ res.writeHead(401, {
35
+ 'Content-Type': 'text/plain',
36
+ 'Content-Length': Buffer.byteLength(body),
37
+ });
38
+ res.end(body);
39
+ return;
40
+ }
41
+ res.setHeader('content-type', 'text/event-stream');
42
+ const keepAlive = setInterval(() => {
43
+ if (req.aborted || res.finished) {
44
+ clearInterval(keepAlive);
45
+ return;
46
+ }
47
+ res.write(': \n\n');
48
+ }, 29000);
49
+ // TODO: Resumable subscriptions using timestamp ID.
50
+ // const lastId = req.headers['last-event-id'];
51
+ try {
52
+ const stream = store.call('watch', query, {
53
+ ...safeOptions,
54
+ raw: true,
55
+ });
56
+ for await (const value of stream) {
57
+ if (req.aborted || res.finished)
58
+ break;
59
+ res.write(`data: ${JSON.stringify(pack(value))}\n\n`);
60
+ }
61
+ }
62
+ catch (e) {
63
+ log(e);
64
+ res.write(`event: graffyerror\ndata: ${e.message}\n\n`);
65
+ }
66
+ res.end();
67
+ }
68
+ else {
69
+ throw Error('httpServer.get_unsupported');
70
+ }
71
+ }
72
+ catch (e) {
73
+ log(e.message);
74
+ log(e.stack);
75
+ const body = `${e.message}`;
76
+ res.writeHead(400, {
77
+ 'Content-Type': 'text/plain',
78
+ 'Content-Length': Buffer.byteLength(body),
79
+ });
80
+ res.end(body);
81
+ }
82
+ }
83
+ else if (req.method === 'POST') {
84
+ try {
85
+ const op = parsed.query.op;
86
+ if (op !== 'write' && op !== 'read') {
87
+ throw Error('httpServer.unsupported_op');
88
+ }
89
+ const chunks = [];
90
+ for await (const chunk of req)
91
+ chunks.push(chunk);
92
+ const payload = unpack(JSON.parse(Buffer.concat(chunks).toString()));
93
+ if (auth &&
94
+ !(await auth(op, (op === 'write' ? decodeGraph : decodeQuery)(payload), safeOptions))) {
95
+ const body = 'unauthorized';
96
+ res.writeHead(401, {
97
+ 'Content-Type': 'text/plain',
98
+ 'Content-Length': Buffer.byteLength(body),
99
+ });
100
+ res.end(body);
101
+ return;
102
+ }
103
+ const value = await store.call(op, payload, safeOptions);
104
+ const body = JSON.stringify(pack(value));
105
+ res.writeHead(200, {
106
+ 'Content-Type': 'application/json',
107
+ 'Content-Length': Buffer.byteLength(body),
108
+ });
109
+ res.end(body);
110
+ }
111
+ catch (e) {
112
+ log(e.message);
113
+ log(e.stack);
114
+ const body = `${e.message}`;
115
+ res.writeHead(400, {
116
+ 'Content-Type': 'text/plain',
117
+ 'Content-Length': Buffer.byteLength(body),
118
+ });
119
+ res.end(body);
120
+ }
121
+ }
122
+ else {
123
+ const body = 'Not implemented';
124
+ res.writeHead(501, {
125
+ 'Content-Type': 'text/plain',
126
+ 'Content-Length': Buffer.byteLength(body),
127
+ });
128
+ res.end(body);
129
+ }
130
+ };
131
+ }
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { default as httpServer } from './httpServer.js';
2
+ export { default as wsServer } from './wsServer.js';
package/package.json CHANGED
@@ -2,21 +2,26 @@
2
2
  "name": "@graffy/server",
3
3
  "description": "Node.js library for building an API for a Graffy store.",
4
4
  "author": "aravind (https://github.com/aravindet)",
5
- "version": "0.18.1-alpha.2",
6
- "main": "./index.cjs",
5
+ "version": "0.19.1-alpha.1",
6
+ "main": "./cjs/index.js",
7
7
  "exports": {
8
- "import": "./index.mjs",
9
- "require": "./index.cjs"
8
+ ".": {
9
+ "import": "./index.js",
10
+ "types": "./index.d.ts"
11
+ },
12
+ "./*": {
13
+ "import": "./*.js",
14
+ "types": "./*.d.ts"
15
+ }
10
16
  },
11
- "module": "./index.mjs",
12
- "types": "./types/index.d.ts",
17
+ "types": "./index.d.ts",
13
18
  "repository": {
14
19
  "type": "git",
15
20
  "url": "git+https://github.com/usegraffy/graffy.git"
16
21
  },
17
22
  "license": "Apache-2.0",
18
23
  "dependencies": {
19
- "@graffy/common": "0.18.1-alpha.2",
24
+ "@graffy/common": "0.19.1-alpha.1",
20
25
  "debug": "^4.4.3",
21
26
  "ws": "^8.19.0"
22
27
  }
@@ -12,8 +12,6 @@
12
12
  * from the client-supplied options before use. Defaults to `[]` (strip all).
13
13
  * @returns
14
14
  */
15
- export default function server(store: GraffyStore, { auth, allowedOptions }?: {
16
- auth?: (operation: string, payload: any, options: any) => Promise<boolean>;
17
- allowedOptions?: string[];
15
+ export default function server(store: any, { auth, allowedOptions }?: {
16
+ allowedOptions?: any[];
18
17
  }): (request: any, socket: any, head: any) => Promise<void>;
19
- export type GraffyStore = import("@graffy/core").default;
package/wsServer.js ADDED
@@ -0,0 +1,105 @@
1
+ import { decodeGraph, decodeQuery, pack, unpack } from '@graffy/common';
2
+ import debug from 'debug';
3
+ import { WebSocketServer } from 'ws';
4
+ const log = debug('graffy:server:ws');
5
+ const PING_INTERVAL = 30000;
6
+ /**
7
+ * @typedef {import('@graffy/core').default} GraffyStore
8
+ * @param {GraffyStore} store
9
+ * @param {object} [options]
10
+ * @param {(operation: string, payload: any, options: any) => Promise<boolean>} [options.auth]
11
+ * Optional callback to authorize each request. Receives the operation name,
12
+ * decoded payload, and the filtered options. Return `true` to allow, `false`
13
+ * (or a rejected promise) to reject with an error response.
14
+ * @param {string[]} [options.allowedOptions]
15
+ * Allowlist of option keys that clients are permitted to pass through to
16
+ * `store.call` and the `auth` callback. Any key not in this list is stripped
17
+ * from the client-supplied options before use. Defaults to `[]` (strip all).
18
+ * @returns
19
+ */
20
+ export default function server(store, { auth, allowedOptions = [] } = {}) {
21
+ if (!store)
22
+ throw new Error('server.store_undef');
23
+ const wss = new WebSocketServer({ noServer: true });
24
+ wss.on('connection', function connection(ws) {
25
+ ws.graffyStreams = {}; // We use this to keep track of streams to close.
26
+ ws.on('message', async function message(msg) {
27
+ try {
28
+ const [id, op, packedPayload, rawOptions] = JSON.parse(msg);
29
+ const safeOptions = Object.fromEntries(Object.entries(rawOptions || {}).filter(([k]) => allowedOptions.includes(k)));
30
+ const payload = unpack(packedPayload);
31
+ if (id === ':pong') {
32
+ ws.pingPending = false;
33
+ return;
34
+ }
35
+ if (auth && op !== 'unwatch') {
36
+ const decoded = op === 'write' ? decodeGraph(payload) : decodeQuery(payload);
37
+ if (!(await auth(op, decoded, safeOptions))) {
38
+ ws.send(JSON.stringify([id, 'unauthorized']));
39
+ return;
40
+ }
41
+ }
42
+ switch (op) {
43
+ case 'read':
44
+ case 'write':
45
+ try {
46
+ const result = await store.call(op, payload, safeOptions);
47
+ ws.send(JSON.stringify([id, null, pack(result)]));
48
+ }
49
+ catch (e) {
50
+ log(`${op}error:${e.message} ${payload}`);
51
+ ws.send(JSON.stringify([id, e.message]));
52
+ }
53
+ break;
54
+ case 'watch':
55
+ try {
56
+ const stream = store.call('watch', payload, {
57
+ ...safeOptions,
58
+ raw: true,
59
+ });
60
+ ws.graffyStreams[id] = stream;
61
+ for await (const value of stream) {
62
+ ws.send(JSON.stringify([id, null, pack(value)]));
63
+ }
64
+ }
65
+ catch (e) {
66
+ log(`${op}error:${e.message} ${payload}`);
67
+ ws.send(JSON.stringify([id, e.message]));
68
+ }
69
+ break;
70
+ case 'unwatch':
71
+ if (!ws.graffyStreams[id])
72
+ break;
73
+ ws.graffyStreams[id].return();
74
+ delete ws.graffyStreams[id];
75
+ break;
76
+ }
77
+ }
78
+ catch (e) {
79
+ log(`Closing socket due to error: ${e.message}`);
80
+ ws.close();
81
+ }
82
+ });
83
+ ws.on('close', () => {
84
+ for (const id in ws.graffyStreams) {
85
+ ws.graffyStreams[id].return();
86
+ delete ws.graffyStreams[id];
87
+ }
88
+ });
89
+ });
90
+ setInterval(function ping() {
91
+ wss.clients.forEach(function each(ws) {
92
+ if (ws.pingPending) {
93
+ ws.terminate();
94
+ return;
95
+ }
96
+ ws.pingPending = true;
97
+ ws.send(JSON.stringify([':ping', Date.now()]));
98
+ });
99
+ }, PING_INTERVAL);
100
+ return async (request, socket, head) => {
101
+ wss.handleUpgrade(request, socket, head, function done(ws) {
102
+ wss.emit('connection', ws, request);
103
+ });
104
+ };
105
+ }
package/index.cjs DELETED
@@ -1,210 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const url = require("node:url");
4
- const common = require("@graffy/common");
5
- const debug = require("debug");
6
- const ws = require("ws");
7
- const log$1 = debug("graffy:server:http");
8
- function server$1(store, { auth, allowedOptions = [] } = {}) {
9
- if (!store) throw new Error("server.store_undef");
10
- return async (req, res) => {
11
- const parsed = url.parse(req.url, true);
12
- const optParam = parsed.query.opts && String(parsed.query.opts);
13
- const rawOptions = optParam && JSON.parse(decodeURIComponent(optParam));
14
- const safeOptions = Object.fromEntries(
15
- Object.entries(rawOptions || {}).filter(
16
- ([k]) => allowedOptions.includes(k)
17
- )
18
- );
19
- if (req.method === "GET") {
20
- try {
21
- const qParam = parsed.query.q && String(parsed.query.q);
22
- const query = qParam && common.unpack(JSON.parse(decodeURIComponent(qParam)));
23
- if (req.headers.accept === "text/event-stream") {
24
- if (auth && !await auth("watch", common.decodeQuery(query), safeOptions)) {
25
- const body = "unauthorized";
26
- res.writeHead(401, {
27
- "Content-Type": "text/plain",
28
- "Content-Length": Buffer.byteLength(body)
29
- });
30
- res.end(body);
31
- return;
32
- }
33
- res.setHeader("content-type", "text/event-stream");
34
- const keepAlive = setInterval(() => {
35
- if (req.aborted || res.finished) {
36
- clearInterval(keepAlive);
37
- return;
38
- }
39
- res.write(": \n\n");
40
- }, 29e3);
41
- try {
42
- const stream = store.call("watch", query, {
43
- ...safeOptions,
44
- raw: true
45
- });
46
- for await (const value of stream) {
47
- if (req.aborted || res.finished) break;
48
- res.write(`data: ${JSON.stringify(common.pack(value))}
49
-
50
- `);
51
- }
52
- } catch (e) {
53
- log$1(e);
54
- res.write(`event: graffyerror
55
- data: ${e.message}
56
-
57
- `);
58
- }
59
- res.end();
60
- } else {
61
- throw Error("httpServer.get_unsupported");
62
- }
63
- } catch (e) {
64
- log$1(e.message);
65
- log$1(e.stack);
66
- const body = `${e.message}`;
67
- res.writeHead(400, {
68
- "Content-Type": "text/plain",
69
- "Content-Length": Buffer.byteLength(body)
70
- });
71
- res.end(body);
72
- }
73
- } else if (req.method === "POST") {
74
- try {
75
- const op = parsed.query.op;
76
- if (op !== "write" && op !== "read") {
77
- throw Error("httpServer.unsupported_op");
78
- }
79
- const chunks = [];
80
- for await (const chunk of req) chunks.push(chunk);
81
- const payload = common.unpack(JSON.parse(Buffer.concat(chunks).toString()));
82
- if (auth && !await auth(
83
- op,
84
- (op === "write" ? common.decodeGraph : common.decodeQuery)(payload),
85
- safeOptions
86
- )) {
87
- const body2 = "unauthorized";
88
- res.writeHead(401, {
89
- "Content-Type": "text/plain",
90
- "Content-Length": Buffer.byteLength(body2)
91
- });
92
- res.end(body2);
93
- return;
94
- }
95
- const value = await store.call(op, payload, safeOptions);
96
- const body = JSON.stringify(common.pack(value));
97
- res.writeHead(200, {
98
- "Content-Type": "application/json",
99
- "Content-Length": Buffer.byteLength(body)
100
- });
101
- res.end(body);
102
- } catch (e) {
103
- log$1(e.message);
104
- log$1(e.stack);
105
- const body = `${e.message}`;
106
- res.writeHead(400, {
107
- "Content-Type": "text/plain",
108
- "Content-Length": Buffer.byteLength(body)
109
- });
110
- res.end(body);
111
- }
112
- } else {
113
- const body = "Not implemented";
114
- res.writeHead(501, {
115
- "Content-Type": "text/plain",
116
- "Content-Length": Buffer.byteLength(body)
117
- });
118
- res.end(body);
119
- }
120
- };
121
- }
122
- const log = debug("graffy:server:ws");
123
- const PING_INTERVAL = 3e4;
124
- function server(store, { auth, allowedOptions = [] } = {}) {
125
- if (!store) throw new Error("server.store_undef");
126
- const wss = new ws.WebSocketServer({ noServer: true });
127
- wss.on("connection", function connection(ws2) {
128
- ws2.graffyStreams = {};
129
- ws2.on("message", async function message(msg) {
130
- try {
131
- const [id, op, packedPayload, rawOptions] = JSON.parse(msg);
132
- const safeOptions = Object.fromEntries(
133
- Object.entries(rawOptions || {}).filter(
134
- ([k]) => allowedOptions.includes(k)
135
- )
136
- );
137
- const payload = common.unpack(packedPayload);
138
- if (id === ":pong") {
139
- ws2.pingPending = false;
140
- return;
141
- }
142
- if (auth && op !== "unwatch") {
143
- const decoded = op === "write" ? common.decodeGraph(payload) : common.decodeQuery(payload);
144
- if (!await auth(op, decoded, safeOptions)) {
145
- ws2.send(JSON.stringify([id, "unauthorized"]));
146
- return;
147
- }
148
- }
149
- switch (op) {
150
- case "read":
151
- case "write":
152
- try {
153
- const result = await store.call(op, payload, safeOptions);
154
- ws2.send(JSON.stringify([id, null, common.pack(result)]));
155
- } catch (e) {
156
- log(`${op}error:${e.message} ${payload}`);
157
- ws2.send(JSON.stringify([id, e.message]));
158
- }
159
- break;
160
- case "watch":
161
- try {
162
- const stream = store.call("watch", payload, {
163
- ...safeOptions,
164
- raw: true
165
- });
166
- ws2.graffyStreams[id] = stream;
167
- for await (const value of stream) {
168
- ws2.send(JSON.stringify([id, null, common.pack(value)]));
169
- }
170
- } catch (e) {
171
- log(`${op}error:${e.message} ${payload}`);
172
- ws2.send(JSON.stringify([id, e.message]));
173
- }
174
- break;
175
- case "unwatch":
176
- if (!ws2.graffyStreams[id]) break;
177
- ws2.graffyStreams[id].return();
178
- delete ws2.graffyStreams[id];
179
- break;
180
- }
181
- } catch (e) {
182
- log(`Closing socket due to error: ${e.message}`);
183
- ws2.close();
184
- }
185
- });
186
- ws2.on("close", () => {
187
- for (const id in ws2.graffyStreams) {
188
- ws2.graffyStreams[id].return();
189
- delete ws2.graffyStreams[id];
190
- }
191
- });
192
- });
193
- setInterval(function ping() {
194
- wss.clients.forEach(function each(ws2) {
195
- if (ws2.pingPending) {
196
- ws2.terminate();
197
- return;
198
- }
199
- ws2.pingPending = true;
200
- ws2.send(JSON.stringify([":ping", Date.now()]));
201
- });
202
- }, PING_INTERVAL);
203
- return async (request, socket, head) => {
204
- wss.handleUpgrade(request, socket, head, function done(ws2) {
205
- wss.emit("connection", ws2, request);
206
- });
207
- };
208
- }
209
- exports.httpServer = server$1;
210
- exports.wsServer = server;
package/index.mjs DELETED
@@ -1,210 +0,0 @@
1
- import url from "node:url";
2
- import { unpack, decodeQuery, pack, decodeGraph } from "@graffy/common";
3
- import debug from "debug";
4
- import { WebSocketServer } from "ws";
5
- const log$1 = debug("graffy:server:http");
6
- function server$1(store, { auth, allowedOptions = [] } = {}) {
7
- if (!store) throw new Error("server.store_undef");
8
- return async (req, res) => {
9
- const parsed = url.parse(req.url, true);
10
- const optParam = parsed.query.opts && String(parsed.query.opts);
11
- const rawOptions = optParam && JSON.parse(decodeURIComponent(optParam));
12
- const safeOptions = Object.fromEntries(
13
- Object.entries(rawOptions || {}).filter(
14
- ([k]) => allowedOptions.includes(k)
15
- )
16
- );
17
- if (req.method === "GET") {
18
- try {
19
- const qParam = parsed.query.q && String(parsed.query.q);
20
- const query = qParam && unpack(JSON.parse(decodeURIComponent(qParam)));
21
- if (req.headers.accept === "text/event-stream") {
22
- if (auth && !await auth("watch", decodeQuery(query), safeOptions)) {
23
- const body = "unauthorized";
24
- res.writeHead(401, {
25
- "Content-Type": "text/plain",
26
- "Content-Length": Buffer.byteLength(body)
27
- });
28
- res.end(body);
29
- return;
30
- }
31
- res.setHeader("content-type", "text/event-stream");
32
- const keepAlive = setInterval(() => {
33
- if (req.aborted || res.finished) {
34
- clearInterval(keepAlive);
35
- return;
36
- }
37
- res.write(": \n\n");
38
- }, 29e3);
39
- try {
40
- const stream = store.call("watch", query, {
41
- ...safeOptions,
42
- raw: true
43
- });
44
- for await (const value of stream) {
45
- if (req.aborted || res.finished) break;
46
- res.write(`data: ${JSON.stringify(pack(value))}
47
-
48
- `);
49
- }
50
- } catch (e) {
51
- log$1(e);
52
- res.write(`event: graffyerror
53
- data: ${e.message}
54
-
55
- `);
56
- }
57
- res.end();
58
- } else {
59
- throw Error("httpServer.get_unsupported");
60
- }
61
- } catch (e) {
62
- log$1(e.message);
63
- log$1(e.stack);
64
- const body = `${e.message}`;
65
- res.writeHead(400, {
66
- "Content-Type": "text/plain",
67
- "Content-Length": Buffer.byteLength(body)
68
- });
69
- res.end(body);
70
- }
71
- } else if (req.method === "POST") {
72
- try {
73
- const op = parsed.query.op;
74
- if (op !== "write" && op !== "read") {
75
- throw Error("httpServer.unsupported_op");
76
- }
77
- const chunks = [];
78
- for await (const chunk of req) chunks.push(chunk);
79
- const payload = unpack(JSON.parse(Buffer.concat(chunks).toString()));
80
- if (auth && !await auth(
81
- op,
82
- (op === "write" ? decodeGraph : decodeQuery)(payload),
83
- safeOptions
84
- )) {
85
- const body2 = "unauthorized";
86
- res.writeHead(401, {
87
- "Content-Type": "text/plain",
88
- "Content-Length": Buffer.byteLength(body2)
89
- });
90
- res.end(body2);
91
- return;
92
- }
93
- const value = await store.call(op, payload, safeOptions);
94
- const body = JSON.stringify(pack(value));
95
- res.writeHead(200, {
96
- "Content-Type": "application/json",
97
- "Content-Length": Buffer.byteLength(body)
98
- });
99
- res.end(body);
100
- } catch (e) {
101
- log$1(e.message);
102
- log$1(e.stack);
103
- const body = `${e.message}`;
104
- res.writeHead(400, {
105
- "Content-Type": "text/plain",
106
- "Content-Length": Buffer.byteLength(body)
107
- });
108
- res.end(body);
109
- }
110
- } else {
111
- const body = "Not implemented";
112
- res.writeHead(501, {
113
- "Content-Type": "text/plain",
114
- "Content-Length": Buffer.byteLength(body)
115
- });
116
- res.end(body);
117
- }
118
- };
119
- }
120
- const log = debug("graffy:server:ws");
121
- const PING_INTERVAL = 3e4;
122
- function server(store, { auth, allowedOptions = [] } = {}) {
123
- if (!store) throw new Error("server.store_undef");
124
- const wss = new WebSocketServer({ noServer: true });
125
- wss.on("connection", function connection(ws) {
126
- ws.graffyStreams = {};
127
- ws.on("message", async function message(msg) {
128
- try {
129
- const [id, op, packedPayload, rawOptions] = JSON.parse(msg);
130
- const safeOptions = Object.fromEntries(
131
- Object.entries(rawOptions || {}).filter(
132
- ([k]) => allowedOptions.includes(k)
133
- )
134
- );
135
- const payload = unpack(packedPayload);
136
- if (id === ":pong") {
137
- ws.pingPending = false;
138
- return;
139
- }
140
- if (auth && op !== "unwatch") {
141
- const decoded = op === "write" ? decodeGraph(payload) : decodeQuery(payload);
142
- if (!await auth(op, decoded, safeOptions)) {
143
- ws.send(JSON.stringify([id, "unauthorized"]));
144
- return;
145
- }
146
- }
147
- switch (op) {
148
- case "read":
149
- case "write":
150
- try {
151
- const result = await store.call(op, payload, safeOptions);
152
- ws.send(JSON.stringify([id, null, pack(result)]));
153
- } catch (e) {
154
- log(`${op}error:${e.message} ${payload}`);
155
- ws.send(JSON.stringify([id, e.message]));
156
- }
157
- break;
158
- case "watch":
159
- try {
160
- const stream = store.call("watch", payload, {
161
- ...safeOptions,
162
- raw: true
163
- });
164
- ws.graffyStreams[id] = stream;
165
- for await (const value of stream) {
166
- ws.send(JSON.stringify([id, null, pack(value)]));
167
- }
168
- } catch (e) {
169
- log(`${op}error:${e.message} ${payload}`);
170
- ws.send(JSON.stringify([id, e.message]));
171
- }
172
- break;
173
- case "unwatch":
174
- if (!ws.graffyStreams[id]) break;
175
- ws.graffyStreams[id].return();
176
- delete ws.graffyStreams[id];
177
- break;
178
- }
179
- } catch (e) {
180
- log(`Closing socket due to error: ${e.message}`);
181
- ws.close();
182
- }
183
- });
184
- ws.on("close", () => {
185
- for (const id in ws.graffyStreams) {
186
- ws.graffyStreams[id].return();
187
- delete ws.graffyStreams[id];
188
- }
189
- });
190
- });
191
- setInterval(function ping() {
192
- wss.clients.forEach(function each(ws) {
193
- if (ws.pingPending) {
194
- ws.terminate();
195
- return;
196
- }
197
- ws.pingPending = true;
198
- ws.send(JSON.stringify([":ping", Date.now()]));
199
- });
200
- }, PING_INTERVAL);
201
- return async (request, socket, head) => {
202
- wss.handleUpgrade(request, socket, head, function done(ws) {
203
- wss.emit("connection", ws, request);
204
- });
205
- };
206
- }
207
- export {
208
- server$1 as httpServer,
209
- server as wsServer
210
- };
File without changes