@wooksjs/event-ws 0.7.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/LICENSE +21 -0
- package/README.md +46 -0
- package/dist/index.cjs +725 -0
- package/dist/index.d.ts +335 -0
- package/dist/index.mjs +677 -0
- package/package.json +64 -0
- package/scripts/setup-skills.js +78 -0
- package/skills/wooksjs-event-ws/SKILL.md +47 -0
- package/skills/wooksjs-event-ws/composables.md +157 -0
- package/skills/wooksjs-event-ws/core.md +229 -0
- package/skills/wooksjs-event-ws/rooms.md +139 -0
- package/skills/wooksjs-event-ws/testing.md +150 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import * as _wooksjs_event_core from '@wooksjs/event-core';
|
|
2
|
+
import { EventContext, EventContextOptions } from '@wooksjs/event-core';
|
|
3
|
+
export { EventContext, useLogger, useRouteParams } from '@wooksjs/event-core';
|
|
4
|
+
import { TConsoleBase } from '@prostojs/logger';
|
|
5
|
+
import http, { IncomingMessage, Server } from 'http';
|
|
6
|
+
import { Duplex } from 'stream';
|
|
7
|
+
import * as wooks from 'wooks';
|
|
8
|
+
import { WooksAdapterBase, WooksUpgradeHandler, Wooks, TWooksHandler } from 'wooks';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the connection `EventContext` from either connection-level or message-level handlers.
|
|
12
|
+
* Mirrors `current()` from `@wooksjs/event-core`.
|
|
13
|
+
*
|
|
14
|
+
* - In `onConnect` / `onDisconnect`: returns `current()` directly.
|
|
15
|
+
* - In `onMessage`: returns `current().parent` (the connection context).
|
|
16
|
+
*/
|
|
17
|
+
declare function currentConnection(ctx?: EventContext): EventContext;
|
|
18
|
+
/**
|
|
19
|
+
* Provides access to the current WebSocket connection (id, send, close).
|
|
20
|
+
* Works in both connection and message contexts via parent chain traversal.
|
|
21
|
+
*/
|
|
22
|
+
declare const useWsConnection: (ctx?: EventContext) => {
|
|
23
|
+
/** Unique connection ID. */
|
|
24
|
+
id: string;
|
|
25
|
+
/** Send a push message to this connection. */
|
|
26
|
+
send(event: string, path: string, data?: unknown, params?: Record<string, string>): void;
|
|
27
|
+
/** Close the connection. */
|
|
28
|
+
close(code?: number, reason?: string): void;
|
|
29
|
+
/** The connection EventContext (for advanced use). */
|
|
30
|
+
context: EventContext;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Provides access to the current WebSocket message data.
|
|
35
|
+
* Only available in message context (inside `onMessage` handlers).
|
|
36
|
+
*/
|
|
37
|
+
declare function useWsMessage<T = unknown>(ctx?: EventContext): WsMessageResult<T>;
|
|
38
|
+
interface WsMessageResult<T> {
|
|
39
|
+
/** Parsed message data (typed via generic). */
|
|
40
|
+
data: T;
|
|
41
|
+
/** Raw message before parsing. */
|
|
42
|
+
raw: Buffer | string;
|
|
43
|
+
/** Correlation ID (undefined if fire-and-forget). */
|
|
44
|
+
id: string | number | undefined;
|
|
45
|
+
/** Message path. */
|
|
46
|
+
path: string;
|
|
47
|
+
/** Message event type. */
|
|
48
|
+
event: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface WsBroadcastOptions {
|
|
52
|
+
/** Room to broadcast to. Default: current message path. */
|
|
53
|
+
room?: string;
|
|
54
|
+
/** Exclude the sender from the broadcast. Default: true. */
|
|
55
|
+
excludeSelf?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Provides room management for the current connection.
|
|
59
|
+
* All methods default to the current message path as the room.
|
|
60
|
+
* Only available in message context (inside `onMessage` handlers).
|
|
61
|
+
*/
|
|
62
|
+
declare const useWsRooms: (ctx?: EventContext) => {
|
|
63
|
+
/** Join a room. Default: current message path. */
|
|
64
|
+
join(room?: string): void;
|
|
65
|
+
/** Leave a room. Default: current message path. */
|
|
66
|
+
leave(room?: string): void;
|
|
67
|
+
/** Broadcast to a room. Default room: current message path. */
|
|
68
|
+
broadcast(event: string, data?: unknown, options?: WsBroadcastOptions): void;
|
|
69
|
+
/** List rooms this connection has joined. */
|
|
70
|
+
rooms(): string[];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Client-to-server message. */
|
|
74
|
+
interface WsClientMessage {
|
|
75
|
+
/** Event type — used as the router method (e.g. "message", "subscribe", "unsubscribe", "rpc"). */
|
|
76
|
+
event: string;
|
|
77
|
+
/** Route path (e.g. "/chat/rooms/lobby"). Always a concrete path, never a pattern. */
|
|
78
|
+
path: string;
|
|
79
|
+
/** Payload. */
|
|
80
|
+
data?: unknown;
|
|
81
|
+
/** Optional correlation ID. When present, the server sends a reply with the same ID. */
|
|
82
|
+
id?: string | number;
|
|
83
|
+
}
|
|
84
|
+
/** Server-to-client reply (sent only when client message included an `id`). */
|
|
85
|
+
interface WsReplyMessage {
|
|
86
|
+
/** Correlation ID matching the client's request. */
|
|
87
|
+
id: string | number;
|
|
88
|
+
/** Response payload (handler return value). */
|
|
89
|
+
data?: unknown;
|
|
90
|
+
/** Error (mutually exclusive with data). */
|
|
91
|
+
error?: {
|
|
92
|
+
code: number;
|
|
93
|
+
message: string;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Server-to-client push (broadcast, direct send, subscription notification). */
|
|
97
|
+
interface WsPushMessage {
|
|
98
|
+
/** Event type. */
|
|
99
|
+
event: string;
|
|
100
|
+
/** The concrete path this message relates to. */
|
|
101
|
+
path: string;
|
|
102
|
+
/** Route params extracted from the path by the server router. */
|
|
103
|
+
params?: Record<string, string>;
|
|
104
|
+
/** Payload. */
|
|
105
|
+
data?: unknown;
|
|
106
|
+
}
|
|
107
|
+
/** Minimal WebSocket instance interface (compatible with ws, uWebSockets.js, Bun). */
|
|
108
|
+
interface WsSocket {
|
|
109
|
+
send(data: string | Buffer): void;
|
|
110
|
+
close(code?: number, reason?: string): void;
|
|
111
|
+
on(event: 'message', handler: (data: Buffer | string) => void): void;
|
|
112
|
+
on(event: 'close', handler: (code: number, reason: Buffer) => void): void;
|
|
113
|
+
on(event: 'error', handler: (err: Error) => void): void;
|
|
114
|
+
on(event: 'pong', handler: () => void): void;
|
|
115
|
+
ping(): void;
|
|
116
|
+
readonly readyState: number;
|
|
117
|
+
}
|
|
118
|
+
/** WebSocket server instance (handles upgrades, tracks no connections itself). */
|
|
119
|
+
interface WsServerInstance {
|
|
120
|
+
handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer, cb: (ws: WsSocket) => void): void;
|
|
121
|
+
close(): void;
|
|
122
|
+
}
|
|
123
|
+
/** Factory that creates a WebSocket server in noServer mode. */
|
|
124
|
+
interface WsServerAdapter {
|
|
125
|
+
create(): WsServerInstance;
|
|
126
|
+
}
|
|
127
|
+
/** Pluggable transport for cross-instance broadcasting (e.g. Redis pub/sub). */
|
|
128
|
+
interface WsBroadcastTransport {
|
|
129
|
+
/** Publish a message to a channel (other instances will receive it). */
|
|
130
|
+
publish(channel: string, payload: string): void | Promise<void>;
|
|
131
|
+
/** Subscribe to messages on a channel. */
|
|
132
|
+
subscribe(channel: string, handler: (payload: string) => void): void | Promise<void>;
|
|
133
|
+
/** Unsubscribe from a channel. */
|
|
134
|
+
unsubscribe(channel: string): void | Promise<void>;
|
|
135
|
+
}
|
|
136
|
+
interface TWooksWsOptions {
|
|
137
|
+
/** Heartbeat ping interval in ms (default: 30000). Set 0 to disable. */
|
|
138
|
+
heartbeatInterval?: number;
|
|
139
|
+
/** Heartbeat pong timeout in ms (default: 5000). Connection closed if pong not received. */
|
|
140
|
+
heartbeatTimeout?: number;
|
|
141
|
+
/** Custom message parser. Default: JSON.parse expecting WsClientMessage shape. */
|
|
142
|
+
messageParser?: (raw: Buffer | string) => WsClientMessage;
|
|
143
|
+
/** Custom message serializer. Default: JSON.stringify. */
|
|
144
|
+
messageSerializer?: (msg: WsReplyMessage | WsPushMessage) => string | Buffer;
|
|
145
|
+
/** Logger instance. */
|
|
146
|
+
logger?: TConsoleBase;
|
|
147
|
+
/** Max message size in bytes (default: 1MB). Messages exceeding this are silently dropped. */
|
|
148
|
+
maxMessageSize?: number;
|
|
149
|
+
/** Custom WsServerAdapter. Default: wraps the `ws` package. */
|
|
150
|
+
wsServerAdapter?: WsServerAdapter;
|
|
151
|
+
/** Broadcast transport for multi-instance deployments. Default: local only. */
|
|
152
|
+
broadcastTransport?: WsBroadcastTransport;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Internal class representing a connected WebSocket client. */
|
|
156
|
+
declare class WsConnection {
|
|
157
|
+
readonly id: string;
|
|
158
|
+
readonly ws: WsSocket;
|
|
159
|
+
readonly ctx: EventContext;
|
|
160
|
+
private readonly serializer;
|
|
161
|
+
readonly rooms: Set<string>;
|
|
162
|
+
alive: boolean;
|
|
163
|
+
constructor(id: string, ws: WsSocket, ctx: EventContext, serializer: (msg: WsReplyMessage | WsPushMessage) => string | Buffer);
|
|
164
|
+
/** Send a push message to this connection. */
|
|
165
|
+
send(event: string, path: string, data?: unknown, params?: Record<string, string>): void;
|
|
166
|
+
/** Send a reply to a client request. */
|
|
167
|
+
reply(id: string | number, data?: unknown): void;
|
|
168
|
+
/** Send an error reply to a client request. */
|
|
169
|
+
replyError(id: string | number, code: number, message: string): void;
|
|
170
|
+
/** Close the connection. */
|
|
171
|
+
close(code?: number, reason?: string): void;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Provides server-wide operations. Available in any context.
|
|
176
|
+
* Not a `defineWook` — reads directly from the adapter state.
|
|
177
|
+
*/
|
|
178
|
+
declare function useWsServer(): {
|
|
179
|
+
/** All active connections. */
|
|
180
|
+
connections(): Map<string, WsConnection>;
|
|
181
|
+
/** Broadcast to ALL connections (not room-scoped). */
|
|
182
|
+
broadcast(event: string, path: string, data?: unknown, params?: Record<string, string>): void;
|
|
183
|
+
/** Get a specific connection by ID. */
|
|
184
|
+
getConnection(id: string): WsConnection | undefined;
|
|
185
|
+
/** Get all connections in a room. */
|
|
186
|
+
roomConnections(room: string): Set<WsConnection>;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/** WebSocket adapter for Wooks. Implements WooksUpgradeHandler for HTTP integration. */
|
|
190
|
+
declare class WooksWs extends WooksAdapterBase implements WooksUpgradeHandler {
|
|
191
|
+
protected logger: TConsoleBase;
|
|
192
|
+
protected eventContextOptions: EventContextOptions;
|
|
193
|
+
private readonly connections;
|
|
194
|
+
private readonly roomManager;
|
|
195
|
+
private readonly wsServer;
|
|
196
|
+
private readonly opts;
|
|
197
|
+
private readonly serializer;
|
|
198
|
+
private readonly parser;
|
|
199
|
+
private onConnectHandler?;
|
|
200
|
+
private onDisconnectHandler?;
|
|
201
|
+
private heartbeatTimer?;
|
|
202
|
+
private server?;
|
|
203
|
+
readonly reqKey: _wooksjs_event_core.Key<http.IncomingMessage>;
|
|
204
|
+
readonly socketKey: _wooksjs_event_core.Key<Duplex>;
|
|
205
|
+
readonly headKey: _wooksjs_event_core.Key<Buffer<ArrayBufferLike>>;
|
|
206
|
+
constructor(wooksOrOpts?: Wooks | WooksAdapterBase | TWooksWsOptions, opts?: TWooksWsOptions);
|
|
207
|
+
/** Register a handler that runs when a new WebSocket connection is established. */
|
|
208
|
+
onConnect(handler: TWooksHandler): void;
|
|
209
|
+
/** Register a handler that runs when a WebSocket connection closes. */
|
|
210
|
+
onDisconnect(handler: TWooksHandler): void;
|
|
211
|
+
/** Register a routed message handler. Uses the standard Wooks router internally. */
|
|
212
|
+
onMessage<ResType = unknown, ParamsType = Record<string, string | string[]>>(event: string, path: string, handler: TWooksHandler<ResType>): wooks.TProstoRouterPathHandle<ParamsType>;
|
|
213
|
+
/**
|
|
214
|
+
* Complete the WebSocket handshake from inside an UPGRADE route handler.
|
|
215
|
+
* Reads req/socket/head from the current HTTP context (set by the HTTP adapter).
|
|
216
|
+
* The HTTP context becomes the parent of the WS connection context.
|
|
217
|
+
*/
|
|
218
|
+
upgrade(): void;
|
|
219
|
+
/**
|
|
220
|
+
* Fallback: called by the HTTP adapter when no UPGRADE route matches.
|
|
221
|
+
* Also used internally for standalone mode.
|
|
222
|
+
*/
|
|
223
|
+
handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): void;
|
|
224
|
+
/** Start a standalone server (without event-http). */
|
|
225
|
+
listen(port: number, hostname?: string): Promise<void>;
|
|
226
|
+
/** Stop the server and clean up. */
|
|
227
|
+
close(): void;
|
|
228
|
+
/** Returns the underlying HTTP server (if any). */
|
|
229
|
+
getServer(): Server | undefined;
|
|
230
|
+
private doUpgrade;
|
|
231
|
+
private acceptConnection;
|
|
232
|
+
private rejectConnection;
|
|
233
|
+
private handleMessage;
|
|
234
|
+
private processHandlers;
|
|
235
|
+
private processAsyncResult;
|
|
236
|
+
private sendReply;
|
|
237
|
+
private handleHandlerError;
|
|
238
|
+
private handleClose;
|
|
239
|
+
private startHeartbeat;
|
|
240
|
+
private stopHeartbeat;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Creates a new WooksWs WebSocket adapter.
|
|
244
|
+
*
|
|
245
|
+
* @example Integrated with HTTP (recommended):
|
|
246
|
+
* ```ts
|
|
247
|
+
* const http = createHttpApp()
|
|
248
|
+
* const ws = createWsApp(http) // auto-registers upgrade contract
|
|
249
|
+
* http.upgrade('/ws', () => ws.upgrade())
|
|
250
|
+
* http.listen(3000)
|
|
251
|
+
* ```
|
|
252
|
+
*
|
|
253
|
+
* @example Standalone:
|
|
254
|
+
* ```ts
|
|
255
|
+
* const ws = createWsApp({ heartbeatInterval: 30_000 })
|
|
256
|
+
* ws.listen(3000)
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
declare function createWsApp(wooksOrOpts?: Wooks | WooksAdapterBase | TWooksWsOptions, opts?: TWooksWsOptions): WooksWs;
|
|
260
|
+
|
|
261
|
+
/** Event kind for WebSocket connections (long-lived, one per client). */
|
|
262
|
+
declare const wsConnectionKind: _wooksjs_event_core.EventKind<{
|
|
263
|
+
id: _wooksjs_event_core.SlotMarker<string>;
|
|
264
|
+
ws: _wooksjs_event_core.SlotMarker<WsSocket>;
|
|
265
|
+
}>;
|
|
266
|
+
/** Event kind for WebSocket messages (short-lived, one per incoming message). */
|
|
267
|
+
declare const wsMessageKind: _wooksjs_event_core.EventKind<{
|
|
268
|
+
data: _wooksjs_event_core.SlotMarker<unknown>;
|
|
269
|
+
rawMessage: _wooksjs_event_core.SlotMarker<string | Buffer<ArrayBufferLike>>;
|
|
270
|
+
messageId: _wooksjs_event_core.SlotMarker<string | number | undefined>;
|
|
271
|
+
messagePath: _wooksjs_event_core.SlotMarker<string>;
|
|
272
|
+
messageEvent: _wooksjs_event_core.SlotMarker<string>;
|
|
273
|
+
}>;
|
|
274
|
+
|
|
275
|
+
/** WebSocket error with a numeric code following HTTP conventions (401, 403, 404, 500, etc.). */
|
|
276
|
+
declare class WsError extends Error {
|
|
277
|
+
readonly code: number;
|
|
278
|
+
constructor(code: number, message?: string);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Manages room → connections mapping with optional distributed broadcast transport. */
|
|
282
|
+
declare class WsRoomManager {
|
|
283
|
+
private readonly transport?;
|
|
284
|
+
private readonly rooms;
|
|
285
|
+
constructor(transport?: WsBroadcastTransport | undefined);
|
|
286
|
+
/** Add a connection to a room. */
|
|
287
|
+
join(connection: WsConnection, room: string): void;
|
|
288
|
+
/** Remove a connection from a room. */
|
|
289
|
+
leave(connection: WsConnection, room: string): void;
|
|
290
|
+
/** Remove a connection from ALL rooms (called on disconnect). */
|
|
291
|
+
leaveAll(connection: WsConnection): void;
|
|
292
|
+
/** Get all connections in a room. */
|
|
293
|
+
connections(room: string): Set<WsConnection>;
|
|
294
|
+
/** Broadcast to all connections in a room. */
|
|
295
|
+
broadcast(room: string, event: string, path: string, data?: unknown, params?: Record<string, string>, exclude?: WsConnection): void;
|
|
296
|
+
/** Handle inbound message from broadcast transport (other instances). */
|
|
297
|
+
private onTransportMessage;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Options for creating a test WS connection context. */
|
|
301
|
+
interface TTestWsConnectionContext {
|
|
302
|
+
/** Connection ID. Default: 'test-conn-id'. */
|
|
303
|
+
id?: string;
|
|
304
|
+
/** Pre-set route parameters. */
|
|
305
|
+
params?: Record<string, string | string[]>;
|
|
306
|
+
/** Optional parent context (e.g., an HTTP context for testing composable reuse). */
|
|
307
|
+
parentCtx?: EventContext;
|
|
308
|
+
}
|
|
309
|
+
/** Options for creating a test WS message context (includes connection context). */
|
|
310
|
+
interface TTestWsMessageContext extends TTestWsConnectionContext {
|
|
311
|
+
/** Message event type. */
|
|
312
|
+
event: string;
|
|
313
|
+
/** Message path. */
|
|
314
|
+
path: string;
|
|
315
|
+
/** Message data. */
|
|
316
|
+
data?: unknown;
|
|
317
|
+
/** Message correlation ID. */
|
|
318
|
+
messageId?: string | number;
|
|
319
|
+
/** Raw message. */
|
|
320
|
+
rawMessage?: Buffer | string;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Creates a fully initialized WS connection context for testing.
|
|
324
|
+
* Returns a runner function that executes callbacks inside the context scope.
|
|
325
|
+
*/
|
|
326
|
+
declare function prepareTestWsConnectionContext(options?: TTestWsConnectionContext): <T>(cb: (...a: any[]) => T) => T;
|
|
327
|
+
/**
|
|
328
|
+
* Creates a fully initialized WS message context for testing.
|
|
329
|
+
* Sets up both connection context (parent) and message context (child).
|
|
330
|
+
* Returns a runner function that executes callbacks inside the message context scope.
|
|
331
|
+
*/
|
|
332
|
+
declare function prepareTestWsMessageContext(options: TTestWsMessageContext): <T>(cb: (...a: any[]) => T) => T;
|
|
333
|
+
|
|
334
|
+
export { WooksWs, WsConnection, WsError, WsRoomManager, createWsApp, currentConnection, prepareTestWsConnectionContext, prepareTestWsMessageContext, useWsConnection, useWsMessage, useWsRooms, useWsServer, wsConnectionKind, wsMessageKind };
|
|
335
|
+
export type { TTestWsConnectionContext, TTestWsMessageContext, TWooksWsOptions, WsBroadcastOptions, WsBroadcastTransport, WsClientMessage, WsPushMessage, WsReplyMessage, WsServerAdapter, WsServerInstance, WsSocket };
|