@zimic/ws 0.1.0-canary.0

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.
package/package.json ADDED
@@ -0,0 +1,114 @@
1
+ {
2
+ "name": "@zimic/ws",
3
+ "description": "Next-gen TypeScript-first WebSocket client and server",
4
+ "keywords": [
5
+ "zimic",
6
+ "typescript",
7
+ "types",
8
+ "typegen",
9
+ "realtime",
10
+ "validation",
11
+ "inference",
12
+ "websocket",
13
+ "ws",
14
+ "api",
15
+ "static"
16
+ ],
17
+ "version": "0.1.0-canary.0",
18
+ "homepage": "https://zimic.dev/docs/ws",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/zimicjs/zimic.git",
22
+ "directory": "packages/zimic-ws"
23
+ },
24
+ "author": {
25
+ "name": "Diego Aquino",
26
+ "url": "https://github.com/diego-aquino"
27
+ },
28
+ "funding": {
29
+ "type": "github",
30
+ "url": "https://github.com/sponsors/zimicjs"
31
+ },
32
+ "private": false,
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "engines": {
37
+ "node": ">=20.0.0"
38
+ },
39
+ "license": "MIT",
40
+ "files": [
41
+ "package.json",
42
+ "README.md",
43
+ "LICENSE.md",
44
+ "src",
45
+ "!src/**/tests",
46
+ "!src/**/__tests__",
47
+ "!src/**/*.test.ts",
48
+ "dist",
49
+ "index.d.ts",
50
+ "server.d.ts"
51
+ ],
52
+ "sideEffects": false,
53
+ "main": "./dist/index.js",
54
+ "module": "./dist/index.mjs",
55
+ "types": "index.d.ts",
56
+ "exports": {
57
+ ".": {
58
+ "types": "./index.d.ts",
59
+ "import": "./dist/index.mjs",
60
+ "require": "./dist/index.js",
61
+ "default": "./dist/index.js"
62
+ },
63
+ "./server": {
64
+ "types": "./server.d.ts",
65
+ "import": "./dist/server.mjs",
66
+ "require": "./dist/server.js",
67
+ "default": "./dist/server.js"
68
+ },
69
+ "./package.json": "./package.json"
70
+ },
71
+ "scripts": {
72
+ "dev": "tsup --watch",
73
+ "build": "tsup",
74
+ "lint": "eslint --cache --no-error-on-unmatched-pattern --no-warn-ignored --fix",
75
+ "lint:turbo": "pnpm lint . --max-warnings 0",
76
+ "style": "prettier --log-level warn --ignore-unknown --no-error-on-unmatched-pattern --cache",
77
+ "style:check": "pnpm style --check",
78
+ "style:format": "pnpm style --write",
79
+ "test": "dotenv -v NODE_ENV=test -v FORCE_COLOR=1 -- vitest",
80
+ "test:turbo": "dotenv -v CI=true -- pnpm run test run --coverage",
81
+ "types:check": "tsc --noEmit",
82
+ "deps:setup-playwright": "playwright install chromium",
83
+ "deps:setup": "pnpm deps:setup-playwright"
84
+ },
85
+ "dependencies": {
86
+ "ws": "8.18.3"
87
+ },
88
+ "devDependencies": {
89
+ "@types/node": "^24.10.0",
90
+ "@types/ws": "^8.18.1",
91
+ "@vitest/browser": "^4.0.6",
92
+ "@vitest/browser-playwright": "^4.0.6",
93
+ "@vitest/coverage-istanbul": "^4.0.6",
94
+ "@zimic/eslint-config-node": "workspace:*",
95
+ "@zimic/interceptor": "workspace:*",
96
+ "@zimic/lint-staged-config": "workspace:*",
97
+ "@zimic/tsconfig": "workspace:*",
98
+ "@zimic/utils": "workspace:*",
99
+ "dotenv-cli": "^11.0.0",
100
+ "eslint": "^9.39.1",
101
+ "playwright": "^1.56.1",
102
+ "tsup": "^8.4.0",
103
+ "typescript": "^5.9.3",
104
+ "vitest": "^4.0.6"
105
+ },
106
+ "peerDependencies": {
107
+ "typescript": ">=5.0.0"
108
+ },
109
+ "peerDependenciesMeta": {
110
+ "typescript": {
111
+ "optional": true
112
+ }
113
+ }
114
+ }
package/server.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './dist/server';
@@ -0,0 +1,7 @@
1
+ import NodeWebSocket from 'ws';
2
+
3
+ // WebSocket is available natively in browsers and Node.js >=22.
4
+ const isNativeWebSocketAvailable = typeof WebSocket !== 'undefined';
5
+
6
+ export const ClientSocket = (isNativeWebSocketAvailable ? WebSocket : NodeWebSocket) as typeof WebSocket;
7
+ export type ClientSocket = InstanceType<typeof ClientSocket>;
@@ -0,0 +1,228 @@
1
+ import { WebSocketEvent, WebSocketEventType, WebSocketMessageData, WebSocketSchema } from '@/types/schema';
2
+
3
+ import { ClientSocket } from './ClientSocket';
4
+ import { closeClientSocket, openClientSocket } from './utils/lifecycle';
5
+
6
+ export type WebSocketClientEventListener<Schema extends WebSocketSchema, Type extends WebSocketEventType<Schema>> = (
7
+ this: WebSocketClient<Schema>,
8
+ event: WebSocketEvent<Schema, Type>,
9
+ ) => unknown;
10
+
11
+ type WebSocketClientRawEventListener = (this: ClientSocket, event: Event) => unknown;
12
+
13
+ class WebSocketClient<Schema extends WebSocketSchema> implements Omit<
14
+ WebSocket,
15
+ `${string}EventListener` | `on${string}`
16
+ > {
17
+ private socket?: ClientSocket;
18
+ private _binaryType: BinaryType = 'blob';
19
+
20
+ private _onopen: WebSocketClientEventListener<Schema, 'open'> | null = null;
21
+ private _onmessage: WebSocketClientEventListener<Schema, 'message'> | null = null;
22
+ private _onclose: WebSocketClientEventListener<Schema, 'close'> | null = null;
23
+ private _onerror: WebSocketClientEventListener<Schema, 'error'> | null = null;
24
+
25
+ private listeners: {
26
+ [Type in WebSocketEventType<Schema>]: Map<
27
+ WebSocketClientEventListener<Schema, Type>,
28
+ WebSocketClientRawEventListener
29
+ >;
30
+ } = {
31
+ open: new Map(),
32
+ message: new Map(),
33
+ close: new Map(),
34
+ error: new Map(),
35
+ };
36
+
37
+ constructor(
38
+ private _url: string,
39
+ private protocols?: string | string[],
40
+ ) {}
41
+
42
+ static CONNECTING = ClientSocket.CONNECTING;
43
+
44
+ get CONNECTING() {
45
+ return WebSocketClient.CONNECTING;
46
+ }
47
+
48
+ static OPEN = ClientSocket.OPEN;
49
+
50
+ get OPEN() {
51
+ return WebSocketClient.OPEN;
52
+ }
53
+
54
+ static CLOSING = ClientSocket.CLOSING;
55
+
56
+ get CLOSING() {
57
+ return WebSocketClient.CLOSING;
58
+ }
59
+
60
+ static CLOSED = ClientSocket.CLOSED;
61
+
62
+ get CLOSED() {
63
+ return WebSocketClient.CLOSED;
64
+ }
65
+
66
+ get binaryType() {
67
+ return this.socket?.binaryType ?? this._binaryType;
68
+ }
69
+
70
+ set binaryType(value: 'blob' | 'arraybuffer') {
71
+ this._binaryType = value;
72
+
73
+ if (this.socket) {
74
+ this.socket.binaryType = value;
75
+ }
76
+ }
77
+
78
+ get url() {
79
+ return this.socket?.url ?? this._url;
80
+ }
81
+
82
+ get protocol() {
83
+ return this.socket?.protocol ?? '';
84
+ }
85
+
86
+ get extensions() {
87
+ return this.socket?.extensions ?? '';
88
+ }
89
+
90
+ get readyState() {
91
+ return this.socket?.readyState ?? ClientSocket.CLOSED;
92
+ }
93
+
94
+ get bufferedAmount() {
95
+ return this.socket?.bufferedAmount ?? 0;
96
+ }
97
+
98
+ async open(options: { timeout?: number } = {}) {
99
+ this.socket = new ClientSocket(this._url, this.protocols);
100
+
101
+ if (this.socket.binaryType !== this._binaryType) {
102
+ this.socket.binaryType = this._binaryType;
103
+ }
104
+
105
+ await openClientSocket(this.socket, options);
106
+ }
107
+
108
+ async close(code?: number, reason?: string, options: { timeout?: number } = {}) {
109
+ if (!this.socket) {
110
+ return;
111
+ }
112
+
113
+ try {
114
+ await closeClientSocket(this.socket, { ...options, code, reason });
115
+ } finally {
116
+ this.onopen = null;
117
+ this.onmessage = null;
118
+ this.onclose = null;
119
+ this.onerror = null;
120
+
121
+ for (const [type, listenerToRawListener] of Object.entries(this.listeners)) {
122
+ for (const rawListener of listenerToRawListener.values()) {
123
+ this.socket.removeEventListener(type, rawListener);
124
+ }
125
+
126
+ listenerToRawListener.clear();
127
+ }
128
+
129
+ this.socket = undefined;
130
+ }
131
+ }
132
+
133
+ send(data: WebSocketMessageData<Schema>) {
134
+ this.socket?.send(data);
135
+ }
136
+
137
+ addEventListener<Type extends WebSocketEventType<Schema>>(
138
+ type: Type,
139
+ listener: (this: WebSocketClient<Schema>, event: WebSocketEvent<Schema, Type>) => unknown,
140
+ options?: boolean | AddEventListenerOptions,
141
+ ) {
142
+ const rawListener = listener.bind(this) as WebSocketClientRawEventListener;
143
+
144
+ this.socket?.addEventListener(type, rawListener, options);
145
+ this.listeners[type].set(listener, rawListener);
146
+ }
147
+
148
+ removeEventListener<Type extends WebSocketEventType<Schema>>(
149
+ type: Type,
150
+ listener: (this: WebSocketClient<Schema>, event: WebSocketEvent<Schema, Type>) => unknown,
151
+ options?: boolean | EventListenerOptions,
152
+ ) {
153
+ const rawListener = this.listeners[type].get(listener);
154
+
155
+ if (rawListener) {
156
+ this.socket?.removeEventListener(type, rawListener, options);
157
+ this.listeners[type].delete(listener);
158
+ }
159
+ }
160
+
161
+ get onopen() {
162
+ return this._onopen;
163
+ }
164
+
165
+ set onopen(listener: WebSocketClientEventListener<Schema, 'open'> | null) {
166
+ this.setUnitaryEventListener('open', listener);
167
+ }
168
+
169
+ get onmessage() {
170
+ return this._onmessage;
171
+ }
172
+
173
+ set onmessage(listener: WebSocketClientEventListener<Schema, 'message'> | null) {
174
+ this.setUnitaryEventListener('message', listener);
175
+ }
176
+
177
+ get onclose() {
178
+ return this._onclose;
179
+ }
180
+
181
+ set onclose(listener: WebSocketClientEventListener<Schema, 'close'> | null) {
182
+ this.setUnitaryEventListener('close', listener);
183
+ }
184
+
185
+ get onerror() {
186
+ return this._onerror;
187
+ }
188
+
189
+ set onerror(listener: WebSocketClientEventListener<Schema, 'error'> | null) {
190
+ this.setUnitaryEventListener('error', listener);
191
+ }
192
+
193
+ private setUnitaryEventListener<Type extends WebSocketEventType<Schema>>(
194
+ type: Type,
195
+ listener: WebSocketClientEventListener<Schema, Type> | null,
196
+ ) {
197
+ type PrivateUnitaryListener = typeof listener;
198
+
199
+ if (listener) {
200
+ const rawListener = listener.bind(this) as WebSocketClientRawEventListener;
201
+ this.listeners[type].set(listener, rawListener);
202
+
203
+ if (this.socket) {
204
+ this.socket[`on${type}`] = rawListener;
205
+ }
206
+
207
+ (this[`_on${type}`] as PrivateUnitaryListener) = listener;
208
+ } else {
209
+ const currentListener = this[`_on${type}`] as PrivateUnitaryListener;
210
+
211
+ if (currentListener) {
212
+ this.listeners[type].delete(currentListener);
213
+ }
214
+
215
+ if (this.socket) {
216
+ this.socket[`on${type}`] = null;
217
+ }
218
+
219
+ (this[`_on${type}`] as PrivateUnitaryListener) = null;
220
+ }
221
+ }
222
+
223
+ dispatchEvent<Type extends WebSocketEventType<Schema>>(event: WebSocketEvent<Schema, Type>) {
224
+ return this.socket?.dispatchEvent(event) ?? false;
225
+ }
226
+ }
227
+
228
+ export default WebSocketClient;
@@ -0,0 +1,89 @@
1
+ import { WebSocketCloseTimeoutError } from '@/errors/WebSocketCloseTimeoutError';
2
+ import { WebSocketOpenTimeoutError } from '@/errors/WebSocketOpenTimeoutError';
3
+
4
+ import { ClientSocket } from '../ClientSocket';
5
+
6
+ export async function openClientSocket(socket: ClientSocket, options: { timeout?: number } = {}) {
7
+ const { timeout: timeoutDuration } = options;
8
+
9
+ const isAlreadyOpen = socket.readyState === socket.OPEN;
10
+
11
+ if (isAlreadyOpen) {
12
+ return;
13
+ }
14
+
15
+ await new Promise<void>((resolve, reject) => {
16
+ function removeAllSocketListeners() {
17
+ socket.removeEventListener('open', handleOpenSuccess); // eslint-disable-line @typescript-eslint/no-use-before-define
18
+ socket.removeEventListener('error', handleError); // eslint-disable-line @typescript-eslint/no-use-before-define
19
+ socket.removeEventListener('close', handleError); // eslint-disable-line @typescript-eslint/no-use-before-define
20
+ }
21
+
22
+ function handleError(error: unknown) {
23
+ removeAllSocketListeners();
24
+ reject(error);
25
+ }
26
+
27
+ const openTimeout =
28
+ timeoutDuration === undefined
29
+ ? undefined
30
+ : setTimeout(() => {
31
+ const timeoutError = new WebSocketOpenTimeoutError(timeoutDuration);
32
+ handleError(timeoutError);
33
+ }, timeoutDuration);
34
+
35
+ function handleOpenSuccess() {
36
+ removeAllSocketListeners();
37
+ clearTimeout(openTimeout);
38
+ resolve();
39
+ }
40
+
41
+ socket.addEventListener('open', handleOpenSuccess);
42
+ socket.addEventListener('error', handleError);
43
+ socket.addEventListener('close', handleError);
44
+ });
45
+ }
46
+
47
+ export async function closeClientSocket(
48
+ socket: ClientSocket,
49
+ options: { code?: number; reason?: string; timeout?: number } = {},
50
+ ) {
51
+ const { timeout: timeoutDuration } = options;
52
+
53
+ const isAlreadyClosed = socket.readyState === socket.CLOSED;
54
+
55
+ if (isAlreadyClosed) {
56
+ return;
57
+ }
58
+
59
+ await new Promise<void>((resolve, reject) => {
60
+ function removeAllSocketListeners() {
61
+ socket.removeEventListener('error', handleError); // eslint-disable-line @typescript-eslint/no-use-before-define
62
+ socket.removeEventListener('close', handleClose); // eslint-disable-line @typescript-eslint/no-use-before-define
63
+ }
64
+
65
+ function handleError(error: unknown) {
66
+ removeAllSocketListeners();
67
+ reject(error);
68
+ }
69
+
70
+ const closeTimeout =
71
+ timeoutDuration === undefined
72
+ ? undefined
73
+ : setTimeout(() => {
74
+ const timeoutError = new WebSocketCloseTimeoutError(timeoutDuration);
75
+ handleError(timeoutError);
76
+ }, timeoutDuration);
77
+
78
+ function handleClose() {
79
+ removeAllSocketListeners();
80
+ clearTimeout(closeTimeout);
81
+ resolve();
82
+ }
83
+
84
+ socket.addEventListener('error', handleError);
85
+ socket.addEventListener('close', handleClose);
86
+
87
+ socket.close(options.code, options.reason);
88
+ });
89
+ }
@@ -0,0 +1,8 @@
1
+ import { WebSocketTimeoutError } from './WebSocketTimeoutError';
2
+
3
+ export class WebSocketCloseTimeoutError extends WebSocketTimeoutError {
4
+ constructor(reachedTimeout: number) {
5
+ super(`Web socket close timed out after ${reachedTimeout}ms.`);
6
+ this.name = 'WebSocketCloseTimeoutError';
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ import { WebSocketTimeoutError } from './WebSocketTimeoutError';
2
+
3
+ export class WebSocketMessageAbortError extends WebSocketTimeoutError {
4
+ constructor() {
5
+ super('Web socket message was aborted.');
6
+ this.name = 'WebSocketMessageAbortError';
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ import { WebSocketTimeoutError } from './WebSocketTimeoutError';
2
+
3
+ export class WebSocketOpenTimeoutError extends WebSocketTimeoutError {
4
+ constructor(reachedTimeout: number) {
5
+ super(`Web socket open timed out after ${reachedTimeout}ms.`);
6
+ this.name = 'WebSocketOpenTimeoutError';
7
+ }
8
+ }
@@ -0,0 +1,6 @@
1
+ export class WebSocketTimeoutError extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+ this.name = 'WebSocketTimeoutError';
5
+ }
6
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { WebSocketCloseTimeoutError } from './errors/WebSocketCloseTimeoutError';
2
+ export { WebSocketMessageAbortError } from './errors/WebSocketMessageAbortError';
3
+ export { WebSocketOpenTimeoutError } from './errors/WebSocketOpenTimeoutError';
4
+ export { WebSocketTimeoutError } from './errors/WebSocketTimeoutError';
5
+
6
+ export { default as WebSocketClient } from './client/WebSocketClient';
@@ -0,0 +1 @@
1
+ export { WebSocketServer as ServerSocket } from 'ws';
@@ -0,0 +1,45 @@
1
+ import { Server as HttpServer } from 'http';
2
+ import { Server as HttpsServer } from 'https';
3
+
4
+ import { ServerSocket } from './ServerSocket';
5
+ import { closeServerSocket, openServerSocket } from './utils/lifecycle';
6
+
7
+ export interface WebSocketServerOptions {
8
+ server: HttpServer | HttpsServer;
9
+ }
10
+
11
+ class WebSocketServer {
12
+ private server: HttpServer | HttpsServer;
13
+ private socket: ServerSocket;
14
+ private isSocketOpen: boolean;
15
+
16
+ constructor(options: WebSocketServerOptions) {
17
+ this.server = options.server;
18
+ this.socket = new ServerSocket({ server: options.server });
19
+ this.isSocketOpen = options.server.listening;
20
+ }
21
+
22
+ get isRunning() {
23
+ return this.server.listening && this.isSocketOpen;
24
+ }
25
+
26
+ async start(options: { timeout?: number } = {}) {
27
+ if (this.isSocketOpen) {
28
+ return;
29
+ }
30
+
31
+ await openServerSocket(this.server, this.socket, options);
32
+ this.isSocketOpen = true;
33
+ }
34
+
35
+ async stop(options: { timeout?: number } = {}) {
36
+ if (!this.isSocketOpen) {
37
+ return;
38
+ }
39
+
40
+ await closeServerSocket(this.server, this.socket, options);
41
+ this.isSocketOpen = false;
42
+ }
43
+ }
44
+
45
+ export default WebSocketServer;
@@ -0,0 +1,2 @@
1
+ export { default as WebSocketServer } from './WebSocketServer';
2
+ export type { WebSocketServerOptions } from './WebSocketServer';
@@ -0,0 +1,81 @@
1
+ import { Server as HttpServer } from 'http';
2
+ import { Server as HttpsServer } from 'https';
3
+
4
+ import { WebSocketCloseTimeoutError } from '@/errors/WebSocketCloseTimeoutError';
5
+ import { WebSocketOpenTimeoutError } from '@/errors/WebSocketOpenTimeoutError';
6
+
7
+ import { ServerSocket } from '../ServerSocket';
8
+
9
+ export async function openServerSocket(
10
+ server: HttpServer | HttpsServer,
11
+ socket: ServerSocket,
12
+ options: { timeout?: number } = {},
13
+ ) {
14
+ const { timeout: timeoutDuration } = options;
15
+
16
+ const isAlreadyOpen = server.listening;
17
+
18
+ if (isAlreadyOpen) {
19
+ return;
20
+ }
21
+
22
+ await new Promise<void>((resolve, reject) => {
23
+ const openTimeout =
24
+ timeoutDuration === undefined
25
+ ? undefined
26
+ : setTimeout(() => {
27
+ const timeoutError = new WebSocketOpenTimeoutError(timeoutDuration);
28
+ reject(timeoutError);
29
+ }, timeoutDuration);
30
+
31
+ socket.once('listening', () => {
32
+ clearTimeout(openTimeout);
33
+ resolve();
34
+ });
35
+
36
+ socket.once('error', (error) => {
37
+ clearTimeout(openTimeout);
38
+ reject(error);
39
+ });
40
+ });
41
+ }
42
+
43
+ export async function closeServerSocket(
44
+ server: HttpServer | HttpsServer,
45
+ socket: ServerSocket,
46
+ options: { timeout?: number } = {},
47
+ ) {
48
+ const { timeout: timeoutDuration } = options;
49
+
50
+ const isAlreadyClosed = !server.listening;
51
+
52
+ if (isAlreadyClosed) {
53
+ return;
54
+ }
55
+
56
+ await new Promise<void>((resolve, reject) => {
57
+ const closeTimeout =
58
+ timeoutDuration === undefined
59
+ ? undefined
60
+ : setTimeout(() => {
61
+ const timeoutError = new WebSocketCloseTimeoutError(timeoutDuration);
62
+ reject(timeoutError);
63
+ }, timeoutDuration);
64
+
65
+ for (const client of socket.clients) {
66
+ client.close();
67
+ }
68
+
69
+ socket.close((error) => {
70
+ clearTimeout(closeTimeout);
71
+
72
+ /* istanbul ignore if -- @preserve
73
+ * This is not expected since the server is not stopped unless it is running. */
74
+ if (error) {
75
+ reject(error);
76
+ } else {
77
+ resolve();
78
+ }
79
+ });
80
+ });
81
+ }
@@ -0,0 +1,8 @@
1
+ import { JSONStringified as InternalJSONStringified } from '@zimic/utils/types/json';
2
+
3
+ /**
4
+ * Represents a value stringified by `JSON.stringify`, maintaining a type reference to the original type.
5
+ *
6
+ * This type is used to validate that the expected stringified body is passed in WebSocket messages.
7
+ */
8
+ export type JSONStringified<Value> = InternalJSONStringified<Value>;
@@ -0,0 +1,35 @@
1
+ import { Branded } from '@zimic/utils/types';
2
+ import { JSONValue } from '@zimic/utils/types/json';
3
+
4
+ import { JSONStringified } from './json';
5
+
6
+ type BaseWebSocketSchema = JSONValue | string | Blob | ArrayBufferLike | ArrayBufferView;
7
+
8
+ export type WebSocketSchema<Schema extends BaseWebSocketSchema = BaseWebSocketSchema> = Branded<
9
+ Schema,
10
+ 'WebSocketSchema'
11
+ >;
12
+
13
+ export type WebSocketMessageData<Schema extends WebSocketSchema> = Schema extends Blob
14
+ ? Schema
15
+ : Schema extends ArrayBufferLike
16
+ ? Schema
17
+ : Schema extends ArrayBufferView
18
+ ? Schema
19
+ : Schema extends string
20
+ ? Schema
21
+ : JSONStringified<Schema>;
22
+
23
+ export interface WebSocketEventMap<Schema extends WebSocketSchema> {
24
+ open: Event;
25
+ message: MessageEvent<WebSocketMessageData<Schema>>;
26
+ close: CloseEvent;
27
+ error: Event;
28
+ }
29
+
30
+ export type WebSocketEventType<Schema extends WebSocketSchema = WebSocketSchema> = keyof WebSocketEventMap<Schema>;
31
+
32
+ export type WebSocketEvent<
33
+ Schema extends WebSocketSchema = WebSocketSchema,
34
+ Type extends WebSocketEventType = WebSocketEventType,
35
+ > = WebSocketEventMap<Schema>[Type];