diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 3bf0f2ea..ec1b5cbd 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -76,8 +76,8 @@ jobs: run: | NODE_ENV=production node benchmark/servers/ws8.mjs & SERVER=ws8 ./k6 run benchmark/k6.mjs - fastify-websocket_ws8: - name: fastify-websocket_ws8 + fastify-websocket: + name: '@fastify/websocket' runs-on: ubuntu-latest steps: - name: Checkout @@ -96,8 +96,8 @@ jobs: run: yarn run build:esm - name: Run run: | - NODE_ENV=production node benchmark/servers/fastify-websocket_ws8.mjs & - SERVER=fastify-websocket_ws8 ./k6 run benchmark/k6.mjs + NODE_ENV=production node benchmark/servers/fastify-websocket.mjs & + SERVER="@fastify/websocket" ./k6 run benchmark/k6.mjs legacy_ws7: name: legacy_ws7 runs-on: ubuntu-latest diff --git a/README.md b/README.md index 0beea595..0aed9970 100644 --- a/README.md +++ b/README.md @@ -99,18 +99,20 @@ uWS }); ``` -##### With [fastify-websocket](https://github.com/fastify/fastify-websocket) +##### 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'; +import fastifyWebsocket from '@fastify/websocket'; // yarn add @fastify/websocket +import { makeHandler } from 'graphql-ws/lib/use/@fastify/websocket'; import { schema } from './previous-step'; const fastify = Fastify(); fastify.register(fastifyWebsocket); -fastify.get('/graphql', { websocket: true }, makeHandler({ schema })); +fastify.register(async (fastify) => { + fastify.get('/graphql', { websocket: true }, makeHandler({ schema })); +}); fastify.listen(4000, (err) => { if (err) { @@ -1322,6 +1324,31 @@ httpServer.listen(4000); +
+🔗 ws server usage with deprecated fastify-websocket + +```typescript +import Fastify from 'fastify'; // yarn add fastify@^3 +import fastifyWebsocket from 'fastify-websocket'; // yarn add fastify-websocket@4.2.2 +import { makeHandler } from 'graphql-ws/lib/use/fastify-websocket'; +import { schema } from './previous-step'; + +const fastify = Fastify(); +fastify.register(fastifyWebsocket); + +fastify.get('/graphql', { websocket: true }, makeHandler({ schema })); + +fastify.listen(4000, (err) => { + if (err) { + fastify.log.error(err); + return process.exit(1); + } + console.log('Listening to port 4000'); +}); +``` + +
+
🔗 ws server usage with subscriptions-transport-ws backwards compatibility diff --git a/benchmark/servers/fastify-websocket.mjs b/benchmark/servers/fastify-websocket.mjs new file mode 100644 index 00000000..c08d3a4b --- /dev/null +++ b/benchmark/servers/fastify-websocket.mjs @@ -0,0 +1,21 @@ +import Fastify from 'fastify'; +import fastifyWebsocket from '@fastify/websocket'; +import { ports } from './ports.mjs'; +import { makeHandler } from '../../lib/use/@fastify/websocket.mjs'; +import { schema } from './schema.mjs'; + +const fastify = Fastify(); +fastify.register(fastifyWebsocket); +fastify.register(async (fastify) => { + fastify.get('/graphql', { websocket: true }, makeHandler({ schema })); +}); + +fastify.listen({ port: ports['@fastify/websocket'] }, (err) => { + if (err) { + fastify.log.error(err); + return process.exit(1); + } + console.log( + `@fastify/websocket - listening on port ${ports['@fastify/websocket']}...`, + ); +}); diff --git a/benchmark/servers/fastify-websocket_ws8.mjs b/benchmark/servers/fastify-websocket_ws8.mjs deleted file mode 100644 index c84a5b9d..00000000 --- a/benchmark/servers/fastify-websocket_ws8.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import Fastify from 'fastify'; -import fastifyWebsocket from 'fastify-websocket'; -import { ports } from './ports.mjs'; -import { makeHandler } from '../../lib/use/fastify-websocket.mjs'; -import { schema } from './schema.mjs'; - -const fastify = Fastify(); -fastify.register(fastifyWebsocket); - -fastify.get('/graphql', { websocket: true }, makeHandler({ schema })); - -fastify.listen(ports['fastify-websocket_ws8'], (err) => { - if (err) { - fastify.log.error(err); - return process.exit(1); - } - console.log( - `fastify-websocket_ws8 - listening on port ${ports['fastify-websocket_ws8']}...`, - ); -}); diff --git a/benchmark/servers/index.mjs b/benchmark/servers/index.mjs index d61ea00b..9e560402 100644 --- a/benchmark/servers/index.mjs +++ b/benchmark/servers/index.mjs @@ -1,5 +1,5 @@ import './uWebSockets.mjs'; import './ws8.mjs'; import './ws7.mjs'; -import './fastify-websocket_ws8.mjs'; +import './fastify-websocket.mjs'; import './legacy_ws7.mjs'; diff --git a/benchmark/servers/ports.mjs b/benchmark/servers/ports.mjs index 2ba151c9..5f2a1eb8 100644 --- a/benchmark/servers/ports.mjs +++ b/benchmark/servers/ports.mjs @@ -3,5 +3,5 @@ export const ports = { ws7: 6543, uWebSockets: 6541, legacy_ws7: 6542, - 'fastify-websocket_ws8': 6544, + '@fastify/websocket': 6544, }; diff --git a/docs/README.md b/docs/README.md index 6f4ee3dd..309f5378 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,6 +9,7 @@ graphql-ws - [client](modules/client.md) - [common](modules/common.md) - [server](modules/server.md) +- [use/@fastify/websocket](modules/use__fastify_websocket.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..93b0bb81 --- /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`<`RouteGenericInterface`, `Server`, `IncomingMessage`, `FastifySchema`, `FastifyTypeProviderDefault`, `unknown`, `FastifyBaseLogger`, `ResolveFastifyRequestType`<`FastifyTypeProviderDefault`, `FastifySchema`, `RouteGenericInterface`\>\> + +The initial HTTP upgrade request before the actual +socket and connection is established. diff --git a/docs/interfaces/use_fastify_websocket.Extra.md b/docs/interfaces/use_fastify_websocket.Extra.md index d7cd9244..2c3ebf23 100644 --- a/docs/interfaces/use_fastify_websocket.Extra.md +++ b/docs/interfaces/use_fastify_websocket.Extra.md @@ -6,6 +6,10 @@ The extra that will be put in the `Context`. +**`Deprecated`** + + Use `@fastify/websocket` instead. + ## Table of contents ### Properties @@ -26,7 +30,7 @@ ___ ### request -• `Readonly` **request**: `FastifyRequest`<`RouteGenericInterface`, `Server`, `IncomingMessage`, `unknown`, `FastifyLoggerInstance`\> +• `Readonly` **request**: `FastifyRequest`<`RouteGenericInterface`, `Server`, `IncomingMessage`, `FastifySchema`, `FastifyTypeProviderDefault`, `unknown`, `FastifyBaseLogger`, `ResolveFastifyRequestType`<`FastifyTypeProviderDefault`, `FastifySchema`, `RouteGenericInterface`\>\> 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..12821ade --- /dev/null +++ b/docs/modules/use__fastify_websocket.md @@ -0,0 +1,40 @@ +[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**<`P`, `E`\>(`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 | +| :------ | :------ | +| `P` | extends `undefined` \| `Record`<`string`, `unknown`\> = `undefined` \| `Record`<`string`, `unknown`\> | +| `E` | extends `Record`<`PropertyKey`, `unknown`\> = `Record`<`PropertyKey`, `never`\> | + +#### Parameters + +| Name | Type | Default value | +| :------ | :------ | :------ | +| `options` | [`ServerOptions`](../interfaces/server.ServerOptions.md)<`P`, [`Extra`](../interfaces/use__fastify_websocket.Extra.md) & `Partial`<`E`\>\> | `undefined` | +| `keepAlive` | `number` | `12_000` | + +#### Returns + +`fastifyWebsocket.WebsocketHandler` diff --git a/docs/modules/use_fastify_websocket.md b/docs/modules/use_fastify_websocket.md index 530714e0..84977154 100644 --- a/docs/modules/use_fastify_websocket.md +++ b/docs/modules/use_fastify_websocket.md @@ -21,6 +21,10 @@ 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 +**`Deprecated`** + + Use `@fastify/websocket` instead. + #### Type parameters | Name | Type | diff --git a/package.json b/package.json index dcbe8902..b860065d 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,11 @@ "import": "./lib/use/uWebSockets.mjs", "types": "./lib/use/uWebSockets.d.ts" }, + "./lib/use/@fastify/websocket": { + "require": "./lib/use/@fastify/websocket.js", + "import": "./lib/use/@fastify/websocket.mjs", + "types": "./lib/use/@fastify/websocket.d.ts" + }, "./lib/use/fastify-websocket": { "require": "./lib/use/fastify-websocket.js", "import": "./lib/use/fastify-websocket.mjs", @@ -93,6 +98,7 @@ "@babel/plugin-proposal-optional-chaining": "^7.18.6", "@babel/preset-env": "^7.18.6", "@babel/preset-typescript": "^7.18.6", + "@fastify/websocket": "^7.0.0", "@rollup/plugin-typescript": "^8.3.3", "@semantic-release/changelog": "^6.0.1", "@semantic-release/git": "^10.0.1", @@ -104,7 +110,7 @@ "eslint": "^8.18.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", - "fastify": "^3.29.0", + "fastify": "^4.4.0", "fastify-websocket": "4.2.2", "glob": "^8.0.3", "graphql": "^16.5.0", diff --git a/src/__tests__/__snapshots__/use.ts.snap b/src/__tests__/__snapshots__/use.ts.snap index a667fcf3..ce1a6fa0 100644 --- a/src/__tests__/__snapshots__/use.ts.snap +++ b/src/__tests__/__snapshots__/use.ts.snap @@ -1,12 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`fastify-websocket should limit the server emitted error message size 1`] = `"Internal error emitted on the WebSocket server. Please check your implementation."`; +exports[`@fastify/websocket should limit the server emitted error message size 1`] = `"Internal error emitted on the WebSocket server. Please check your implementation."`; -exports[`fastify-websocket should limit the socket emitted error message size 1`] = `"Internal error emitted on a WebSocket socket. Please check your implementation."`; +exports[`@fastify/websocket should limit the socket emitted error message size 1`] = `"Internal error emitted on a WebSocket socket. Please check your implementation."`; -exports[`fastify-websocket should report server emitted errors to clients by closing the connection 1`] = `"Internal error emitted on the WebSocket server. Please check your implementation."`; +exports[`@fastify/websocket should report server emitted errors to clients by closing the connection 1`] = `"Internal error emitted on the WebSocket server. Please check your implementation."`; -exports[`fastify-websocket should report socket emitted errors to clients by closing the connection 1`] = `"Internal error emitted on a WebSocket socket. Please check your implementation."`; +exports[`@fastify/websocket should report socket emitted errors to clients by closing the connection 1`] = `"Internal error emitted on a WebSocket socket. Please check your implementation."`; exports[`ws should limit the server emitted error message size 1`] = `"Internal error emitted on the WebSocket server. Please check your implementation."`; diff --git a/src/__tests__/use.ts b/src/__tests__/use.ts index 7e2ae798..1d32cb9e 100644 --- a/src/__tests__/use.ts +++ b/src/__tests__/use.ts @@ -147,7 +147,7 @@ for (const { tServer, skipUWS, startTServer } of tServers) { expect((ctx.extra as WSExtra).request).toBeInstanceOf( http.IncomingMessage, ); - } else if (tServer === 'fastify-websocket') { + } else if (tServer === '@fastify/websocket') { expect((ctx.extra as FastifyExtra).connection).toBeInstanceOf( stream.Duplex, ); @@ -155,7 +155,7 @@ for (const { tServer, skipUWS, startTServer } of tServers) { (ctx.extra as FastifyExtra).connection.socket, ).toBeInstanceOf(ws); expect((ctx.extra as FastifyExtra).request.constructor.name).toBe( - 'Request', + '_Request', ); } else { fail('Missing test case for ' + tServer); diff --git a/src/__tests__/utils/tservers.ts b/src/__tests__/utils/tservers.ts index 71d68a0b..f7daed43 100644 --- a/src/__tests__/utils/tservers.ts +++ b/src/__tests__/utils/tservers.ts @@ -8,7 +8,7 @@ import ws, { WebSocketServer } from 'ws'; import ws7 from 'ws7'; import uWS from 'uWebSockets.js'; import Fastify from 'fastify'; -import fastifyWebsocket from 'fastify-websocket'; +import fastifyWebsocket from '@fastify/websocket'; import { useServer as useWSServer, Extra as WSExtra } from '../../use/ws'; import { @@ -18,7 +18,7 @@ import { import { makeHandler as makeFastifyHandler, Extra as FastifyExtra, -} from '../../use/fastify-websocket'; +} from '../../use/@fastify/websocket'; export { WSExtra, UWSExtra, FastifyExtra }; // distinct server for each test; if you forget to dispose, the fixture wont @@ -475,9 +475,6 @@ export async function startFastifyWSTServer( const emitter = new EventEmitter(); const port = await getAvailablePort(); - const fastify = Fastify(); - fastify.register(fastifyWebsocket); - // sockets to kick off on teardown const sockets = new Set(); @@ -500,44 +497,48 @@ export async function startFastifyWSTServer( }; } - 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'); - }); + const fastify = Fastify(); + fastify.register(fastifyWebsocket); + fastify.register(async (fastify) => { + 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'); + 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); + keepAlive, + ).call(fastify, connection, request); + }); }); const dispose: Dispose = (beNice) => { @@ -559,12 +560,7 @@ export async function startFastifyWSTServer( }; leftovers.push(dispose); - await new Promise((resolve, reject) => { - fastify.listen(port, (err) => { - if (err) return reject(err); - resolve(); - }); - }); + await fastify.listen({ port }); return { url: `ws://localhost:${port}${path}`, @@ -695,7 +691,7 @@ export const tServers = [ itForFastify: it.skip, }, { - tServer: 'fastify-websocket' as const, + tServer: '@fastify/websocket' as const, startTServer: startFastifyWSTServer, skipWS: it, skipUWS: it, diff --git a/src/use/@fastify/websocket.ts b/src/use/@fastify/websocket.ts new file mode 100644 index 00000000..e77e1d07 --- /dev/null +++ b/src/use/@fastify/websocket.ts @@ -0,0 +1,187 @@ +import type { FastifyRequest } from 'fastify'; +import type * as fastifyWebsocket from '@fastify/websocket'; +import { handleProtocols, makeServer, ServerOptions } from '../../server'; +import { + DEPRECATED_GRAPHQL_WS_PROTOCOL, + ConnectionInitMessage, + CloseCode, +} from '../../common'; +import { limitCloseReason } from '../../utils'; + +/** + * 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< + P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'], + 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_000 // 12 seconds + */ + keepAlive = 12_000, +): fastifyWebsocket.WebsocketHandler { + const isProd = process.env.NODE_ENV === 'production'; + const server = makeServer(options); + + // we dont have access to the fastify-websocket server instance yet, + // register an error handler on first connection ONCE only + let handlingServerEmittedErrors = false; + + return function handler(connection, request) { + const { socket } = connection; + + // might be too late, but meh + this.websocketServer.options.handleProtocols = handleProtocols; + + // handle server emitted errors only if not already handling + if (!handlingServerEmittedErrors) { + handlingServerEmittedErrors = true; + this.websocketServer.once('error', (err) => { + console.error( + 'Internal error emitted on the WebSocket server. ' + + 'Please check your implementation.', + err, + ); + + // catch the first thrown error and re-throw it once all clients have been notified + let firstErr: Error | null = null; + + // report server errors by erroring out all clients with the same error + for (const client of this.websocketServer.clients) { + try { + client.close( + CloseCode.InternalServerError, + isProd + ? 'Internal server error' + : limitCloseReason(err.message, 'Internal server error'), + ); + } catch (err) { + firstErr = firstErr ?? err; + } + } + + if (firstErr) throw firstErr; + }); + } + + // used as listener on two streams, prevent superfluous calls on close + let emittedErrorHandled = false; + function handleEmittedError(err: Error) { + if (emittedErrorHandled) return; + emittedErrorHandled = true; + console.error( + 'Internal error emitted on a WebSocket socket. ' + + 'Please check your implementation.', + err, + ); + socket.close( + CloseCode.InternalServerError, + isProd + ? 'Internal server error' + : limitCloseReason(err.message, 'Internal server error'), + ); + } + + // fastify-websocket uses the WebSocket.createWebSocketStream, + // therefore errors get emitted on both the connection and the socket + connection.once('error', handleEmittedError); + socket.once('error', handleEmittedError); + + // 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) => { + if (socket.readyState !== socket.OPEN) return resolve(); + 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(String(event)); + } catch (err) { + console.error( + 'Internal error occurred during message handling. ' + + 'Please check your implementation.', + err, + ); + socket.close( + CloseCode.InternalServerError, + isProd + ? 'Internal server error' + : limitCloseReason(err.message, 'Internal server error'), + ); + } + }), + }, + { connection, request } as Extra & Partial, + ); + + socket.once('close', (code, reason) => { + if (pongWait) clearTimeout(pongWait); + if (pingInterval) clearInterval(pingInterval); + if ( + !isProd && + code === CloseCode.SubprotocolNotAcceptable && + socket.protocol === DEPRECATED_GRAPHQL_WS_PROTOCOL + ) + console.warn( + `Client provided the unsupported and deprecated subprotocol "${socket.protocol}" used by subscriptions-transport-ws.` + + 'Please see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws.', + ); + closed(code, String(reason)); + }); + }; +} diff --git a/src/use/fastify-websocket.ts b/src/use/fastify-websocket.ts index 5708a8a9..72ae43fd 100644 --- a/src/use/fastify-websocket.ts +++ b/src/use/fastify-websocket.ts @@ -1,16 +1,14 @@ import type { FastifyRequest } from 'fastify'; import type * as fastifyWebsocket from 'fastify-websocket'; -import { handleProtocols, makeServer, ServerOptions } from '../server'; -import { - DEPRECATED_GRAPHQL_WS_PROTOCOL, - ConnectionInitMessage, - CloseCode, -} from '../common'; -import { limitCloseReason } from '../utils'; +import { makeHandler as makeHandlerCurrent } from './@fastify/websocket'; +import { ServerOptions } from '../server'; +import { ConnectionInitMessage } from '../common'; /** * The extra that will be put in the `Context`. * + * @deprecated Use `@fastify/websocket` instead. + * * @category Server/fastify-websocket */ export interface Extra { @@ -30,6 +28,8 @@ export interface Extra { * 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 * + * @deprecated Use `@fastify/websocket` instead. + * * @category Server/fastify-websocket */ export function makeHandler< @@ -46,142 +46,6 @@ export function makeHandler< */ keepAlive = 12_000, ): fastifyWebsocket.WebsocketHandler { - const isProd = process.env.NODE_ENV === 'production'; - const server = makeServer(options); - - // we dont have access to the fastify-websocket server instance yet, - // register an error handler on first connection ONCE only - let handlingServerEmittedErrors = false; - - return function handler(connection, request) { - const { socket } = connection; - - // might be too late, but meh - this.websocketServer.options.handleProtocols = handleProtocols; - - // handle server emitted errors only if not already handling - if (!handlingServerEmittedErrors) { - handlingServerEmittedErrors = true; - this.websocketServer.once('error', (err) => { - console.error( - 'Internal error emitted on the WebSocket server. ' + - 'Please check your implementation.', - err, - ); - - // catch the first thrown error and re-throw it once all clients have been notified - let firstErr: Error | null = null; - - // report server errors by erroring out all clients with the same error - for (const client of this.websocketServer.clients) { - try { - client.close( - CloseCode.InternalServerError, - isProd - ? 'Internal server error' - : limitCloseReason(err.message, 'Internal server error'), - ); - } catch (err) { - firstErr = firstErr ?? err; - } - } - - if (firstErr) throw firstErr; - }); - } - - // used as listener on two streams, prevent superfluous calls on close - let emittedErrorHandled = false; - function handleEmittedError(err: Error) { - if (emittedErrorHandled) return; - emittedErrorHandled = true; - console.error( - 'Internal error emitted on a WebSocket socket. ' + - 'Please check your implementation.', - err, - ); - socket.close( - CloseCode.InternalServerError, - isProd - ? 'Internal server error' - : limitCloseReason(err.message, 'Internal server error'), - ); - } - - // fastify-websocket uses the WebSocket.createWebSocketStream, - // therefore errors get emitted on both the connection and the socket - connection.once('error', handleEmittedError); - socket.once('error', handleEmittedError); - - // 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) => { - if (socket.readyState !== socket.OPEN) return resolve(); - 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(String(event)); - } catch (err) { - console.error( - 'Internal error occurred during message handling. ' + - 'Please check your implementation.', - err, - ); - socket.close( - CloseCode.InternalServerError, - isProd - ? 'Internal server error' - : limitCloseReason(err.message, 'Internal server error'), - ); - } - }), - }, - { connection, request } as Extra & Partial, - ); - - socket.once('close', (code, reason) => { - if (pongWait) clearTimeout(pongWait); - if (pingInterval) clearInterval(pingInterval); - if ( - !isProd && - code === CloseCode.SubprotocolNotAcceptable && - socket.protocol === DEPRECATED_GRAPHQL_WS_PROTOCOL - ) - console.warn( - `Client provided the unsupported and deprecated subprotocol "${socket.protocol}" used by subscriptions-transport-ws.` + - 'Please see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws.', - ); - closed(code, String(reason)); - }); - }; + // new handler can be reused, the semantics stayed the same + return makeHandlerCurrent(options, keepAlive); } diff --git a/yarn.lock b/yarn.lock index 4269bd7c..927640f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1337,19 +1337,47 @@ __metadata: languageName: node linkType: hard -"@fastify/ajv-compiler@npm:^1.0.0": +"@fastify/ajv-compiler@npm:^3.1.1": + version: 3.1.2 + resolution: "@fastify/ajv-compiler@npm:3.1.2" + dependencies: + ajv: ^8.10.0 + ajv-formats: ^2.1.1 + fast-uri: ^2.0.0 + checksum: 8876754279eef74d91b2a5dd42c2a5ca43c80de92a15d1b6cb980e0b27f572710a67a7675dd100b172e92941efaf2ecda57653d254542e5d94ca3ff785e0ce31 + languageName: node + linkType: hard + +"@fastify/deepmerge@npm:^1.0.0": version: 1.1.0 - resolution: "@fastify/ajv-compiler@npm:1.1.0" + resolution: "@fastify/deepmerge@npm:1.1.0" + checksum: 3e6839ef2ab5ab524d90290c11e6997abda53cdfa9037639a794f4f1e1d9047c1c23e51d44d8a74b303285ef08f753d4bcff631f36d581ad24ee9fee6a0d2cf1 + languageName: node + linkType: hard + +"@fastify/error@npm:^3.0.0": + version: 3.0.0 + resolution: "@fastify/error@npm:3.0.0" + checksum: d9ea16db2d17e4d54f34ad2daf7bbd223fd3fd5682e55406f61dae66616a2fd79fa7585736e6e3b46e9dc60da6e96018f92ebb2f87fd100b4e8ad27308aa9c74 + languageName: node + linkType: hard + +"@fastify/fast-json-stringify-compiler@npm:^4.0.0": + version: 4.0.0 + resolution: "@fastify/fast-json-stringify-compiler@npm:4.0.0" dependencies: - ajv: ^6.12.6 - checksum: b8a2522ead00a01ab7ff2921f00aa8e4aeb943949191ce2a617c88e4679db1358a70e4099791828a397a50e5d6f6bd75184ad0ac75a12dffeb9df4c089986a32 + fast-json-stringify: ^5.0.0 + checksum: 5cfeebcd9fb7ae5d912836503a7934ed4b9db83c30cae15e393341b52c059cd83531b375226406f8056b09f472c11560c730999c7ae4647c2fb9771d372db60a languageName: node linkType: hard -"@fastify/error@npm:^2.0.0": - version: 2.0.0 - resolution: "@fastify/error@npm:2.0.0" - checksum: ecf0834966b2bfb33ff834e3d55fe4dc04cbe9f822fda6c937b12cce4f162be4f8b0577ee665bc856d7012b1640c12472a1829a22ae38d287342c90b0f33a595 +"@fastify/websocket@npm:^7.0.0": + version: 7.0.0 + resolution: "@fastify/websocket@npm:7.0.0" + dependencies: + fastify-plugin: ^4.0.0 + ws: ^8.0.0 + checksum: 7a08519997d70aa553c9205327c22df1849e0331bc5eb59d15ecd55879072005c10dd411059a5d7aaea73e02f3411852c2999da93353216e290d626431790672 languageName: node linkType: hard @@ -2585,7 +2613,16 @@ __metadata: languageName: node linkType: hard -"abstract-logging@npm:^2.0.0": +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: ^5.0.0 + checksum: 170bdba9b47b7e65906a28c8ce4f38a7a369d78e2271706f020849c1bfe0ee2067d4261df8bbb66eb84f79208fd5b710df759d64191db58cfba7ce8ef9c54b75 + languageName: node + linkType: hard + +"abstract-logging@npm:^2.0.1": version: 2.0.1 resolution: "abstract-logging@npm:2.0.1" checksum: 6967d15e5abbafd17f56eaf30ba8278c99333586fa4f7935fd80e93cfdc006c37fcc819c5d63ee373a12e6cb2d0417f7c3c6b9e42b957a25af9937d26749415e @@ -2666,7 +2703,21 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.10.0, ajv@npm:^6.11.0, ajv@npm:^6.12.4, ajv@npm:^6.12.6": +"ajv-formats@npm:^2.1.1": + version: 2.1.1 + resolution: "ajv-formats@npm:2.1.1" + dependencies: + ajv: ^8.0.0 + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 4a287d937f1ebaad4683249a4c40c0fa3beed30d9ddc0adba04859026a622da0d317851316ea64b3680dc60f5c3c708105ddd5d5db8fe595d9d0207fd19f90b7 + languageName: node + linkType: hard + +"ajv@npm:^6.10.0, ajv@npm:^6.12.4": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -2678,7 +2729,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.1.0": +"ajv@npm:^8.0.0, ajv@npm:^8.10.0": version: 8.11.0 resolution: "ajv@npm:8.11.0" dependencies: @@ -2853,15 +2904,14 @@ __metadata: languageName: node linkType: hard -"avvio@npm:^7.1.2": - version: 7.2.5 - resolution: "avvio@npm:7.2.5" +"avvio@npm:^8.1.3": + version: 8.2.0 + resolution: "avvio@npm:8.2.0" dependencies: archy: ^1.0.0 debug: ^4.0.0 fastq: ^1.6.1 - queue-microtask: ^1.1.2 - checksum: 9a0aa7208441f5abe49c12741ad7f127283ec749b25f0b941a1c84e8f41e958b86b56a1c6baff5a7b5ae5e713a919f1128702dbcf80a6b17e7dc5b095c85b3bd + checksum: bbd06eeb1f9ef428dbc32a32e06c350a7b320f60348698fd234145a4100f3688ce5d0999b966eb6ca70f9511d0c35fed5ef4651d276715e7e3e94a2d465cb56d languageName: node linkType: hard @@ -4094,6 +4144,13 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 1ffe3bb22a6d51bdeb6bf6f7cf97d2ff4a74b017ad12284cc9e6a279e727dc30a5de6bb613e5596ff4dc3e517841339ad09a7eec44266eccb1aa201a30448166 + languageName: node + linkType: hard + "eventemitter3@npm:^3.1.0": version: 3.1.2 resolution: "eventemitter3@npm:3.1.2" @@ -4138,13 +4195,6 @@ __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: 427a48fe0907e76f0e9a2c228e253b4d8a8ab21d130ee9e4bb8339c5ba4086235cf9576831f7b20955a752eae4b525a177ff9d5825dd8d416e7726939194fbee - 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" @@ -4179,15 +4229,16 @@ __metadata: languageName: node linkType: hard -"fast-json-stringify@npm:^2.5.2": - version: 2.7.13 - resolution: "fast-json-stringify@npm:2.7.13" +"fast-json-stringify@npm:^5.0.0": + version: 5.1.0 + resolution: "fast-json-stringify@npm:5.1.0" dependencies: - ajv: ^6.11.0 - deepmerge: ^4.2.2 + "@fastify/deepmerge": ^1.0.0 + ajv: ^8.10.0 + ajv-formats: ^2.1.1 + fast-uri: ^2.1.0 rfdc: ^1.2.0 - string-similarity: ^4.0.1 - checksum: f78ab25047c790de5b521c369e0b18c595055d48a106add36e9f86fe45be40226f168ff4708a226e187d0b46f1d6b32129842041728944bd9a03ca5efbbe4ccb + checksum: 712e795ecb9d261b4c2c3c5375dbb038434e2bba7eada617d7280c13c7266ab56553f731c5a2a26a71d2d23e485f04949a18374b958aa7cc12773174f86402cb languageName: node linkType: hard @@ -4198,17 +4249,17 @@ __metadata: languageName: node linkType: hard -"fast-redact@npm:^3.0.0": +"fast-redact@npm:^3.1.1": version: 3.1.1 resolution: "fast-redact@npm:3.1.1" checksum: e486cc9990b5c9724f39bf4e392c1b250c8fd5e8c0145be80c73de3461fc390babe7b48f35746b50bf3cbcd917e093b5685ae66295162c7d9b686a761d48e989 languageName: node linkType: hard -"fast-safe-stringify@npm:^2.0.8": - version: 2.1.1 - resolution: "fast-safe-stringify@npm:2.1.1" - checksum: a851cbddc451745662f8f00ddb622d6766f9bd97642dabfd9a405fb0d646d69fc0b9a1243cbf67f5f18a39f40f6fa821737651ff1bceeba06c9992ca2dc5bd3d +"fast-uri@npm:^2.0.0, fast-uri@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-uri@npm:2.1.0" + checksum: 60ecece5ab05515729ec04d1732ee68bd4429cab8c06ebf8db512a094a0077ddc5af6a27c75922875bc9e13b58e947832242cdcb2cb23c51dc753412222dca83 languageName: node linkType: hard @@ -4226,6 +4277,13 @@ __metadata: languageName: node linkType: hard +"fastify-plugin@npm:^4.0.0": + version: 4.1.0 + resolution: "fastify-plugin@npm:4.1.0" + checksum: 98d4041c3dc51e1146d74cc76c68d8074d4fbedc8f1094de1f4e54fc9b4a7600e64431e3937be9412f25397b7d0a7ee66be55d2e6c292b67782a5f0c40bea970 + languageName: node + linkType: hard + "fastify-websocket@npm:4.2.2": version: 4.2.2 resolution: "fastify-websocket@npm:4.2.2" @@ -4236,26 +4294,25 @@ __metadata: languageName: node linkType: hard -"fastify@npm:^3.29.0": - version: 3.29.0 - resolution: "fastify@npm:3.29.0" - dependencies: - "@fastify/ajv-compiler": ^1.0.0 - "@fastify/error": ^2.0.0 - abstract-logging: ^2.0.0 - avvio: ^7.1.2 - fast-json-stringify: ^2.5.2 - find-my-way: ^4.5.0 - flatstr: ^1.0.12 - light-my-request: ^4.2.0 - pino: ^6.13.0 - process-warning: ^1.0.0 +"fastify@npm:^4.4.0": + version: 4.4.0 + resolution: "fastify@npm:4.4.0" + dependencies: + "@fastify/ajv-compiler": ^3.1.1 + "@fastify/error": ^3.0.0 + "@fastify/fast-json-stringify-compiler": ^4.0.0 + abstract-logging: ^2.0.1 + avvio: ^8.1.3 + find-my-way: ^7.0.0 + light-my-request: ^5.0.0 + pino: ^8.0.0 + process-warning: ^2.0.0 proxy-addr: ^2.0.7 - rfdc: ^1.1.4 - secure-json-parse: ^2.0.0 - semver: ^7.3.2 - tiny-lru: ^8.0.1 - checksum: ed2964035e34843d08c09eee80f5f14bd8cc0ab9b46ac9d146c6821b586a359a93e1354fca4004ac14e37b267afe5bb1ba3ddb555ecf09b74d9b6bf2f9893ba1 + rfdc: ^1.3.0 + secure-json-parse: ^2.4.0 + semver: ^7.3.7 + tiny-lru: ^8.0.2 + checksum: ca5296d6b97916a2ed9596fa3f2d76e2e0b2cc45185adec2c693900bff54550fbed0029dd4106c7c2075ffc877245bbac58068ffc75993b524d00d733c962cde languageName: node linkType: hard @@ -4313,15 +4370,13 @@ __metadata: languageName: node linkType: hard -"find-my-way@npm:^4.5.0": - version: 4.5.1 - resolution: "find-my-way@npm:4.5.1" +"find-my-way@npm:^7.0.0": + version: 7.0.1 + resolution: "find-my-way@npm:7.0.1" 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: 85b8c07d34a36f0203438e0c0f0cdbfaf5e1c521ed2e56f9250bed846ceb0eea074127fad7f70137d61bed56387047f212969cc0ba5d818ed5e37b3e3606c43f + checksum: ffadbab35c75cb6dc7d9f81a94c4c305713d01b676d684069d751b2d8525d5c84e4fe4f439345b0aed80b7da18ca4fc13579d3c497488ab06976ee30ed78eb33 languageName: node linkType: hard @@ -4363,13 +4418,6 @@ __metadata: languageName: node linkType: hard -"flatstr@npm:^1.0.12": - version: 1.0.12 - resolution: "flatstr@npm:1.0.12" - checksum: e1bb562c94b119e958bf37e55738b172b5f8aaae6532b9660ecd877779f8559dbbc89613ba6b29ccc13447e14c59277d41450f785cf75c30df9fce62f459e9a8 - languageName: node - linkType: hard - "flatted@npm:^3.1.0": version: 3.2.6 resolution: "flatted@npm:3.2.6" @@ -4646,6 +4694,7 @@ __metadata: "@babel/plugin-proposal-optional-chaining": ^7.18.6 "@babel/preset-env": ^7.18.6 "@babel/preset-typescript": ^7.18.6 + "@fastify/websocket": ^7.0.0 "@rollup/plugin-typescript": ^8.3.3 "@semantic-release/changelog": ^6.0.1 "@semantic-release/git": ^10.0.1 @@ -4657,7 +4706,7 @@ __metadata: eslint: ^8.18.0 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 - fastify: ^3.29.0 + fastify: ^4.4.0 fastify-websocket: 4.2.2 glob: ^8.0.3 graphql: ^16.5.0 @@ -6076,15 +6125,14 @@ __metadata: languageName: node linkType: hard -"light-my-request@npm:^4.2.0": - version: 4.10.1 - resolution: "light-my-request@npm:4.10.1" +"light-my-request@npm:^5.0.0": + version: 5.4.0 + resolution: "light-my-request@npm:5.4.0" dependencies: - ajv: ^8.1.0 cookie: ^0.5.0 - process-warning: ^1.0.0 + process-warning: ^2.0.0 set-cookie-parser: ^2.4.1 - checksum: 137856232489f0198f4da72fcc6c83db9ed8d653b8c00c30f9e518a58bd67259e429894657153ec667c5ce2064206ea8ea5b6be76a9ada9376a054f2de51748f + checksum: cc87a63136668498fcbc84f094f950c427c7d2c0d3ecf5b97074c9992ff061b8f5f6ede3bbf2b86fa69730612345119509ca3bb2ea77b20986f9c252f2793559 languageName: node linkType: hard @@ -6924,6 +6972,13 @@ __metadata: languageName: node linkType: hard +"on-exit-leak-free@npm:^2.1.0": + version: 2.1.0 + resolution: "on-exit-leak-free@npm:2.1.0" + checksum: 7334d98b87b0c89c9b69c747760b21196ff35afdedc4eaf1a0a3a02964463d7f6802481b120e4c8298967c74773ca7b914ab2eb3d9b279010eb7f67ac4960eed + languageName: node + linkType: hard + "once@npm:^1.3.0, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -7228,27 +7283,41 @@ __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: 77e29675b116e42ae9fe6d4ef52ef3a082ffc54922b122d85935f93ddcc20277f0b0c873c5c6c5274a67b0409c672aaae3de6bcea10a2d84699718dda55ba95b +"pino-abstract-transport@npm:v1.0.0": + version: 1.0.0 + resolution: "pino-abstract-transport@npm:1.0.0" + dependencies: + readable-stream: ^4.0.0 + split2: ^4.0.0 + checksum: 05dd0eda52dd99fd204b39fe7b62656744b63e863bc052cdd5105d25f226a236966d0a46e39a1ace4838f6e988c608837ff946d2d0bc92835ca7baa0a3bff8d8 + languageName: node + linkType: hard + +"pino-std-serializers@npm:^6.0.0": + version: 6.0.0 + resolution: "pino-std-serializers@npm:6.0.0" + checksum: d9dc1779b3870cdbe00dc2dff15e3931eb126bb144bc9f746d83a2c1174a28e366ed0abe63379dee2fee474e6018a088bfbb2c4b57c1e206601918f5a61e276f languageName: node linkType: hard -"pino@npm:^6.13.0": - version: 6.14.0 - resolution: "pino@npm:6.14.0" +"pino@npm:^8.0.0": + version: 8.4.0 + resolution: "pino@npm:8.4.0" dependencies: - fast-redact: ^3.0.0 - fast-safe-stringify: ^2.0.8 - flatstr: ^1.0.12 - pino-std-serializers: ^3.1.0 - process-warning: ^1.0.0 + atomic-sleep: ^1.0.0 + fast-redact: ^3.1.1 + on-exit-leak-free: ^2.1.0 + pino-abstract-transport: v1.0.0 + pino-std-serializers: ^6.0.0 + process-warning: ^2.0.0 quick-format-unescaped: ^4.0.3 - sonic-boom: ^1.0.2 + real-require: ^0.2.0 + safe-stable-stringify: ^2.3.1 + sonic-boom: ^3.1.0 + thread-stream: ^2.0.0 bin: pino: bin.js - checksum: eb13e12e3a3d682abe4a4da426455a9f4e041e55e4fa57d72d9677ee8d188a9c952f69347e728a3761c8262cdce76ef24bee29e1a53ab15aa9c5e851099163d0 + checksum: ccb5d3d83ae9b9fc17b6a8f44a0204cbb57036322cc7ed6cfaba2d1cebacbeaadebf7c9c047bc0b8136654698ca7f2b6671243f5151998a8f5e0bb431e7a2476 languageName: node linkType: hard @@ -7336,10 +7405,10 @@ __metadata: languageName: node linkType: hard -"process-warning@npm:^1.0.0": - version: 1.0.0 - resolution: "process-warning@npm:1.0.0" - checksum: c708a03241deec3cabaeee39c4f9ee8c4d71f1c5ef9b746c8252cdb952a6059068cfcdaf348399775244cbc441b6ae5e26a9c87ed371f88335d84f26d19180f9 +"process-warning@npm:^2.0.0": + version: 2.0.0 + resolution: "process-warning@npm:2.0.0" + checksum: a2bb299835bced58e63cbe06a8fd6e048a648d3649e81b62c442b63112a3f0a86912e7b1a9c557daca30652232d3b0a7f1972fb87c36334e2a5a6f3d5c4a76c9 languageName: node linkType: hard @@ -7433,7 +7502,7 @@ __metadata: languageName: node linkType: hard -"queue-microtask@npm:^1.1.2, queue-microtask@npm:^1.2.2": +"queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" checksum: b676f8c040cdc5b12723ad2f91414d267605b26419d5c821ff03befa817ddd10e238d22b25d604920340fd73efd8ba795465a0377c4adf45a4a41e4234e42dc4 @@ -7571,6 +7640,15 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.0.0": + version: 4.1.0 + resolution: "readable-stream@npm:4.1.0" + dependencies: + abort-controller: ^3.0.0 + checksum: ff2bb513af6fb43618c8360211b5b9052e25a59e6626d3669c7ba060d021dfffa43c43832e11b18acd6aac15b057c6deae1c41004c1731688c95c455ad02f982 + languageName: node + linkType: hard + "readdir-scoped-modules@npm:^1.1.0": version: 1.1.0 resolution: "readdir-scoped-modules@npm:1.1.0" @@ -7583,6 +7661,13 @@ __metadata: languageName: node linkType: hard +"real-require@npm:^0.2.0": + version: 0.2.0 + resolution: "real-require@npm:0.2.0" + checksum: fa060f19f2f447adf678d1376928c76379dce5f72bd334da301685ca6cdcb7b11356813332cc243c88470796bc2e2b1e2917fc10df9143dd93c2ea608694971d + languageName: node + linkType: hard + "redent@npm:^3.0.0": version: 3.0.0 resolution: "redent@npm:3.0.0" @@ -7791,7 +7876,7 @@ __metadata: languageName: node linkType: hard -"rfdc@npm:^1.1.4, rfdc@npm:^1.2.0": +"rfdc@npm:^1.2.0, rfdc@npm:^1.3.0": version: 1.3.0 resolution: "rfdc@npm:1.3.0" checksum: fb2ba8512e43519983b4c61bd3fa77c0f410eff6bae68b08614437bc3f35f91362215f7b4a73cbda6f67330b5746ce07db5dd9850ad3edc91271ad6deea0df32 @@ -7869,6 +7954,13 @@ __metadata: languageName: node linkType: hard +"safe-stable-stringify@npm:^2.3.1": + version: 2.3.1 + resolution: "safe-stable-stringify@npm:2.3.1" + checksum: a0a0bad0294c3e2a9d1bf3cf2b1096dfb83c162d09a5e4891e488cce082120bd69161d2a92aae7fc48255290f17700decae9c89a07fe139794e61b5c8b411377 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -7885,10 +7977,10 @@ __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: efaafcaa08a4646ca829b29168474f57fb289a0ca7a1d77b66b55a0292785bc6eb9143b21cfc50b37dd12a823c25b12aa1771f18314ed5a616a1f8f12a318533 +"secure-json-parse@npm:^2.4.0": + version: 2.5.0 + resolution: "secure-json-parse@npm:2.5.0" + checksum: 84147a32615ce0d93d2fbba60cde85ae362f45cc948ea134e4d6d1e678bb4b7f3a5ce9b9692ed052baefeb2e1c8ba183b34920390e6a089925b97b0d8f7ab064 languageName: node linkType: hard @@ -7946,13 +8038,6 @@ __metadata: languageName: node linkType: hard -"semver-store@npm:^0.3.0": - version: 0.3.0 - resolution: "semver-store@npm:0.3.0" - checksum: b38f747123e850191526a912657c653c7e5963d164a8daf99e52aa30bc8c5bdac176dc6dab714e17a1a8489ac138c18ff7161b1961f1882888bce637990442dd - languageName: node - linkType: hard - "semver@npm:2 || 3 || 4 || 5": version: 5.7.1 resolution: "semver@npm:5.7.1" @@ -8101,13 +8186,12 @@ __metadata: languageName: node linkType: hard -"sonic-boom@npm:^1.0.2": - version: 1.4.1 - resolution: "sonic-boom@npm:1.4.1" +"sonic-boom@npm:^3.1.0": + version: 3.2.0 + resolution: "sonic-boom@npm:3.2.0" dependencies: atomic-sleep: ^1.0.0 - flatstr: ^1.0.12 - checksum: 189fa8fe5c2dc05d3513fc1a4926a2f16f132fa6fa0b511745a436010cdcd9c1d3b3cb6a9d7c05bd32a965dc77673a5ac0eb0992e920bdedd16330d95323124f + checksum: 526669b78e0ac3bcbe2a53e5ac8960d3b25e61d8e6a46eaed5a0c46d7212c5f638bb136236870babedfcb626063711ba8f81e538f88b79e6a90a5b2ff71943b4 languageName: node linkType: hard @@ -8188,6 +8272,13 @@ __metadata: languageName: node linkType: hard +"split2@npm:^4.0.0": + version: 4.1.0 + resolution: "split2@npm:4.1.0" + checksum: ec581597cb74c13cdfb5e2047543dd40cb1e8e9803c7b1e0c29ede05f2b4f049b2d6e7f2788a225d544549375719658b8f38e9366364dec35dc7a12edfda5ee5 + languageName: node + linkType: hard + "split2@npm:~1.0.0": version: 1.0.0 resolution: "split2@npm:1.0.0" @@ -8251,13 +8342,6 @@ __metadata: languageName: node linkType: hard -"string-similarity@npm:^4.0.1": - version: 4.0.4 - resolution: "string-similarity@npm:4.0.4" - checksum: 797b41b24e1eb6b3b0ab896950b58c295a19a82933479c75f7b5279ffb63e0b456a8c8d10329c02f607ca1a50370e961e83d552aa468ff3b0fa15809abc9eff7 - languageName: node - linkType: hard - "string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -8496,6 +8580,15 @@ __metadata: languageName: node linkType: hard +"thread-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "thread-stream@npm:2.0.1" + dependencies: + real-require: ^0.2.0 + checksum: a78c904d4b22d7b59f774da380b04333e0c7d30505e320eda4bfa4c10e946229f16df6aedb13c7608415cb334657c4a4c6d5d45bf621f6ddb97d3a0141345989 + languageName: node + linkType: hard + "throat@npm:^6.0.1": version: 6.0.1 resolution: "throat@npm:6.0.1" @@ -8529,7 +8622,7 @@ __metadata: languageName: node linkType: hard -"tiny-lru@npm:^8.0.1": +"tiny-lru@npm:^8.0.2": version: 8.0.2 resolution: "tiny-lru@npm:8.0.2" checksum: ec4d884914626760eef05cd57850f21a153adeeb7c4242eb8d44a031f1bd8489f18c1bf5d6f10f0a11c5dcfe03b302f26b00f2b879b38853599486bf0dca8c97