diff --git a/README.md b/README.md index dfc6b99f..d7783411 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,34 @@ uWS }); ``` +##### With [fastify-websocket](https://github.com/fastify/fastify-websocket) + +```ts +import Fastify from 'fastify'; // yarn add fastify +import fastifyWebsocket from 'fastify-websocket'; // yarn add fastify-websocket +import { makeHandler } from 'graphql-ws/lib/use/fastify-websocket'; + +const fastify = Fastify(); +fastify.register(fastifyWebsocket); + +fastify.get( + '/graphql', + { websocket: true }, + makeHandler( + // from the previous step + { schema, roots }, + ), +); + +fastify.listen(4000, (err) => { + if (err) { + fastify.log.error(err); + return process.exit(1); + } + console.log('Listening to port 4000'); +}); +``` + #### Use the client ```ts diff --git a/docs/README.md b/docs/README.md index 2cf9bc84..58369382 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,5 +9,6 @@ graphql-ws - [client](modules/client.md) - [common](modules/common.md) - [server](modules/server.md) +- [use/fastify-websocket](modules/use_fastify_websocket.md) - [use/uWebSockets](modules/use_uwebsockets.md) - [use/ws](modules/use_ws.md) diff --git a/docs/interfaces/use_fastify_websocket.extra.md b/docs/interfaces/use_fastify_websocket.extra.md new file mode 100644 index 00000000..8ecf8f61 --- /dev/null +++ b/docs/interfaces/use_fastify_websocket.extra.md @@ -0,0 +1,32 @@ +[graphql-ws](../README.md) / [use/fastify-websocket](../modules/use_fastify_websocket.md) / Extra + +# Interface: Extra + +[use/fastify-websocket](../modules/use_fastify_websocket.md).Extra + +The extra that will be put in the `Context`. + +## Table of contents + +### Properties + +- [connection](use_fastify_websocket.extra.md#connection) +- [request](use_fastify_websocket.extra.md#request) + +## Properties + +### connection + +• `Readonly` **connection**: `SocketStream` + +The underlying socket connection between the server and the client. +The WebSocket socket is located under the `socket` parameter. + +___ + +### request + +• `Readonly` **request**: `FastifyRequest` + +The initial HTTP upgrade request before the actual +socket and connection is established. diff --git a/docs/modules/use_fastify_websocket.md b/docs/modules/use_fastify_websocket.md new file mode 100644 index 00000000..1516b114 --- /dev/null +++ b/docs/modules/use_fastify_websocket.md @@ -0,0 +1,39 @@ +[graphql-ws](../README.md) / use/fastify-websocket + +# Module: use/fastify-websocket + +## Table of contents + +### Interfaces + +- [Extra](../interfaces/use_fastify_websocket.extra.md) + +### Functions + +- [makeHandler](use_fastify_websocket.md#makehandler) + +## Server/fastify-websocket + +### makeHandler + +▸ **makeHandler**(`options`, `keepAlive?`): `fastifyWebsocket.WebsocketHandler` + +Make a handler to use on a [fastify-websocket](https://github.com/fastify/fastify-websocket) route. +This is a basic starter, feel free to copy the code over and adjust it to your needs + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `E` | `E`: `Record` = `Record` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `options` | [ServerOptions](../interfaces/server.serveroptions.md)<[Extra](../interfaces/use_fastify_websocket.extra.md) & `Partial`\> | +| `keepAlive` | `number` | + +#### Returns + +`fastifyWebsocket.WebsocketHandler` diff --git a/package.json b/package.json index db4586f0..31bdc38e 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,10 @@ "require": "./lib/use/uWebSockets.js", "import": "./lib/use/uWebSockets.mjs" }, + "./lib/use/fastify-websocket": { + "require": "./lib/use/fastify-websocket.js", + "import": "./lib/use/fastify-websocket.mjs" + }, "./package.json": "./package.json" }, "types": "lib/index.d.ts", @@ -90,6 +94,8 @@ "eslint": "^7.27.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^3.4.0", + "fastify": "^3.17.0", + "fastify-websocket": "^3.2.0", "glob": "^7.1.7", "graphql": "^15.5.0", "jest": "^27.0.4", diff --git a/src/tests/use.ts b/src/tests/use.ts index 61874751..d955e673 100644 --- a/src/tests/use.ts +++ b/src/tests/use.ts @@ -1,5 +1,6 @@ import http from 'http'; import ws from 'ws'; +import stream from 'stream'; import { MessageType, stringifyMessage, @@ -12,6 +13,7 @@ import { tServers, WSExtra, UWSExtra, + FastifyExtra, waitForDone, } from './utils'; @@ -108,6 +110,16 @@ for (const { tServer, startTServer } of tServers) { expect((ctx.extra as WSExtra).request).toBeInstanceOf( http.IncomingMessage, ); + } else if (tServer === 'fastify-websocket') { + expect((ctx.extra as FastifyExtra).connection).toBeInstanceOf( + stream.Duplex, + ); + expect( + (ctx.extra as FastifyExtra).connection.socket, + ).toBeInstanceOf(ws); + expect((ctx.extra as FastifyExtra).request.constructor.name).toBe( + 'Request', + ); } else { throw new Error('Missing test case for ' + tServer); } diff --git a/src/tests/utils/tservers.ts b/src/tests/utils/tservers.ts index 490ed4e7..31dfb665 100644 --- a/src/tests/utils/tservers.ts +++ b/src/tests/utils/tservers.ts @@ -5,13 +5,19 @@ import { ServerOptions, Context } from '../../server'; import ws from 'ws'; import uWS from 'uWebSockets.js'; +import Fastify from 'fastify'; +import fastifyWebsocket from 'fastify-websocket'; import { useServer as useWSServer, Extra as WSExtra } from '../../use/ws'; import { makeBehavior as makeUWSBehavior, Extra as UWSExtra, } from '../../use/uWebSockets'; -export { WSExtra, UWSExtra }; +import { + makeHandler as makeFastifyHandler, + Extra as FastifyExtra, +} from '../../use/fastify-websocket'; +export { WSExtra, UWSExtra, FastifyExtra }; // distinct server for each test; if you forget to dispose, the fixture wont const leftovers: Dispose[] = []; @@ -38,7 +44,7 @@ export interface TServer { expire?: number, ) => Promise; waitForConnect: ( - test?: (ctx: Context) => void, + test?: (ctx: Context) => void, expire?: number, ) => Promise; waitForOperation: (test?: () => void, expire?: number) => Promise; @@ -407,17 +413,217 @@ export async function startUWSTServer( }; } +export async function startFastifyWSTServer( + options: Partial = {}, + keepAlive?: number, // for ws tests sake +): Promise { + const path = '/simple'; + const emitter = new EventEmitter(); + const port = await getAvailablePort(); + + const fastify = Fastify(); + fastify.register(fastifyWebsocket); + + // sockets to kick off on teardown + const sockets = new Set(); + + const pendingConnections: Context[] = []; + const pendingClients: TServerClient[] = []; + let pendingOperations = 0, + pendingCompletes = 0, + pendingCloses = 0; + + function toClient(socket: ws): TServerClient { + return { + onMessage: (cb) => { + socket.on('message', cb); + return () => socket.off('message', cb); + }, + close: (...args) => socket.close(...args), + }; + } + + fastify.get(path, { websocket: true }, (connection, request) => { + sockets.add(connection.socket); + pendingClients.push(toClient(connection.socket)); + connection.socket.once('close', () => { + sockets.delete(connection.socket); + pendingCloses++; + emitter.emit('close'); + }); + + makeFastifyHandler( + { + schema, + ...options, + onConnect: async (...args) => { + pendingConnections.push(args[0]); + const permitted = await options?.onConnect?.(...args); + emitter.emit('conn'); + return permitted; + }, + onOperation: async (ctx, msg, args, result) => { + pendingOperations++; + const maybeResult = await options?.onOperation?.( + ctx, + msg, + args, + result, + ); + emitter.emit('operation'); + return maybeResult; + }, + onComplete: async (...args) => { + pendingCompletes++; + await options?.onComplete?.(...args); + emitter.emit('compl'); + }, + }, + keepAlive, + ).call(fastify, connection, request); + }); + + const dispose: Dispose = (beNice) => { + return new Promise((resolve, reject) => { + for (const socket of sockets) { + if (beNice) socket.close(1001, 'Going away'); + else socket.terminate(); + sockets.delete(socket); + } + + fastify.websocketServer.close((err) => { + if (err) return reject(err); + fastify.close(() => { + leftovers.splice(leftovers.indexOf(dispose), 1); + resolve(); + }); + }); + }); + }; + leftovers.push(dispose); + + await new Promise((resolve, reject) => { + fastify.listen(port, (err) => { + if (err) return reject(err); + resolve(); + }); + }); + + return { + url: `ws://localhost:${port}${path}`, + getClients() { + return Array.from(fastify.websocketServer.clients, toClient); + }, + waitForClient(test, expire) { + return new Promise((resolve) => { + function done() { + // the on connect listener below will be called before our listener, populating the queue + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const client = pendingClients.shift()!; + test?.(client); + resolve(); + } + if (pendingClients.length > 0) return done(); + fastify.websocketServer.once('connection', done); + if (expire) + setTimeout(() => { + fastify.websocketServer.off('connection', done); // expired + resolve(); + }, expire); + }); + }, + waitForClientClose(test, expire) { + return new Promise((resolve) => { + function done() { + pendingCloses--; + test?.(); + resolve(); + } + if (pendingCloses > 0) return done(); + + emitter.once('close', done); + if (expire) + setTimeout(() => { + emitter.off('close', done); // expired + resolve(); + }, expire); + }); + }, + pong, + waitForConnect(test, expire) { + return new Promise((resolve) => { + function done() { + // the on connect listener below will be called before our listener, populating the queue + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const ctx = pendingConnections.shift()!; + test?.(ctx); + resolve(); + } + if (pendingConnections.length > 0) return done(); + emitter.once('conn', done); + if (expire) + setTimeout(() => { + emitter.off('conn', done); // expired + resolve(); + }, expire); + }); + }, + waitForOperation(test, expire) { + return new Promise((resolve) => { + function done() { + pendingOperations--; + test?.(); + resolve(); + } + if (pendingOperations > 0) return done(); + emitter.once('operation', done); + if (expire) + setTimeout(() => { + emitter.off('operation', done); // expired + resolve(); + }, expire); + }); + }, + waitForComplete(test, expire) { + return new Promise((resolve) => { + function done() { + pendingCompletes--; + test?.(); + resolve(); + } + if (pendingCompletes > 0) return done(); + emitter.once('compl', done); + if (expire) + setTimeout(() => { + emitter.off('compl', done); // expired + resolve(); + }, expire); + }); + }, + dispose, + }; +} + export const tServers = [ { tServer: 'ws' as const, startTServer: startWSTServer, itForWS: it, itForUWS: it.skip, + itForFastify: it.skip, }, { tServer: 'uWebSockets.js' as const, startTServer: startUWSTServer, itForWS: it.skip, itForUWS: it, + itForFastify: it.skip, + }, + { + tServer: 'fastify-websocket' as const, + startTServer: startFastifyWSTServer, + itForWS: it.skip, + itForUWS: it.skip, + itForFastify: it, }, ]; diff --git a/src/use/fastify-websocket.ts b/src/use/fastify-websocket.ts new file mode 100644 index 00000000..c26d2af2 --- /dev/null +++ b/src/use/fastify-websocket.ts @@ -0,0 +1,111 @@ +import type { FastifyRequest } from 'fastify'; +import type * as fastifyWebsocket from 'fastify-websocket'; +import { makeServer, ServerOptions } from '../server'; +import { GRAPHQL_TRANSPORT_WS_PROTOCOL } from '../common'; + +/** + * The extra that will be put in the `Context`. + * + * @category Server/fastify-websocket + */ +export interface Extra { + /** + * The underlying socket connection between the server and the client. + * The WebSocket socket is located under the `socket` parameter. + */ + readonly connection: fastifyWebsocket.SocketStream; + /** + * The initial HTTP upgrade request before the actual + * socket and connection is established. + */ + readonly request: FastifyRequest; +} + +/** + * Make a handler to use on a [fastify-websocket](https://github.com/fastify/fastify-websocket) route. + * This is a basic starter, feel free to copy the code over and adjust it to your needs + * + * @category Server/fastify-websocket + */ +export function makeHandler< + E extends Record = Record, +>( + options: ServerOptions>, + /** + * The timout between dispatched keep-alive messages. Internally uses the [ws Ping and Pongs]((https://developer.mozilla.org/en-US/docs/Web/API/wss_API/Writing_ws_servers#Pings_and_Pongs_The_Heartbeat_of_wss)) + * to check that the link between the clients and the server is operating and to prevent the link + * from being broken due to idling. + * + * @default 12 * 1000 // 12 seconds + */ + keepAlive = 12 * 1000, +): fastifyWebsocket.WebsocketHandler { + const isProd = process.env.NODE_ENV === 'production'; + const server = makeServer(options); + + return (connection, request) => { + const { socket } = connection; + + socket.on('error', (err) => + socket.close(1011, isProd ? 'Internal Error' : err.message), + ); + + // keep alive through ping-pong messages + let pongWait: NodeJS.Timeout | null = null; + const pingInterval = + keepAlive > 0 && isFinite(keepAlive) + ? setInterval(() => { + // ping pong on open sockets only + if (socket.readyState === socket.OPEN) { + // terminate the connection after pong wait has passed because the client is idle + pongWait = setTimeout(() => { + socket.terminate(); + }, keepAlive); + + // listen for client's pong and stop socket termination + socket.once('pong', () => { + if (pongWait) { + clearTimeout(pongWait); + pongWait = null; + } + }); + + socket.ping(); + } + }, keepAlive) + : null; + + const closed = server.opened( + { + protocol: socket.protocol, + send: (data) => + new Promise((resolve, reject) => { + socket.send(data, (err) => (err ? reject(err) : resolve())); + }), + close: (code, reason) => socket.close(code, reason), + onMessage: (cb) => + socket.on('message', async (event) => { + try { + await cb(event.toString()); + } catch (err) { + socket.close(1011, isProd ? 'Internal Error' : err.message); + } + }), + }, + { connection, request } as Extra & Partial, + ); + + socket.once('close', (code, reason) => { + if (pongWait) clearTimeout(pongWait); + if (pingInterval) clearInterval(pingInterval); + if (!isProd && code === 1002) + console.warn( + `WebSocket protocol error occured. It was most likely caused due to an ` + + `unsupported subprotocol "${socket.protocol}" requested by the client. ` + + `graphql-ws implements exclusively the "${GRAPHQL_TRANSPORT_WS_PROTOCOL}" subprotocol, ` + + 'please make sure that the client implements it too.', + ); + closed(code, reason); + }); + }; +} diff --git a/yarn.lock b/yarn.lock index ebf3e942..8fe32ae8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1297,6 +1297,32 @@ __metadata: languageName: node linkType: hard +"@fastify/ajv-compiler@npm:^1.0.0": + version: 1.1.0 + resolution: "@fastify/ajv-compiler@npm:1.1.0" + dependencies: + ajv: ^6.12.6 + checksum: 2df7d2edb4f14fb6cfbdd3bba3d059e9c495ce33f8cce2b73c72a5ef753e0e98cd92cd7be9b025c970276a729a2f5309de14d1e8f06eaa75ef24224a7cd31778 + languageName: node + linkType: hard + +"@fastify/forwarded@npm:^1.0.0": + version: 1.0.0 + resolution: "@fastify/forwarded@npm:1.0.0" + checksum: ce8131fe51bed9315c2df8ff0d143a216ef603f88b0590e73e0e7abc6ad1eb78f6cab593b73ac9be0ca8e8921ec5c405161987a964011501451ca7e1b892839d + languageName: node + linkType: hard + +"@fastify/proxy-addr@npm:^3.0.0": + version: 3.0.0 + resolution: "@fastify/proxy-addr@npm:3.0.0" + dependencies: + "@fastify/forwarded": ^1.0.0 + ipaddr.js: ^2.0.0 + checksum: 9bad0be0ecd00b1d05b04620346d38442f092839ca038477cb31bc4937304c1e1593a0e0a8478208563c3b99f99b61d43879102904b97d8aa14132048642b552 + languageName: node + linkType: hard + "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.1.0 resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" @@ -2337,6 +2363,13 @@ __metadata: languageName: node linkType: hard +"abstract-logging@npm:^2.0.0": + version: 2.0.1 + resolution: "abstract-logging@npm:2.0.1" + checksum: 1c43af55808a983a44b4c6b35dd718045694b4ce8d51cf1d62ed38458bbc0dc096b8bffece56c76650c02b3e0692684b78402ee9a31e5aa322162321d6f88a7c + languageName: node + linkType: hard + "acorn-globals@npm:^6.0.0": version: 6.0.0 resolution: "acorn-globals@npm:6.0.0" @@ -2411,7 +2444,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.10.0, ajv@npm:^6.12.3, ajv@npm:^6.12.4": +"ajv@npm:^6.10.0, ajv@npm:^6.11.0, ajv@npm:^6.12.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.6": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -2535,7 +2568,7 @@ __metadata: languageName: node linkType: hard -"archy@npm:~1.0.0": +"archy@npm:^1.0.0, archy@npm:~1.0.0": version: 1.0.0 resolution: "archy@npm:1.0.0" checksum: fed06a0487f79dd89f30a8558f3e8f88011025ded47b10e412a4fc8f842a4ddec6e51af5a117258f5b84bef587cff7d1e056df4f453a7d8752a46e25bf5be7dc @@ -2633,6 +2666,25 @@ __metadata: languageName: node linkType: hard +"atomic-sleep@npm:^1.0.0": + version: 1.0.0 + resolution: "atomic-sleep@npm:1.0.0" + checksum: 2c6fa68cafef5ec1501245da00cde40b8f7ac71428bd727a923ea883b81ad643667a85677056cd663ad3ca584a49dbeb3a1bd4e6c70c1e9e36afd71b6e36ef96 + languageName: node + linkType: hard + +"avvio@npm:^7.1.2": + version: 7.2.2 + resolution: "avvio@npm:7.2.2" + dependencies: + archy: ^1.0.0 + debug: ^4.0.0 + fastq: ^1.6.1 + queue-microtask: ^1.1.2 + checksum: 8c63c109187213adc9cbe1e5907d850051ccbb4e19c2536b571fc5eced6ce19e566b920ef5f2e605feed3d0e7212ba31b755a717925928f8643baeca419ce9e3 + languageName: node + linkType: hard + "aws-sign2@npm:~0.7.0": version: 0.7.0 resolution: "aws-sign2@npm:0.7.0" @@ -3306,6 +3358,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.4.0": + version: 0.4.1 + resolution: "cookie@npm:0.4.1" + checksum: b8e0928e3e7aba013087974b33a6eec730b0a68b7ec00fc3c089a56ba2883bcf671252fc2ed64775aa1ca64796b6e1f6fdddba25a66808aef77614d235fd3e06 + languageName: node + linkType: hard + "core-js-compat@npm:^3.9.0, core-js-compat@npm:^3.9.1": version: 3.13.1 resolution: "core-js-compat@npm:3.13.1" @@ -4020,6 +4079,13 @@ __metadata: languageName: node linkType: hard +"fast-decode-uri-component@npm:^1.0.1": + version: 1.0.1 + resolution: "fast-decode-uri-component@npm:1.0.1" + checksum: cb3186565330299464f409745086404098127b834b3e8576ae90f3733cf6bf3ea1831e0e5c802c8b2263af0ac1ecf8aaeb99bc34bb4a935496b3318bb60ef3a1 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -4055,6 +4121,18 @@ __metadata: languageName: node linkType: hard +"fast-json-stringify@npm:^2.5.2": + version: 2.7.6 + resolution: "fast-json-stringify@npm:2.7.6" + dependencies: + ajv: ^6.11.0 + deepmerge: ^4.2.2 + rfdc: ^1.2.0 + string-similarity: ^4.0.1 + checksum: ec18d31dc1453896998f05f6a1470c76cdce14955b1a6fef2d3a4d0f23adde1d35fb4f3ac48b94a35ccffb514e9c17ecbb905f9a6e291321f331500f647c5c59 + languageName: node + linkType: hard + "fast-levenshtein@npm:^2.0.6, fast-levenshtein@npm:~2.0.6": version: 2.0.6 resolution: "fast-levenshtein@npm:2.0.6" @@ -4062,7 +4140,76 @@ __metadata: languageName: node linkType: hard -"fastq@npm:^1.6.0": +"fast-redact@npm:^3.0.0": + version: 3.0.1 + resolution: "fast-redact@npm:3.0.1" + checksum: f8b3d39a6073506a474ef049702b6a98d9b2d2e24bf573760fc0a7bd4bb61c9bf7c55129f650bb812410dda94e3b8c0043fb4a2034064028751ab354844a4fb1 + languageName: node + linkType: hard + +"fast-safe-stringify@npm:^2.0.7": + version: 2.0.7 + resolution: "fast-safe-stringify@npm:2.0.7" + checksum: 7bd22543263b707870d70c6f2336b6e8563e34d6807dc388cc0566895e31e0a8273af017a7eb1c9538d0ef54288284e1c0585b557bd856491295a847159fd929 + languageName: node + linkType: hard + +"fastify-error@npm:^0.3.0": + version: 0.3.1 + resolution: "fastify-error@npm:0.3.1" + checksum: ba3b964a9cc7ee72e1573f10951705ee8cadbc7a395af54ad5cfef7be05ad6eec558b71d7d9862cce92dd68145ccc7e9ab79487c37ed8c763bcf7bbf40ef6d36 + languageName: node + linkType: hard + +"fastify-plugin@npm:^3.0.0": + version: 3.0.0 + resolution: "fastify-plugin@npm:3.0.0" + checksum: 201c30749b42e77e6159359ae436a56243dc2b920d2efab126b2b680f61d23251d51ee2334a7cbb9b30d8be13746983df8d5695828fb1e4495e4d1d325f66793 + languageName: node + linkType: hard + +"fastify-warning@npm:^0.2.0": + version: 0.2.0 + resolution: "fastify-warning@npm:0.2.0" + checksum: 17b9e2ffdfbfd40486f3395e4c33a8f997c9d23c3201624eed5596f3e3dcd3d5bf4f0c18b3bf7c32f3e80d1d573df9d59bc5c179fcfe5de52a1baa64934a54cf + languageName: node + linkType: hard + +"fastify-websocket@npm:^3.2.0": + version: 3.2.0 + resolution: "fastify-websocket@npm:3.2.0" + dependencies: + fastify-plugin: ^3.0.0 + ws: ^7.4.2 + checksum: a1cc55381dbc80e88575b2573a715a32f8f42a861a540826b77fab1950751cb9de91950a7dd726947dc9a4057afbea6bb513260585f277e3b9d2ec89c410c737 + languageName: node + linkType: hard + +"fastify@npm:^3.17.0": + version: 3.17.0 + resolution: "fastify@npm:3.17.0" + dependencies: + "@fastify/ajv-compiler": ^1.0.0 + "@fastify/proxy-addr": ^3.0.0 + abstract-logging: ^2.0.0 + avvio: ^7.1.2 + fast-json-stringify: ^2.5.2 + fastify-error: ^0.3.0 + fastify-warning: ^0.2.0 + find-my-way: ^4.0.0 + flatstr: ^1.0.12 + light-my-request: ^4.2.0 + pino: ^6.2.1 + readable-stream: ^3.4.0 + rfdc: ^1.1.4 + secure-json-parse: ^2.0.0 + semver: ^7.3.2 + tiny-lru: ^7.0.0 + checksum: d2fe251909227c4cf6a4f0098b36bc97d4d0f9d368b7ce3fdca8c3a7aac0e76fb6c0031122b9faf22af9fa88b7edef3abcf31cb0c5c778cb3ebdd12b5d08983b + languageName: node + linkType: hard + +"fastq@npm:^1.6.0, fastq@npm:^1.6.1": version: 1.11.0 resolution: "fastq@npm:1.11.0" dependencies: @@ -4116,6 +4263,18 @@ __metadata: languageName: node linkType: hard +"find-my-way@npm:^4.0.0": + version: 4.1.0 + resolution: "find-my-way@npm:4.1.0" + dependencies: + fast-decode-uri-component: ^1.0.1 + fast-deep-equal: ^3.1.3 + safe-regex2: ^2.0.0 + semver-store: ^0.3.0 + checksum: 01a1cf36b8d7090dbf35b9a49abbf5aa2e0039ffad5f5d4fddd87810575c8897a226453e3d4ef1e0b1e6c0c84ba086089023b562b7419802a2a298019d2f0361 + languageName: node + linkType: hard + "find-up@npm:^2.0.0": version: 2.1.0 resolution: "find-up@npm:2.1.0" @@ -4154,6 +4313,13 @@ __metadata: languageName: node linkType: hard +"flatstr@npm:^1.0.12": + version: 1.0.12 + resolution: "flatstr@npm:1.0.12" + checksum: 2803767f91887ffd60ac2aac0d6ccf2dd9e2d8f216628a73e3f525d5b5bfa4ac9a5b57334a4c1e6d5622f92f440c52562f7ca9719ace9d025d6c5b7a1a1579db + languageName: node + linkType: hard + "flatted@npm:^3.1.0": version: 3.1.1 resolution: "flatted@npm:3.1.1" @@ -4449,6 +4615,8 @@ __metadata: eslint: ^7.27.0 eslint-config-prettier: ^8.3.0 eslint-plugin-prettier: ^3.4.0 + fastify: ^3.17.0 + fastify-websocket: ^3.2.0 glob: ^7.1.7 graphql: ^15.5.0 jest: ^27.0.4 @@ -4826,6 +4994,13 @@ __metadata: languageName: node linkType: hard +"ipaddr.js@npm:^2.0.0": + version: 2.0.0 + resolution: "ipaddr.js@npm:2.0.0" + checksum: c14a0aaac9b3383be5226ea8b0b7707d9d6b021d5a8b04c6cdd9f6cea29583089d93718e10a8352460ed63becd834c7b93b62dbd46d2db4595086c9dc880630b + languageName: node + linkType: hard + "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -5972,6 +6147,19 @@ __metadata: languageName: node linkType: hard +"light-my-request@npm:^4.2.0": + version: 4.4.1 + resolution: "light-my-request@npm:4.4.1" + dependencies: + ajv: ^6.12.2 + cookie: ^0.4.0 + fastify-warning: ^0.2.0 + readable-stream: ^3.6.0 + set-cookie-parser: ^2.4.1 + checksum: 3c095d268c3f797c8b69e44ef3d24e149aed2116e58b696144eecd975814ca50693603d04c6d50f462f736483fbc9b1e02a4c0a6c658964fea442493e28cd191 + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.1.6 resolution: "lines-and-columns@npm:1.1.6" @@ -7187,6 +7375,29 @@ __metadata: languageName: node linkType: hard +"pino-std-serializers@npm:^3.1.0": + version: 3.2.0 + resolution: "pino-std-serializers@npm:3.2.0" + checksum: fb386422f018951ecdaf241b76554d6149928e9dd5c89d1bc12100d61d7f14b140fcbbfcf9203921b21cda05cc3eab2499289fe272358d50836627ccda15f5ec + languageName: node + linkType: hard + +"pino@npm:^6.2.1": + version: 6.11.3 + resolution: "pino@npm:6.11.3" + dependencies: + fast-redact: ^3.0.0 + fast-safe-stringify: ^2.0.7 + flatstr: ^1.0.12 + pino-std-serializers: ^3.1.0 + quick-format-unescaped: ^4.0.3 + sonic-boom: ^1.0.2 + bin: + pino: bin.js + checksum: c6fa52d31bfc2ab3e0b746aa4f859bd723dc7b0e3f15773cd9cdfc16846707a101ce9856c71a671e6896c24627dcfaee429fb943afaf85e226d796716d0c03db + languageName: node + linkType: hard + "pirates@npm:^4.0.1": version: 4.0.1 resolution: "pirates@npm:4.0.1" @@ -7389,13 +7600,20 @@ __metadata: languageName: node linkType: hard -"queue-microtask@npm:^1.2.2": +"queue-microtask@npm:^1.1.2, queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" checksum: 0f88d794d4d825d39cdc2cda2fa701722858fc8de9567ad612776fce0d113376a3fc67f6a0091f31c9142b28f0c14fef08e9f92422b49f2372d5537e250fbfad languageName: node linkType: hard +"quick-format-unescaped@npm:^4.0.3": + version: 4.0.3 + resolution: "quick-format-unescaped@npm:4.0.3" + checksum: 08bbbe4937df113082fcc42dfbdb75df7e5291df805cd25347e0241f2f97bfb8368899dfbece10b79411249f81196b268ad1a36cc42208e4074cf89d27c3d055 + languageName: node + linkType: hard + "quick-lru@npm:^4.0.1": version: 4.0.1 resolution: "quick-lru@npm:4.0.1" @@ -7494,7 +7712,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:3, readable-stream@npm:^3.0.0": +"readable-stream@npm:3, readable-stream@npm:^3.0.0, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": version: 3.6.0 resolution: "readable-stream@npm:3.6.0" dependencies: @@ -7736,6 +7954,13 @@ __metadata: languageName: node linkType: hard +"ret@npm:~0.2.0": + version: 0.2.2 + resolution: "ret@npm:0.2.2" + checksum: ad260291ff71343d443016b7c6ba0831dd9a51201e621ac8cfc19ab3491ffab41d2b5e4e3a3def06568a665b37a901921f51739c5012c093f2d8582f2d39f3de + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -7750,6 +7975,13 @@ __metadata: languageName: node linkType: hard +"rfdc@npm:^1.1.4, rfdc@npm:^1.2.0": + version: 1.3.0 + resolution: "rfdc@npm:1.3.0" + checksum: 34dd5c5acf95c1248a81a539b899490e3b2a73373f2fa4187ce0e555581b64c4d08619f022970d6aabe9e044cfa4d4f6118939dfd640c01da68c36d89f118807 + languageName: node + linkType: hard + "rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" @@ -7812,6 +8044,15 @@ __metadata: languageName: node linkType: hard +"safe-regex2@npm:^2.0.0": + version: 2.0.0 + resolution: "safe-regex2@npm:2.0.0" + dependencies: + ret: ~0.2.0 + checksum: 7557097314b77a5d1405486ff9bed7a4d745b657b21a7242c7a9bbf9d7364d6329a603c6f6428d2037b339acf95c99e19808a9904ebfe92ce0525fa199d950d3 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -7828,6 +8069,13 @@ __metadata: languageName: node linkType: hard +"secure-json-parse@npm:^2.0.0": + version: 2.4.0 + resolution: "secure-json-parse@npm:2.4.0" + checksum: bbf325b52d3eb399b23a337ef7185380424600b4d6c07e62e8d1ab9e9cc657258b897841e4da1ecca9c9d4a83e426756f74fff1685640b3fbe4f481f577517d4 + languageName: node + linkType: hard + "semantic-release@npm:17.4.3, semantic-release@npm:^17.4.3": version: 17.4.3 resolution: "semantic-release@npm:17.4.3" @@ -7882,6 +8130,13 @@ __metadata: languageName: node linkType: hard +"semver-store@npm:^0.3.0": + version: 0.3.0 + resolution: "semver-store@npm:0.3.0" + checksum: 13ca89d1f480425ee362834406cebb3274e6c47c7f1104a144b738dae5877063a55b02fa955bfc276c1e53aa3fd1ac82f78e85ba2b312371d569ef5d4723bfe5 + languageName: node + linkType: hard + "semver@npm:2 || 3 || 4 || 5": version: 5.7.1 resolution: "semver@npm:5.7.1" @@ -7936,6 +8191,13 @@ __metadata: languageName: node linkType: hard +"set-cookie-parser@npm:^2.4.1": + version: 2.4.8 + resolution: "set-cookie-parser@npm:2.4.8" + checksum: 8460a56cf7ad505adbd00e26a6c8df2cc4575c83fe095a9223ac94ff5b1664ce35947ed013c0f45952335084210eb163f87732c812058ef9344582268d1cd799 + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -8047,6 +8309,16 @@ __metadata: languageName: node linkType: hard +"sonic-boom@npm:^1.0.2": + version: 1.4.1 + resolution: "sonic-boom@npm:1.4.1" + dependencies: + atomic-sleep: ^1.0.0 + flatstr: ^1.0.12 + checksum: d681f4ef6910e4ae698f17c9b1f3120ea9fc26ae25c870c5c6e73e29b5b4ba88005df507041f57a5e06ee85c739285f35c91604af9d61eabaeed96e79ef34824 + languageName: node + linkType: hard + "source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.19": version: 0.5.19 resolution: "source-map-support@npm:0.5.19" @@ -8212,6 +8484,13 @@ __metadata: languageName: node linkType: hard +"string-similarity@npm:^4.0.1": + version: 4.0.4 + resolution: "string-similarity@npm:4.0.4" + checksum: f80d907e2ba07012553fa5fc6ed5a9bc76da628acc0e8b7079a5bbcabff4c8c521287233424790651e52d8a8cdccfc071c163ebf23f18d09ff928dd3dc625f7e + languageName: node + linkType: hard + "string-width@npm:^1.0.1": version: 1.0.2 resolution: "string-width@npm:1.0.2" @@ -8513,6 +8792,13 @@ __metadata: languageName: node linkType: hard +"tiny-lru@npm:^7.0.0": + version: 7.0.6 + resolution: "tiny-lru@npm:7.0.6" + checksum: 9fb221279eee2747e20511ce9d4cc080527b6c44f835b867d5f02e136fe0e34f0c10348af7e22c56420ffb69636ac042eb4ffead90dd8d80a7b6368be30f112a + languageName: node + linkType: hard + "tiny-relative-date@npm:^1.3.0": version: 1.3.0 resolution: "tiny-relative-date@npm:1.3.0" @@ -9109,7 +9395,7 @@ typescript@4.2.4: languageName: node linkType: hard -"ws@npm:^7.4.5, ws@npm:^7.4.6": +"ws@npm:^7.4.2, ws@npm:^7.4.5, ws@npm:^7.4.6": version: 7.4.6 resolution: "ws@npm:7.4.6" peerDependencies: