@rlanz/socket 0.0.1-5 → 0.0.1-7

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.
Files changed (64) hide show
  1. package/README.md +174 -160
  2. package/build/{types-C7Q36ryo.d.ts → base_channel-Dr40l7CW.d.ts} +178 -169
  3. package/build/chunk-2X6WGCH5.js +120 -0
  4. package/build/chunk-2X6WGCH5.js.map +1 -0
  5. package/build/chunk-6PJ4ALJD.js +115 -0
  6. package/build/chunk-6PJ4ALJD.js.map +1 -0
  7. package/build/chunk-E6ILCRL3.js +182 -0
  8. package/build/chunk-E6ILCRL3.js.map +1 -0
  9. package/build/{chunk-SHH6U4CI.js → chunk-G75J233Z.js} +1 -1
  10. package/build/chunk-G75J233Z.js.map +1 -0
  11. package/build/{chunk-B4Y3TNDI.js → chunk-IUQ3GYZZ.js} +5 -24
  12. package/build/chunk-IUQ3GYZZ.js.map +1 -0
  13. package/build/chunk-QNOOQXYY.js +899 -0
  14. package/build/chunk-QNOOQXYY.js.map +1 -0
  15. package/build/{chunk-ANT2E3LT.js → chunk-YVC7IMDN.js} +21 -5
  16. package/build/chunk-YVC7IMDN.js.map +1 -0
  17. package/build/framework-B-4kGmAw.d.ts +13 -0
  18. package/build/index-Fg-tkHhP.d.ts +150 -0
  19. package/build/index.d.ts +23 -12
  20. package/build/index.js +12 -14
  21. package/build/index.js.map +1 -1
  22. package/build/providers/socket_provider.d.ts +2 -2
  23. package/build/providers/socket_provider.js +474 -215
  24. package/build/providers/socket_provider.js.map +1 -1
  25. package/build/services/socket.d.ts +2 -2
  26. package/build/{socket_service-mybqv1hj.d.ts → socket_service-BYEB8LIN.d.ts} +7 -7
  27. package/build/src/assembler_hook.d.ts +6 -4
  28. package/build/src/assembler_hook.js +49 -63
  29. package/build/src/assembler_hook.js.map +1 -1
  30. package/build/src/client/index.d.ts +3 -147
  31. package/build/src/client/index.js +4 -837
  32. package/build/src/client/index.js.map +1 -1
  33. package/build/src/client/react.d.ts +13 -5
  34. package/build/src/client/react.js +27 -11
  35. package/build/src/client/react.js.map +1 -1
  36. package/build/src/client/types.d.ts +30 -35
  37. package/build/src/client/vue.d.ts +17 -5
  38. package/build/src/client/vue.js +31 -13
  39. package/build/src/client/vue.js.map +1 -1
  40. package/build/src/decorators.d.ts +3 -2
  41. package/build/src/decorators.js +1 -1
  42. package/build/src/health_check.d.ts +2 -2
  43. package/build/src/health_check.js +14 -3
  44. package/build/src/health_check.js.map +1 -1
  45. package/build/src/otel.js +0 -1
  46. package/build/src/otel.js.map +1 -1
  47. package/build/src/testing.d.ts +3 -12
  48. package/build/src/testing.js +1 -1
  49. package/build/src/types/tracing_channels.d.ts +1 -2
  50. package/build/src/types.d.ts +1 -1
  51. package/package.json +2 -9
  52. package/build/chunk-ANT2E3LT.js.map +0 -1
  53. package/build/chunk-B4Y3TNDI.js.map +0 -1
  54. package/build/chunk-HK7Z65DA.js +0 -19
  55. package/build/chunk-HK7Z65DA.js.map +0 -1
  56. package/build/chunk-SFAY2ZA4.js +0 -28
  57. package/build/chunk-SFAY2ZA4.js.map +0 -1
  58. package/build/chunk-SHH6U4CI.js.map +0 -1
  59. package/build/chunk-YAX5EHHB.js +0 -453
  60. package/build/chunk-YAX5EHHB.js.map +0 -1
  61. package/build/framework-CvxgHwbv.d.ts +0 -12
  62. package/build/src/client/svelte.d.ts +0 -22
  63. package/build/src/client/svelte.js +0 -82
  64. package/build/src/client/svelte.js.map +0 -1
@@ -72,161 +72,6 @@ declare class PresenceManager {
72
72
  getLocalSockets(channel: string): PresenceSocket[];
73
73
  }
74
74
 
75
- interface ChannelBroadcaster {
76
- to(channel: string): {
77
- emit(event: string, data: unknown): void;
78
- except(socketId: string): {
79
- emit(event: string, data: unknown): void;
80
- };
81
- };
82
- }
83
- /**
84
- * Error whose message may intentionally be returned to a socket client.
85
- */
86
- declare class SocketResponseError extends Error {
87
- }
88
- /**
89
- * Abstract class used to define a channel.
90
- *
91
- * @example
92
- * ```ts
93
- * export default class ChatChannel extends BaseChannel {
94
- * static pattern = 'chat/:roomId'
95
- * static middlewares = [
96
- * async (ctx, next) => {
97
- * // Authorize access to the channel through middleware.
98
- * // if (!Room.isMember(ctx.params.roomId, ctx.socket.user.id)) throw new Error('Forbidden')
99
- * await next()
100
- * },
101
- * ]
102
- * }
103
- * ```
104
- *
105
- * @example With presence and decorated handlers
106
- * ```ts
107
- * import { onMessage } from '@rlanz/socket/decorators'
108
- *
109
- * export default class ChatChannel extends BaseChannel {
110
- * static pattern = 'chat/:roomId'
111
- * static options = { presence: true }
112
- *
113
- * static middlewares = [
114
- * async (ctx, next) => {
115
- * if (!ctx.socket.user.isVerified) throw new Error('Not verified')
116
- * await next()
117
- * }
118
- * ]
119
- *
120
- * @onMessage('chat:send')
121
- * async handleSend(socket: AuthenticatedSocket<User>, data: unknown) {
122
- * // Handle chat:send event
123
- * }
124
- *
125
- * @onMessage('chat:typing')
126
- * async handleTyping(socket: AuthenticatedSocket<User>, data: unknown) {
127
- * // Handle chat:typing event
128
- * }
129
- * }
130
- * ```
131
- */
132
- declare class BaseChannel<User = unknown, ServerEvents = unknown> {
133
- /** Type-only server event declarations consumed by generated browser client types. */
134
- readonly $serverEvents: ServerEvents;
135
- /**
136
- * URL-like channel pattern, for example: 'chat/:roomId'.
137
- */
138
- static pattern: string;
139
- /**
140
- * Channel options.
141
- */
142
- static options?: ChannelOptions;
143
- /**
144
- * Middlewares to run before subscription.
145
- */
146
- static middlewares?: Middleware<any>[];
147
- /**
148
- * Event handler map.
149
- * Ex: { 'chat:send': 'handleSend' }
150
- */
151
- protected handlers: ChannelHandlerMap<User>;
152
- /**
153
- * Injected socket service.
154
- */
155
- protected socket: ChannelBroadcaster;
156
- /**
157
- * Full channel name, for example: 'chat/general'.
158
- */
159
- protected channelName: string;
160
- /**
161
- * Parameters extracted from the pattern, for example: { roomId: 'general' }.
162
- */
163
- protected params: Record<string, string>;
164
- /**
165
- * Injected presence manager when presence is enabled.
166
- */
167
- protected presence: PresenceManager;
168
- /**
169
- * Injects dependencies.
170
- */
171
- $setContext(socket: ChannelBroadcaster, channelName: string, params: Record<string, string>, presence?: PresenceManager): void;
172
- /**
173
- * Emits to every channel member.
174
- */
175
- protected broadcast<K extends unknown extends ServerEvents ? string : keyof ServerEvents & string>(event: K, data: unknown extends ServerEvents ? unknown : ServerEvents[K & keyof ServerEvents]): void;
176
- /**
177
- * Emits to every channel member except one socket.
178
- */
179
- protected broadcastExcept<K extends unknown extends ServerEvents ? string : keyof ServerEvents & string>(socketId: string, event: K, data: unknown extends ServerEvents ? unknown : ServerEvents[K & keyof ServerEvents]): void;
180
- /**
181
- * Runs the middleware chain.
182
- *
183
- * middleware[0] -> middleware[1] -> ... -> subscribe
184
- */
185
- $runMiddlewares(ctx: MiddlewareContext<User>): Promise<void>;
186
- /**
187
- * Returns member information for presence.
188
- * Implement when options.presence = true.
189
- */
190
- getPresenceInfo?(socket: AuthenticatedSocket<User>): PresenceInfo;
191
- /**
192
- * Hook called when a socket joins the channel.
193
- */
194
- onJoin?(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void>;
195
- /**
196
- * Hook called when a socket leaves the channel.
197
- */
198
- onLeave?(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void>;
199
- /**
200
- * Hook called when a message is received on the channel.
201
- * Used as fallback when no event handler matches.
202
- */
203
- onMessage?(socket: AuthenticatedSocket<User>, event: string, data: unknown): Promise<void>;
204
- /**
205
- * Hook called when a member joins a presence channel.
206
- */
207
- onMemberJoin?(socket: AuthenticatedSocket<User>, member: PresenceMember, ...params: string[]): Promise<void>;
208
- /**
209
- * Hook called when a member leaves a presence channel.
210
- */
211
- onMemberLeave?(socket: AuthenticatedSocket<User>, member: PresenceMember, ...params: string[]): Promise<void>;
212
- /**
213
- * Dispatches a message to the matching handler.
214
- * Called by SocketService. Do not override.
215
- */
216
- $handleMessage(socket: AuthenticatedSocket<User>, event: string, data: unknown): Promise<unknown>;
217
- /**
218
- * Runs the generic join hook. ChannelSubscriptions owns subscription lifecycle.
219
- */
220
- $handleJoin(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void>;
221
- /**
222
- * Runs the generic leave hook. ChannelSubscriptions owns subscription lifecycle.
223
- */
224
- $handleLeave(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void>;
225
- $getPresenceInfo(socket: AuthenticatedSocket<User>): PresenceInfo;
226
- }
227
- /** Constructor for any BaseChannel specialization. */
228
- type BaseChannelConstructor = typeof BaseChannel<any, any>;
229
-
230
75
  /**
231
76
  * Duration can be specified as milliseconds or as a human-readable string.
232
77
  */
@@ -239,6 +84,19 @@ interface SocketTransportConfig {
239
84
  * How long to wait for other instances to answer a presence snapshot request.
240
85
  */
241
86
  presenceTimeout?: Duration;
87
+ /**
88
+ * In-memory retry policy for transport publications that fail.
89
+ *
90
+ * The queue is enabled and limited to 1000 distinct messages by default.
91
+ * Set maxSize to null only when an unbounded queue is explicitly desired.
92
+ */
93
+ retryQueue?: SocketRetryQueueConfig;
94
+ }
95
+ interface SocketRetryQueueConfig {
96
+ enabled?: boolean;
97
+ removeDuplicates?: boolean;
98
+ maxSize?: number | null;
99
+ retryInterval?: Duration | false;
242
100
  }
243
101
 
244
102
  /**
@@ -281,17 +139,13 @@ interface AuthenticatedSocket<User = unknown> {
281
139
  */
282
140
  user: User | undefined;
283
141
  /**
284
- * Emits an event to this socket.
142
+ * Returns the authenticated user or throws a client-safe unauthorized error.
285
143
  */
286
- emit(event: string, data: unknown): void;
287
- /**
288
- * Joins the user target used by SocketService.toUser().
289
- */
290
- joinUserRoom(userId: string | number): Promise<void>;
144
+ getUserOrFail(): User;
291
145
  /**
292
- * Leaves the current SocketService.toUser() target.
146
+ * Emits an event to this socket.
293
147
  */
294
- leaveUserRoom(): Promise<void>;
148
+ emit(event: string, data: unknown): void;
295
149
  /**
296
150
  * Disconnects the socket.
297
151
  */
@@ -310,6 +164,8 @@ interface SocketUpgradeContext {
310
164
  }
311
165
  type SocketAuthenticationResult<User = unknown> = User | false | null;
312
166
  type SocketAuthenticationHandler<User = unknown> = (ctx: SocketUpgradeContext) => SocketAuthenticationResult<User> | Promise<SocketAuthenticationResult<User>>;
167
+ type SocketOriginValues = boolean | string | string[];
168
+ type SocketOriginResolver = (origin: string, httpContext: HttpContext) => SocketOriginValues;
313
169
  /**
314
170
  * AdonisJS HTTP middleware executed for the WebSocket upgrade request.
315
171
  */
@@ -373,8 +229,6 @@ type Middleware<User = unknown> = MiddlewareFn<User> | MiddlewareClass<User>;
373
229
  type ChannelServerEventsOf<T> = T extends {
374
230
  readonly $serverEvents: infer Events;
375
231
  } ? Events : unknown;
376
- type ChannelHandlerFn<User = unknown> = (socket: AuthenticatedSocket<User>, data: any) => unknown | Promise<unknown>;
377
- type ChannelHandlerMap<User = unknown> = Record<string, ChannelHandlerFn<User>>;
378
232
  /**
379
233
  * Package configuration.
380
234
  */
@@ -382,10 +236,14 @@ interface SocketConfig<User = unknown> {
382
236
  websocket?: {
383
237
  path?: string;
384
238
  /**
385
- * Additional browser origins allowed to open a WebSocket connection.
386
- * Same-origin requests and clients without an Origin header are allowed by default.
239
+ * Browser origin policy for opening a WebSocket connection.
240
+ *
241
+ * Uses the same value shapes as the AdonisJS CORS `origin` option. A resolver
242
+ * receives the request origin and HTTP context and may return any static value.
243
+ * Same-origin requests are allowed by default when this option is not defined.
244
+ * Clients without an Origin header are always allowed.
387
245
  */
388
- allowedOrigins?: string[];
246
+ origin?: SocketOriginValues | SocketOriginResolver;
389
247
  /**
390
248
  * HTTP middleware executed only for the WebSocket upgrade request.
391
249
  *
@@ -473,4 +331,155 @@ interface SocketEvents<User = unknown> {
473
331
  };
474
332
  }
475
333
 
476
- export { type AuthenticatedSocket as A, BaseChannel as B, type ChannelHandlerMap as C, type Duration as D, type Middleware as M, PresenceManager as P, type RawSocket as R, type SocketConfig as S, type ChannelServerEventsOf as a, type ChannelAck as b, type ChannelMessage as c, type SocketAuthenticationHandler as d, type BaseChannelConstructor as e, type ChannelHandlerFn as f, type ChannelMatch as g, type ChannelOptions as h, type MiddlewareClass as i, type MiddlewareContext as j, type MiddlewareFn as k, type PresenceInfo as l, type PresenceMember as m, type SocketAuthenticationResult as n, type SocketEvents as o, type SocketHealthSnapshot as p, type SocketHttpMiddleware as q, SocketResponseError as r, type SocketServiceStatus as s, type SocketUpgradeContext as t, type PresenceData as u };
334
+ interface ChannelBroadcaster {
335
+ to(channel: string): {
336
+ emit(event: string, data: unknown): void;
337
+ except(socketId: string): {
338
+ emit(event: string, data: unknown): void;
339
+ };
340
+ };
341
+ }
342
+ /**
343
+ * Error whose message may intentionally be returned to a socket client.
344
+ */
345
+ declare class SocketResponseError extends Error {
346
+ }
347
+ /**
348
+ * Abstract class used to define a channel.
349
+ *
350
+ * @example
351
+ * ```ts
352
+ * export default class ChatChannel extends BaseChannel {
353
+ * static pattern = 'chat/:roomId'
354
+ * static middlewares = [
355
+ * async (ctx, next) => {
356
+ * // Authorize access to the channel through middleware.
357
+ * // if (!Room.isMember(ctx.params.roomId, ctx.socket.user.id)) throw new Error('Forbidden')
358
+ * await next()
359
+ * },
360
+ * ]
361
+ * }
362
+ * ```
363
+ *
364
+ * @example With presence and decorated handlers
365
+ * ```ts
366
+ * import { onMessage } from '@rlanz/socket/decorators'
367
+ *
368
+ * export default class ChatChannel extends BaseChannel {
369
+ * static pattern = 'chat/:roomId'
370
+ * static options = { presence: true }
371
+ *
372
+ * static middlewares = [
373
+ * async (ctx, next) => {
374
+ * if (!ctx.socket.user.isVerified) throw new Error('Not verified')
375
+ * await next()
376
+ * }
377
+ * ]
378
+ *
379
+ * @onMessage('chat:send')
380
+ * async handleSend(socket: AuthenticatedSocket<User>, data: unknown) {
381
+ * // Handle chat:send event
382
+ * }
383
+ *
384
+ * @onMessage('chat:typing')
385
+ * async handleTyping(socket: AuthenticatedSocket<User>, data: unknown) {
386
+ * // Handle chat:typing event
387
+ * }
388
+ * }
389
+ * ```
390
+ */
391
+ declare class BaseChannel<User = unknown, ServerEvents = Record<never, never>> {
392
+ /** Type-only server event declarations consumed by generated browser client types. */
393
+ readonly $serverEvents: ServerEvents;
394
+ /**
395
+ * URL-like channel pattern, for example: 'chat/:roomId'.
396
+ */
397
+ static pattern: string;
398
+ /**
399
+ * Channel options.
400
+ */
401
+ static options?: ChannelOptions;
402
+ /**
403
+ * Middlewares to run before subscription.
404
+ */
405
+ static middlewares?: Middleware<any>[];
406
+ /**
407
+ * Injected socket service.
408
+ */
409
+ protected socket: ChannelBroadcaster;
410
+ /**
411
+ * Full channel name, for example: 'chat/general'.
412
+ */
413
+ protected channelName: string;
414
+ /**
415
+ * Parameters extracted from the pattern, for example: { roomId: 'general' }.
416
+ */
417
+ protected params: Record<string, string>;
418
+ /**
419
+ * Injected presence manager when presence is enabled.
420
+ */
421
+ protected presence: PresenceManager;
422
+ /**
423
+ * Injects dependencies.
424
+ */
425
+ $setContext(socket: ChannelBroadcaster, channelName: string, params: Record<string, string>, presence?: PresenceManager): void;
426
+ /**
427
+ * Emits to every channel member.
428
+ */
429
+ protected broadcast<K extends keyof ServerEvents & string>(event: K, data: ServerEvents[K]): void;
430
+ /**
431
+ * Emits to every channel member except one socket.
432
+ */
433
+ protected broadcastExcept<K extends keyof ServerEvents & string>(socketId: string, event: K, data: ServerEvents[K]): void;
434
+ /**
435
+ * Runs the middleware chain.
436
+ *
437
+ * middleware[0] -> middleware[1] -> ... -> subscribe
438
+ */
439
+ $runMiddlewares(ctx: MiddlewareContext<User>): Promise<void>;
440
+ /**
441
+ * Returns member information for presence.
442
+ * Implement when options.presence = true.
443
+ */
444
+ getPresenceInfo?(socket: AuthenticatedSocket<User>): PresenceInfo;
445
+ /**
446
+ * Hook called when a socket joins the channel.
447
+ */
448
+ onJoin?(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void>;
449
+ /**
450
+ * Hook called when a socket leaves the channel.
451
+ */
452
+ onLeave?(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void>;
453
+ /**
454
+ * Hook called when a member joins a presence channel.
455
+ */
456
+ onMemberJoin?(socket: AuthenticatedSocket<User>, member: PresenceMember, ...params: string[]): Promise<void>;
457
+ /**
458
+ * Hook called when a member leaves a presence channel.
459
+ */
460
+ onMemberLeave?(socket: AuthenticatedSocket<User>, member: PresenceMember, ...params: string[]): Promise<void>;
461
+ /**
462
+ * Dispatches a message to the matching handler.
463
+ * Called by SocketService. Do not override.
464
+ */
465
+ $handleMessage(socket: AuthenticatedSocket<User>, event: string, data: unknown): Promise<unknown>;
466
+ /**
467
+ * Runs the generic join hook. ChannelSubscriptions owns subscription lifecycle.
468
+ */
469
+ $handleJoin(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void>;
470
+ /**
471
+ * Runs the generic leave hook. ChannelSubscriptions owns subscription lifecycle.
472
+ */
473
+ $handleLeave(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void>;
474
+ $getPresenceInfo(socket: AuthenticatedSocket<User>): PresenceInfo;
475
+ }
476
+ /** Constructor for any BaseChannel specialization, including container-injected dependencies. */
477
+ interface BaseChannelConstructor {
478
+ new (...args: any[]): BaseChannel<any, any>;
479
+ readonly name: string;
480
+ pattern: string;
481
+ options?: ChannelOptions;
482
+ middlewares?: Middleware<any>[];
483
+ }
484
+
485
+ export { type AuthenticatedSocket as A, BaseChannel as B, type ChannelServerEventsOf as C, type Duration as D, type Middleware as M, PresenceManager as P, type RawSocket as R, type SocketAuthenticationHandler as S, type ChannelAck as a, type ChannelMessage as b, type SocketConfig as c, type ChannelOptions as d, type MiddlewareClass as e, type MiddlewareContext as f, type MiddlewareFn as g, type PresenceInfo as h, type PresenceMember as i, type SocketAuthenticationResult as j, type SocketEvents as k, type SocketHealthSnapshot as l, type SocketHttpMiddleware as m, type SocketOriginResolver as n, type SocketOriginValues as o, SocketResponseError as p, type SocketRetryQueueConfig as q, type SocketServiceStatus as r, type SocketTransportConfig as s, type SocketUpgradeContext as t, type BaseChannelConstructor as u, type ChannelMatch as v, type PresenceData as w };
@@ -0,0 +1,120 @@
1
+ import {
2
+ getDecoratedChannelHandlers
3
+ } from "./chunk-G75J233Z.js";
4
+
5
+ // src/base_channel.ts
6
+ var SocketResponseError = class extends Error {
7
+ };
8
+ var MissingPresenceInfoError = class extends SocketResponseError {
9
+ constructor() {
10
+ super("Presence channels must implement getPresenceInfo(socket)");
11
+ }
12
+ };
13
+ var BaseChannel = class {
14
+ /**
15
+ * URL-like channel pattern, for example: 'chat/:roomId'.
16
+ */
17
+ static pattern;
18
+ /**
19
+ * Channel options.
20
+ */
21
+ static options;
22
+ /**
23
+ * Middlewares to run before subscription.
24
+ */
25
+ static middlewares;
26
+ /**
27
+ * Injected socket service.
28
+ */
29
+ socket;
30
+ /**
31
+ * Full channel name, for example: 'chat/general'.
32
+ */
33
+ channelName;
34
+ /**
35
+ * Parameters extracted from the pattern, for example: { roomId: 'general' }.
36
+ */
37
+ params;
38
+ /**
39
+ * Injected presence manager when presence is enabled.
40
+ */
41
+ presence;
42
+ /**
43
+ * Injects dependencies.
44
+ */
45
+ $setContext(socket, channelName, params, presence) {
46
+ this.socket = socket;
47
+ this.channelName = channelName;
48
+ this.params = params;
49
+ if (presence) {
50
+ this.presence = presence;
51
+ }
52
+ }
53
+ /**
54
+ * Emits to every channel member.
55
+ */
56
+ broadcast(event, data) {
57
+ this.socket.to(this.channelName).emit(event, data);
58
+ }
59
+ /**
60
+ * Emits to every channel member except one socket.
61
+ */
62
+ broadcastExcept(socketId, event, data) {
63
+ this.socket.to(this.channelName).except(socketId).emit(event, data);
64
+ }
65
+ /**
66
+ * Runs the middleware chain.
67
+ *
68
+ * middleware[0] -> middleware[1] -> ... -> subscribe
69
+ */
70
+ async $runMiddlewares(ctx) {
71
+ const middlewares = this.constructor.middlewares || [];
72
+ let index = 0;
73
+ const next = async () => {
74
+ if (index >= middlewares.length) return;
75
+ const middleware = middlewares[index++];
76
+ if (typeof middleware === "function") {
77
+ await middleware(ctx, next);
78
+ } else {
79
+ await middleware.handle(ctx, next);
80
+ }
81
+ };
82
+ await next();
83
+ }
84
+ /**
85
+ * Dispatches a message to the matching handler.
86
+ * Called by SocketService. Do not override.
87
+ */
88
+ async $handleMessage(socket, event, data) {
89
+ const handlers = getDecoratedChannelHandlers(this);
90
+ const handler = handlers[event];
91
+ if (typeof handler === "function") {
92
+ return Reflect.apply(handler, this, [socket, data]);
93
+ }
94
+ throw new SocketResponseError(`Unknown channel event: ${event}`);
95
+ }
96
+ /**
97
+ * Runs the generic join hook. ChannelSubscriptions owns subscription lifecycle.
98
+ */
99
+ async $handleJoin(socket, ...params) {
100
+ await this.onJoin?.(socket, ...params);
101
+ }
102
+ /**
103
+ * Runs the generic leave hook. ChannelSubscriptions owns subscription lifecycle.
104
+ */
105
+ async $handleLeave(socket, ...params) {
106
+ await this.onLeave?.(socket, ...params);
107
+ }
108
+ $getPresenceInfo(socket) {
109
+ if (!this.getPresenceInfo) {
110
+ throw new MissingPresenceInfoError();
111
+ }
112
+ return this.getPresenceInfo(socket);
113
+ }
114
+ };
115
+
116
+ export {
117
+ SocketResponseError,
118
+ BaseChannel
119
+ };
120
+ //# sourceMappingURL=chunk-2X6WGCH5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/base_channel.ts"],"sourcesContent":["import type {\n AuthenticatedSocket,\n ChannelOptions,\n PresenceInfo,\n PresenceMember,\n Middleware,\n MiddlewareContext,\n} from './types.js'\nimport type { PresenceManager } from './presence_manager.js'\nimport { getDecoratedChannelHandlers } from './decorators.js'\n\ninterface ChannelBroadcaster {\n to(channel: string): {\n emit(event: string, data: unknown): void\n except(socketId: string): {\n emit(event: string, data: unknown): void\n }\n }\n}\n\n/**\n * Error whose message may intentionally be returned to a socket client.\n */\nexport class SocketResponseError extends Error {}\n\nexport class MissingPresenceInfoError extends SocketResponseError {\n constructor() {\n super('Presence channels must implement getPresenceInfo(socket)')\n }\n}\n\n/**\n * Abstract class used to define a channel.\n *\n * @example\n * ```ts\n * export default class ChatChannel extends BaseChannel {\n * static pattern = 'chat/:roomId'\n * static middlewares = [\n * async (ctx, next) => {\n * // Authorize access to the channel through middleware.\n * // if (!Room.isMember(ctx.params.roomId, ctx.socket.user.id)) throw new Error('Forbidden')\n * await next()\n * },\n * ]\n * }\n * ```\n *\n * @example With presence and decorated handlers\n * ```ts\n * import { onMessage } from '@rlanz/socket/decorators'\n *\n * export default class ChatChannel extends BaseChannel {\n * static pattern = 'chat/:roomId'\n * static options = { presence: true }\n *\n * static middlewares = [\n * async (ctx, next) => {\n * if (!ctx.socket.user.isVerified) throw new Error('Not verified')\n * await next()\n * }\n * ]\n *\n * @onMessage('chat:send')\n * async handleSend(socket: AuthenticatedSocket<User>, data: unknown) {\n * // Handle chat:send event\n * }\n *\n * @onMessage('chat:typing')\n * async handleTyping(socket: AuthenticatedSocket<User>, data: unknown) {\n * // Handle chat:typing event\n * }\n * }\n * ```\n */\nexport class BaseChannel<User = unknown, ServerEvents = Record<never, never>> {\n /** Type-only server event declarations consumed by generated browser client types. */\n declare readonly $serverEvents: ServerEvents\n /**\n * URL-like channel pattern, for example: 'chat/:roomId'.\n */\n static pattern: string\n\n /**\n * Channel options.\n */\n static options?: ChannelOptions\n\n /**\n * Middlewares to run before subscription.\n */\n static middlewares?: Middleware<any>[]\n\n /**\n * Injected socket service.\n */\n protected socket!: ChannelBroadcaster\n\n /**\n * Full channel name, for example: 'chat/general'.\n */\n protected channelName!: string\n\n /**\n * Parameters extracted from the pattern, for example: { roomId: 'general' }.\n */\n protected params!: Record<string, string>\n\n /**\n * Injected presence manager when presence is enabled.\n */\n protected presence!: PresenceManager\n\n /**\n * Injects dependencies.\n */\n $setContext(\n socket: ChannelBroadcaster,\n channelName: string,\n params: Record<string, string>,\n presence?: PresenceManager\n ): void {\n this.socket = socket\n this.channelName = channelName\n this.params = params\n if (presence) {\n this.presence = presence\n }\n }\n\n /**\n * Emits to every channel member.\n */\n protected broadcast<K extends keyof ServerEvents & string>(\n event: K,\n data: ServerEvents[K]\n ): void {\n this.socket.to(this.channelName).emit(event, data)\n }\n\n /**\n * Emits to every channel member except one socket.\n */\n protected broadcastExcept<K extends keyof ServerEvents & string>(\n socketId: string,\n event: K,\n data: ServerEvents[K]\n ): void {\n this.socket.to(this.channelName).except(socketId).emit(event, data)\n }\n\n /**\n * Runs the middleware chain.\n *\n * middleware[0] -> middleware[1] -> ... -> subscribe\n */\n async $runMiddlewares(ctx: MiddlewareContext<User>): Promise<void> {\n const middlewares = (this.constructor as typeof BaseChannel).middlewares || []\n\n let index = 0\n\n const next = async (): Promise<void> => {\n if (index >= middlewares.length) return\n\n const middleware = middlewares[index++]\n\n if (typeof middleware === 'function') {\n await middleware(ctx, next)\n } else {\n await middleware.handle(ctx, next)\n }\n }\n\n await next()\n }\n\n /**\n * Returns member information for presence.\n * Implement when options.presence = true.\n */\n getPresenceInfo?(socket: AuthenticatedSocket<User>): PresenceInfo\n\n /**\n * Hook called when a socket joins the channel.\n */\n onJoin?(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void>\n\n /**\n * Hook called when a socket leaves the channel.\n */\n onLeave?(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void>\n\n /**\n * Hook called when a member joins a presence channel.\n */\n onMemberJoin?(\n socket: AuthenticatedSocket<User>,\n member: PresenceMember,\n ...params: string[]\n ): Promise<void>\n\n /**\n * Hook called when a member leaves a presence channel.\n */\n onMemberLeave?(\n socket: AuthenticatedSocket<User>,\n member: PresenceMember,\n ...params: string[]\n ): Promise<void>\n\n /**\n * Dispatches a message to the matching handler.\n * Called by SocketService. Do not override.\n */\n async $handleMessage(\n socket: AuthenticatedSocket<User>,\n event: string,\n data: unknown\n ): Promise<unknown> {\n const handlers = getDecoratedChannelHandlers<User>(this)\n const handler = handlers[event]\n\n if (typeof handler === 'function') {\n return Reflect.apply(handler, this, [socket, data])\n }\n\n throw new SocketResponseError(`Unknown channel event: ${event}`)\n }\n\n /**\n * Runs the generic join hook. ChannelSubscriptions owns subscription lifecycle.\n */\n async $handleJoin(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void> {\n await this.onJoin?.(socket, ...params)\n }\n\n /**\n * Runs the generic leave hook. ChannelSubscriptions owns subscription lifecycle.\n */\n async $handleLeave(socket: AuthenticatedSocket<User>, ...params: string[]): Promise<void> {\n await this.onLeave?.(socket, ...params)\n }\n\n $getPresenceInfo(socket: AuthenticatedSocket<User>): PresenceInfo {\n if (!this.getPresenceInfo) {\n throw new MissingPresenceInfoError()\n }\n\n return this.getPresenceInfo(socket)\n }\n}\n\n/** Constructor for any BaseChannel specialization, including container-injected dependencies. */\nexport interface BaseChannelConstructor {\n new (...args: any[]): BaseChannel<any, any>\n readonly name: string\n pattern: string\n options?: ChannelOptions\n middlewares?: Middleware<any>[]\n}\n"],"mappings":";;;;;AAuBO,IAAM,sBAAN,cAAkC,MAAM;AAAC;AAEzC,IAAM,2BAAN,cAAuC,oBAAoB;AAAA,EAChE,cAAc;AACZ,UAAM,0DAA0D;AAAA,EAClE;AACF;AA8CO,IAAM,cAAN,MAAuE;AAAA;AAAA;AAAA;AAAA,EAM5E,OAAO;AAAA;AAAA;AAAA;AAAA,EAKP,OAAO;AAAA;AAAA;AAAA;AAAA,EAKP,OAAO;AAAA;AAAA;AAAA;AAAA,EAKG;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA,EAKV,YACE,QACA,aACA,QACA,UACM;AACN,SAAK,SAAS;AACd,SAAK,cAAc;AACnB,SAAK,SAAS;AACd,QAAI,UAAU;AACZ,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKU,UACR,OACA,MACM;AACN,SAAK,OAAO,GAAG,KAAK,WAAW,EAAE,KAAK,OAAO,IAAI;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKU,gBACR,UACA,OACA,MACM;AACN,SAAK,OAAO,GAAG,KAAK,WAAW,EAAE,OAAO,QAAQ,EAAE,KAAK,OAAO,IAAI;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,gBAAgB,KAA6C;AACjE,UAAM,cAAe,KAAK,YAAmC,eAAe,CAAC;AAE7E,QAAI,QAAQ;AAEZ,UAAM,OAAO,YAA2B;AACtC,UAAI,SAAS,YAAY,OAAQ;AAEjC,YAAM,aAAa,YAAY,OAAO;AAEtC,UAAI,OAAO,eAAe,YAAY;AACpC,cAAM,WAAW,KAAK,IAAI;AAAA,MAC5B,OAAO;AACL,cAAM,WAAW,OAAO,KAAK,IAAI;AAAA,MACnC;AAAA,IACF;AAEA,UAAM,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAwCA,MAAM,eACJ,QACA,OACA,MACkB;AAClB,UAAM,WAAW,4BAAkC,IAAI;AACvD,UAAM,UAAU,SAAS,KAAK;AAE9B,QAAI,OAAO,YAAY,YAAY;AACjC,aAAO,QAAQ,MAAM,SAAS,MAAM,CAAC,QAAQ,IAAI,CAAC;AAAA,IACpD;AAEA,UAAM,IAAI,oBAAoB,0BAA0B,KAAK,EAAE;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,WAAsC,QAAiC;AACvF,UAAM,KAAK,SAAS,QAAQ,GAAG,MAAM;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,WAAsC,QAAiC;AACxF,UAAM,KAAK,UAAU,QAAQ,GAAG,MAAM;AAAA,EACxC;AAAA,EAEA,iBAAiB,QAAiD;AAChE,QAAI,CAAC,KAAK,iBAAiB;AACzB,YAAM,IAAI,yBAAyB;AAAA,IACrC;AAEA,WAAO,KAAK,gBAAgB,MAAM;AAAA,EACpC;AACF;","names":[]}
@@ -0,0 +1,115 @@
1
+ import {
2
+ ChannelPatternSyntax
3
+ } from "./chunk-E6ILCRL3.js";
4
+
5
+ // src/bindable_channel_pattern.ts
6
+ var BindableChannelPattern = class _BindableChannelPattern extends ChannelPatternSyntax {
7
+ constructor(source) {
8
+ super(source);
9
+ this.source = source;
10
+ const bindingSegments = [];
11
+ const parameters = [];
12
+ const parameterNames = /* @__PURE__ */ new Set();
13
+ const serializedSegments = [];
14
+ let bindable = true;
15
+ if (this.canonicalPattern !== "/" && (this.segments.length === 0 || this.segments.length !== this.canonicalPattern.split("/").length)) {
16
+ bindable = false;
17
+ }
18
+ for (const [index, segment] of this.segments.entries()) {
19
+ const final = index === this.segments.length - 1;
20
+ if (segment.kind === "static") {
21
+ if (segment.value.length === 0 || /[:*?]/.test(segment.value)) {
22
+ bindable = false;
23
+ break;
24
+ }
25
+ bindingSegments.push({ kind: "static", value: segment.value });
26
+ serializedSegments.push(segment.value);
27
+ continue;
28
+ }
29
+ if (segment.kind === "parameter") {
30
+ const unsupportedParameter = segment.suffix !== "" || !/^[^.:?/*]+$/.test(segment.name) || segment.optional && !final || segment.name === "wildcard" || parameterNames.has(segment.name);
31
+ if (unsupportedParameter) {
32
+ bindable = false;
33
+ break;
34
+ }
35
+ parameterNames.add(segment.name);
36
+ parameters.push({ name: segment.name, optional: segment.optional });
37
+ bindingSegments.push({
38
+ kind: "parameter",
39
+ name: segment.name,
40
+ optional: segment.optional,
41
+ wildcard: false
42
+ });
43
+ serializedSegments.push(`:${segment.name}${segment.optional ? "?" : ""}`);
44
+ continue;
45
+ }
46
+ if (!final || segment.name !== "*" || parameterNames.has("wildcard")) {
47
+ bindable = false;
48
+ break;
49
+ }
50
+ parameterNames.add("wildcard");
51
+ parameters.push({ name: "wildcard", optional: false });
52
+ bindingSegments.push({
53
+ kind: "parameter",
54
+ name: "wildcard",
55
+ optional: false,
56
+ wildcard: true
57
+ });
58
+ serializedSegments.push(segment.name);
59
+ }
60
+ if (this.canonicalPattern !== "/" && serializedSegments.join("/") !== this.canonicalPattern) {
61
+ bindable = false;
62
+ }
63
+ this.#bindable = bindable;
64
+ this.parameters = Object.freeze(parameters.map((parameter) => Object.freeze(parameter)));
65
+ this.#bindingSegments = Object.freeze(bindingSegments.map((segment) => Object.freeze(segment)));
66
+ Object.freeze(this);
67
+ }
68
+ parameters;
69
+ #bindable;
70
+ #bindingSegments;
71
+ /**
72
+ * Parse and validate the pattern subset that generated clients can bind.
73
+ */
74
+ static parse(value) {
75
+ const pattern = new _BindableChannelPattern(value);
76
+ return pattern.#bindable ? pattern : null;
77
+ }
78
+ /**
79
+ * Bind all required parameters and return one concrete channel name.
80
+ */
81
+ bind(parameters = {}) {
82
+ if (this.canonicalPattern === "/") {
83
+ return "/";
84
+ }
85
+ const values = [];
86
+ for (const segment of this.#bindingSegments) {
87
+ if (segment.kind === "static") {
88
+ values.push(segment.value);
89
+ continue;
90
+ }
91
+ const value = Reflect.get(parameters, segment.name);
92
+ if (value === void 0 && segment.optional) {
93
+ continue;
94
+ }
95
+ if (value === void 0) {
96
+ const label = segment.wildcard ? "wildcard parameter" : `"${segment.name}" parameter`;
97
+ throw new Error(`Missing ${label} for channel: ${this.source}`);
98
+ }
99
+ if (typeof value !== "string" && typeof value !== "number") {
100
+ throw new Error(`Invalid "${segment.name}" parameter for channel: ${this.source}`);
101
+ }
102
+ const bound = String(value);
103
+ if (bound.length === 0 || !segment.wildcard && bound.includes("/")) {
104
+ throw new Error(`Invalid "${segment.name}" parameter for channel: ${this.source}`);
105
+ }
106
+ values.push(bound);
107
+ }
108
+ return values.join("/") || "/";
109
+ }
110
+ };
111
+
112
+ export {
113
+ BindableChannelPattern
114
+ };
115
+ //# sourceMappingURL=chunk-6PJ4ALJD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bindable_channel_pattern.ts"],"sourcesContent":["import { ChannelPatternSyntax } from './channel_pattern_syntax.js'\n\nexport interface BindableChannelParameter {\n readonly name: string\n readonly optional: boolean\n}\n\ntype BindableChannelSegment =\n | { readonly kind: 'static'; readonly value: string }\n | {\n readonly kind: 'parameter'\n readonly name: string\n readonly optional: boolean\n readonly wildcard: boolean\n }\n\n/**\n * The deliberately small subset of channel patterns that generated clients can bind.\n * Runtime routing keeps using the complete grammar through ChannelPattern.\n */\nexport class BindableChannelPattern extends ChannelPatternSyntax {\n readonly parameters: readonly BindableChannelParameter[]\n readonly #bindable: boolean\n readonly #bindingSegments: readonly BindableChannelSegment[]\n\n private constructor(private readonly source: string) {\n super(source)\n\n const bindingSegments: BindableChannelSegment[] = []\n const parameters: BindableChannelParameter[] = []\n const parameterNames = new Set<string>()\n const serializedSegments: string[] = []\n let bindable = true\n\n // Reject syntax that does not map to one complete segment per slash.\n if (\n this.canonicalPattern !== '/' &&\n (this.segments.length === 0 ||\n this.segments.length !== this.canonicalPattern.split('/').length)\n ) {\n bindable = false\n }\n\n for (const [index, segment] of this.segments.entries()) {\n const final = index === this.segments.length - 1\n\n if (segment.kind === 'static') {\n if (segment.value.length === 0 || /[:*?]/.test(segment.value)) {\n bindable = false\n break\n }\n bindingSegments.push({ kind: 'static', value: segment.value })\n serializedSegments.push(segment.value)\n continue\n }\n\n if (segment.kind === 'parameter') {\n // Generated parameters do not support suffixes or intermediate optional values.\n // The wildcard name is reserved for the final wildcard segment.\n const unsupportedParameter =\n segment.suffix !== '' ||\n !/^[^.:?/*]+$/.test(segment.name) ||\n (segment.optional && !final) ||\n segment.name === 'wildcard' ||\n parameterNames.has(segment.name)\n\n if (unsupportedParameter) {\n bindable = false\n break\n }\n\n parameterNames.add(segment.name)\n parameters.push({ name: segment.name, optional: segment.optional })\n bindingSegments.push({\n kind: 'parameter',\n name: segment.name,\n optional: segment.optional,\n wildcard: false,\n })\n serializedSegments.push(`:${segment.name}${segment.optional ? '?' : ''}`)\n continue\n }\n\n // A generated wildcard must be the final segment and must use the reserved key.\n if (!final || segment.name !== '*' || parameterNames.has('wildcard')) {\n bindable = false\n break\n }\n\n parameterNames.add('wildcard')\n parameters.push({ name: 'wildcard', optional: false })\n bindingSegments.push({\n kind: 'parameter',\n name: 'wildcard',\n optional: false,\n wildcard: true,\n })\n serializedSegments.push(segment.name)\n }\n\n // Reject syntax that the parser cannot reproduce without a change.\n if (this.canonicalPattern !== '/' && serializedSegments.join('/') !== this.canonicalPattern) {\n bindable = false\n }\n\n this.#bindable = bindable\n this.parameters = Object.freeze(parameters.map((parameter) => Object.freeze(parameter)))\n this.#bindingSegments = Object.freeze(bindingSegments.map((segment) => Object.freeze(segment)))\n Object.freeze(this)\n }\n\n /**\n * Parse and validate the pattern subset that generated clients can bind.\n */\n static parse(value: string): BindableChannelPattern | null {\n const pattern = new BindableChannelPattern(value)\n return pattern.#bindable ? pattern : null\n }\n\n /**\n * Bind all required parameters and return one concrete channel name.\n */\n bind(parameters: object = {}): string {\n if (this.canonicalPattern === '/') {\n return '/'\n }\n\n const values: string[] = []\n for (const segment of this.#bindingSegments) {\n if (segment.kind === 'static') {\n values.push(segment.value)\n continue\n }\n\n const value = Reflect.get(parameters, segment.name) as unknown\n\n // Omit only an optional parameter. All other segments need one value.\n if (value === undefined && segment.optional) {\n continue\n }\n if (value === undefined) {\n const label = segment.wildcard ? 'wildcard parameter' : `\"${segment.name}\" parameter`\n throw new Error(`Missing ${label} for channel: ${this.source}`)\n }\n if (typeof value !== 'string' && typeof value !== 'number') {\n throw new Error(`Invalid \"${segment.name}\" parameter for channel: ${this.source}`)\n }\n\n const bound = String(value)\n\n // An ordinary parameter must stay in one segment. A wildcard can contain slashes.\n if (bound.length === 0 || (!segment.wildcard && bound.includes('/'))) {\n throw new Error(`Invalid \"${segment.name}\" parameter for channel: ${this.source}`)\n }\n values.push(bound)\n }\n\n return values.join('/') || '/'\n }\n}\n"],"mappings":"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","names":[]}