@syncar/server 1.0.0-alpha.1

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.
@@ -0,0 +1,3602 @@
1
+ import * as node_https from 'node:https';
2
+ import * as node_http from 'node:http';
3
+ import { IncomingMessage } from 'node:http';
4
+ import WebSocket, { ServerOptions, WebSocketServer } from 'ws';
5
+ import { EventEmitter } from 'node:events';
6
+
7
+ /**
8
+ * Message identifier
9
+ *
10
+ * @remarks
11
+ * Unique identifier for a message. Typically a UUID or similar unique string.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const messageId: MessageId = "550e8400-e29b-41d4-a716-446655440000"
16
+ * ```
17
+ */
18
+ type MessageId = string;
19
+ /**
20
+ * Client identifier (e.g., WebSocket connection ID)
21
+ *
22
+ * @remarks
23
+ * Unique identifier for a connected client. This ID is generated when
24
+ * a client connects and is used to address messages to specific clients.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const clientId: ClientId = "client_123abc"
29
+ * ```
30
+ */
31
+ type ClientId = string;
32
+ /**
33
+ * Subscriber identifier (typically client ID)
34
+ *
35
+ * @remarks
36
+ * Identifier for a subscriber to a channel. In most cases, this is the
37
+ * same as the client ID, but can be different for custom subscription models.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const subscriberId: SubscriberId = "client_123abc"
42
+ * ```
43
+ */
44
+ type SubscriberId = string;
45
+ /**
46
+ * Channel name
47
+ *
48
+ * @remarks
49
+ * String identifier for a channel. Channel names starting with `__` are
50
+ * reserved for internal use (e.g., `__broadcast__`).
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const channelName: ChannelName = "chat"
55
+ * ```
56
+ */
57
+ type ChannelName = string;
58
+ /**
59
+ * Unix timestamp in milliseconds
60
+ *
61
+ * @remarks
62
+ * Standard timestamp format used throughout the server for tracking
63
+ * connection times, message timestamps, and other time-based events.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const timestamp: Timestamp = Date.now()
68
+ * ```
69
+ */
70
+ type Timestamp = number;
71
+ /**
72
+ * Generic data payload for messages
73
+ *
74
+ * @remarks
75
+ * Type alias for the data payload in messages. Can be any type,
76
+ * with `unknown` as the default for type safety.
77
+ *
78
+ * @template T - The type of the data payload (default: unknown)
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * const payload: DataPayload<string> = "Hello world"
83
+ * const complexPayload: DataPayload<{ text: string; user: string }> = {
84
+ * text: "Hello",
85
+ * user: "Alice"
86
+ * }
87
+ * ```
88
+ */
89
+ type DataPayload<T = unknown> = T;
90
+ /**
91
+ * Logger interface
92
+ *
93
+ * @remarks
94
+ * Interface for logging operations. Implementations can write to console,
95
+ * files, or external logging services. All methods accept variadic arguments
96
+ * for flexible logging.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const logger: ILogger = {
101
+ * debug: (msg, ...args) => console.debug('[DEBUG]', msg, ...args),
102
+ * info: (msg, ...args) => console.info('[INFO]', msg, ...args),
103
+ * warn: (msg, ...args) => console.warn('[WARN]', msg, ...args),
104
+ * error: (msg, ...args) => console.error('[ERROR]', msg, ...args),
105
+ * }
106
+ * ```
107
+ *
108
+ * @example
109
+ * ### Using with pino
110
+ * ```ts
111
+ * import pino from 'pino'
112
+ *
113
+ * const logger: ILogger = pino({
114
+ * level: 'info',
115
+ * transport: {
116
+ * target: 'pino-pretty',
117
+ * },
118
+ * })
119
+ * ```
120
+ */
121
+ interface ILogger {
122
+ /**
123
+ * Log a debug message
124
+ *
125
+ * @param message - The log message
126
+ * @param args - Additional arguments to log
127
+ */
128
+ debug(message: string, ...args: unknown[]): void;
129
+ /**
130
+ * Log an info message
131
+ *
132
+ * @param message - The log message
133
+ * @param args - Additional arguments to log
134
+ */
135
+ info(message: string, ...args: unknown[]): void;
136
+ /**
137
+ * Log a warning message
138
+ *
139
+ * @param message - The log message
140
+ * @param args - Additional arguments to log
141
+ */
142
+ warn(message: string, ...args: unknown[]): void;
143
+ /**
144
+ * Log an error message
145
+ *
146
+ * @param message - The log message
147
+ * @param args - Additional arguments to log
148
+ */
149
+ error(message: string, ...args: unknown[]): void;
150
+ }
151
+ /**
152
+ * ID Generator function type
153
+ *
154
+ * @remarks
155
+ * Function type for generating unique client IDs from incoming HTTP requests.
156
+ * Can be synchronous or asynchronous, and can throw to reject connections.
157
+ *
158
+ * @param request - The incoming HTTP upgrade request
159
+ * @returns The generated client ID
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * const generateId: IdGenerator = (request) => {
164
+ * const token = request.headers.authorization?.split(' ')[1]
165
+ * return extractUserIdFromToken(token)
166
+ * }
167
+ * ```
168
+ *
169
+ * @example
170
+ * ### Async ID generator
171
+ * ```ts
172
+ * const generateId: IdGenerator = async (request) => {
173
+ * const token = request.headers.authorization?.split(' ')[1]
174
+ * const user = await verifyJwt(token)
175
+ * return user.id
176
+ * }
177
+ * ```
178
+ */
179
+ type IdGenerator = (request: IncomingMessage) => ClientId | Promise<ClientId>;
180
+ /**
181
+ * Base client connection interface
182
+ *
183
+ * @remarks
184
+ * Represents a connected WebSocket client with metadata about the connection.
185
+ * This interface is used throughout the server for client tracking and management.
186
+ *
187
+ * @property id - Unique client identifier
188
+ * @property connectedAt - Unix timestamp (ms) when the client connected
189
+ * @property lastPingAt - Unix timestamp (ms) of the last ping/pong (optional)
190
+ * @property socket - The raw WebSocket instance
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * const client: IClientConnection = {
195
+ * id: 'client_123abc',
196
+ * connectedAt: 1699123456789,
197
+ * lastPingAt: 1699123459999,
198
+ * socket: wsSocket
199
+ * }
200
+ *
201
+ * console.log(`Client ${client.id} connected at ${new Date(client.connectedAt).toLocaleString()}`)
202
+ * ```
203
+ */
204
+ interface IClientConnection {
205
+ /** Unique client identifier */
206
+ readonly id: ClientId;
207
+ /** Connected timestamp */
208
+ readonly connectedAt: Timestamp;
209
+ /** Last ping timestamp */
210
+ lastPingAt?: Timestamp;
211
+ /** Raw WebSocket instance */
212
+ readonly socket: WebSocket;
213
+ }
214
+ /**
215
+ * Message types for protocol communication
216
+ *
217
+ * @remarks
218
+ * Enum defining all message types supported by the Synca protocol.
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * if (message.type === MessageType.DATA) {
223
+ * // Handle data message
224
+ * } else if (message.type === MessageType.SIGNAL) {
225
+ * // Handle signal message
226
+ * }
227
+ * ```
228
+ *
229
+ * @see {@link SignalType} for signal message subtypes
230
+ */
231
+ declare enum MessageType {
232
+ /** Data message - carries application data */
233
+ DATA = "data",
234
+ /** Signal message - control message for subscriptions, pings, etc. */
235
+ SIGNAL = "signal",
236
+ /** Error message - reports errors to clients */
237
+ ERROR = "error",
238
+ /** Acknowledgment message - confirms message receipt */
239
+ ACK = "ack"
240
+ }
241
+ /**
242
+ * Signal types for control messages
243
+ *
244
+ * @remarks
245
+ * Enum defining all signal types used for protocol control operations.
246
+ *
247
+ * @example
248
+ * ```ts
249
+ * if (message.type === MessageType.SIGNAL) {
250
+ * if (message.signal === SignalType.SUBSCRIBE) {
251
+ * // Handle subscription
252
+ * } else if (message.signal === SignalType.PING) {
253
+ * // Respond with pong
254
+ * }
255
+ * }
256
+ * ```
257
+ */
258
+ declare enum SignalType {
259
+ /** Subscribe to a channel */
260
+ SUBSCRIBE = "subscribe",
261
+ /** Unsubscribe from a channel */
262
+ UNSUBSCRIBE = "unsubscribe",
263
+ /** Ping message for keep-alive */
264
+ PING = "ping",
265
+ /** Pong response to ping */
266
+ PONG = "pong",
267
+ /** Confirmation of successful subscription */
268
+ SUBSCRIBED = "subscribed",
269
+ /** Confirmation of successful unsubscription */
270
+ UNSUBSCRIBED = "unsubscribed"
271
+ }
272
+ /**
273
+ * Standard error codes for messages
274
+ *
275
+ * @remarks
276
+ * Enum defining error codes used in error messages sent to clients.
277
+ *
278
+ * @example
279
+ * ```ts
280
+ * if (message.type === MessageType.ERROR) {
281
+ * const { code, message } = message.data
282
+ * console.error(`Error ${code}: ${message}`)
283
+ * }
284
+ * ```
285
+ */
286
+ declare enum ErrorCode {
287
+ /** Invalid message type */
288
+ INVALID_MESSAGE_TYPE = "INVALID_MESSAGE_TYPE",
289
+ /** Channel name missing from message */
290
+ MISSING_CHANNEL = "MISSING_CHANNEL",
291
+ /** Unknown signal type */
292
+ UNKNOWN_SIGNAL = "UNKNOWN_SIGNAL",
293
+ /** Channel not found */
294
+ CHANNEL_NOT_FOUND = "CHANNEL_NOT_FOUND",
295
+ /** Reserved channel name (starts with __) */
296
+ RESERVED_CHANNEL_NAME = "RESERVED_CHANNEL_NAME",
297
+ /** Invalid message format */
298
+ INVALID_MESSAGE_FORMAT = "INVALID_MESSAGE_FORMAT",
299
+ /** ID required but not provided */
300
+ ID_REQUIRED = "ID_REQUIRED",
301
+ /** ID already taken */
302
+ ID_TAKEN = "ID_TAKEN",
303
+ /** Invalid ID format */
304
+ INVALID_ID_FORMAT = "INVALID_ID_FORMAT",
305
+ /** Rate limit exceeded */
306
+ RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED"
307
+ }
308
+ /**
309
+ * Base message interface
310
+ *
311
+ * @remarks
312
+ * All messages in the Synca protocol extend this interface with
313
+ * common fields like ID, timestamp, and optional channel.
314
+ *
315
+ * @property id - Unique message identifier
316
+ * @property timestamp - Unix timestamp (ms) when message was created
317
+ * @property channel - Optional channel name (required for most message types)
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * const baseMessage: BaseMessage = {
322
+ * id: generateId(),
323
+ * timestamp: Date.now(),
324
+ * channel: 'chat'
325
+ * }
326
+ * ```
327
+ */
328
+ interface BaseMessage {
329
+ /** Unique message identifier */
330
+ id: MessageId;
331
+ /** Message timestamp */
332
+ timestamp: Timestamp;
333
+ /** Channel name */
334
+ channel?: ChannelName;
335
+ }
336
+ /**
337
+ * Data message (typed message with channel)
338
+ *
339
+ * @remarks
340
+ * Carries application data from sender to channel subscribers.
341
+ * This is the primary message type for user-defined communication.
342
+ *
343
+ * @template T - Type of the data payload (default: unknown)
344
+ *
345
+ * @property type - Message type discriminator (always DATA)
346
+ * @property channel - The target channel name
347
+ * @property data - The message data payload
348
+ *
349
+ * @example
350
+ * ```ts
351
+ * const chatMessage: DataMessage<{ text: string; user: string }> = {
352
+ * id: generateId(),
353
+ * timestamp: Date.now(),
354
+ * type: MessageType.DATA,
355
+ * channel: 'chat',
356
+ * data: {
357
+ * text: 'Hello world!',
358
+ * user: 'Alice'
359
+ * }
360
+ * }
361
+ *
362
+ * // Type narrowing
363
+ * if (message.type === MessageType.DATA) {
364
+ * console.log(`${message.data.user}: ${message.data.text}`)
365
+ * }
366
+ * ```
367
+ */
368
+ interface DataMessage<T = unknown> extends BaseMessage {
369
+ type: MessageType.DATA;
370
+ channel: ChannelName;
371
+ /** Message payload */
372
+ data: T;
373
+ }
374
+ /**
375
+ * Signal message (control message)
376
+ *
377
+ * @remarks
378
+ * Control messages for protocol operations like subscribe/unsubscribe,
379
+ * ping/pong keep-alive, and subscription confirmations.
380
+ *
381
+ * @property type - Message type discriminator (always SIGNAL)
382
+ * @property channel - The target channel name
383
+ * @property signal - The signal type
384
+ * @property data - Optional data payload
385
+ *
386
+ * @example
387
+ * ### Subscribe signal
388
+ * ```ts
389
+ * const subscribeSignal: SignalMessage = {
390
+ * id: generateId(),
391
+ * timestamp: Date.now(),
392
+ * type: MessageType.SIGNAL,
393
+ * channel: 'chat',
394
+ * signal: SignalType.SUBSCRIBE
395
+ * }
396
+ * ```
397
+ *
398
+ * @example
399
+ * ### Ping signal
400
+ * ```ts
401
+ * const pingSignal: SignalMessage = {
402
+ * id: generateId(),
403
+ * timestamp: Date.now(),
404
+ * type: MessageType.SIGNAL,
405
+ * signal: SignalType.PING
406
+ * }
407
+ * ```
408
+ */
409
+ interface SignalMessage extends BaseMessage {
410
+ type: MessageType.SIGNAL;
411
+ channel: ChannelName;
412
+ /** Signal type */
413
+ signal: SignalType;
414
+ /** Message payload */
415
+ data?: DataPayload;
416
+ }
417
+ /**
418
+ * Error data structure
419
+ *
420
+ * @remarks
421
+ * Contains error information sent to clients when an error occurs.
422
+ *
423
+ * @property message - Human-readable error message
424
+ * @property code - Optional machine-readable error code
425
+ *
426
+ * @example
427
+ * ```ts
428
+ * const errorData: ErrorData = {
429
+ * message: 'Channel not found',
430
+ * code: ErrorCode.CHANNEL_NOT_FOUND
431
+ * }
432
+ * ```
433
+ */
434
+ interface ErrorData {
435
+ /** Human-readable error message */
436
+ message: string;
437
+ /** Machine-readable error code */
438
+ code?: ErrorCode;
439
+ /** Additional error context */
440
+ [key: string]: unknown;
441
+ }
442
+ /**
443
+ * Error message
444
+ *
445
+ * @remarks
446
+ * Sent to clients when an error occurs during message processing.
447
+ *
448
+ * @property type - Message type discriminator (always ERROR)
449
+ * @property data - Error details including message and optional code
450
+ *
451
+ * @example
452
+ * ```ts
453
+ * const errorMessage: ErrorMessage = {
454
+ * id: generateId(),
455
+ * timestamp: Date.now(),
456
+ * type: MessageType.ERROR,
457
+ * data: {
458
+ * message: 'Channel not found',
459
+ * code: ErrorCode.CHANNEL_NOT_FOUND
460
+ * }
461
+ * }
462
+ * ```
463
+ */
464
+ interface ErrorMessage extends BaseMessage {
465
+ type: MessageType.ERROR;
466
+ /** Error detail */
467
+ data: ErrorData;
468
+ }
469
+ /**
470
+ * Acknowledgment message
471
+ *
472
+ * @remarks
473
+ * Confirms receipt of a message. Used for reliable messaging patterns.
474
+ *
475
+ * @property type - Message type discriminator (always ACK)
476
+ * @property ackMessageId - ID of the message being acknowledged
477
+ *
478
+ * @example
479
+ * ```ts
480
+ * const ackMessage: AckMessage = {
481
+ * id: generateId(),
482
+ * timestamp: Date.now(),
483
+ * type: MessageType.ACK,
484
+ * ackMessageId: 'original-message-id'
485
+ * }
486
+ * ```
487
+ */
488
+ interface AckMessage extends BaseMessage {
489
+ type: MessageType.ACK;
490
+ /** Original message being acknowledged */
491
+ ackMessageId: MessageId;
492
+ }
493
+ /**
494
+ * Union type for all supported messages in the protocol.
495
+ *
496
+ * @remarks
497
+ * Discriminated union of all message types. Use type narrowing with
498
+ * the `type` property to access specific message fields.
499
+ *
500
+ * @template T - Type of the data payload for DataMessage (default: unknown)
501
+ *
502
+ * @example
503
+ * ```ts
504
+ * function handleMessage(message: Message) {
505
+ * switch (message.type) {
506
+ * case MessageType.DATA:
507
+ * // message is DataMessage
508
+ * console.log('Data:', message.data)
509
+ * break
510
+ * case MessageType.SIGNAL:
511
+ * // message is SignalMessage
512
+ * console.log('Signal:', message.signal)
513
+ * break
514
+ * case MessageType.ERROR:
515
+ * // message is ErrorMessage
516
+ * console.error('Error:', message.data.message)
517
+ * break
518
+ * case MessageType.ACK:
519
+ * // message is AckMessage
520
+ * console.log('Ack for:', message.ackMessageId)
521
+ * break
522
+ * }
523
+ * }
524
+ * ```
525
+ *
526
+ * @example
527
+ * ### Type narrowing with DataMessage generic
528
+ * ```ts
529
+ * interface ChatMessage {
530
+ * text: string
531
+ * user: string
532
+ * }
533
+ *
534
+ * function isChatMessage(message: Message): message is DataMessage<ChatMessage> {
535
+ * return message.type === MessageType.DATA && message.channel === 'chat'
536
+ * }
537
+ *
538
+ * if (isChatMessage(message)) {
539
+ * console.log(`${message.data.user}: ${message.data.text}`)
540
+ * }
541
+ * ```
542
+ */
543
+ type Message<T = unknown> = DataMessage<T> | SignalMessage | ErrorMessage | AckMessage;
544
+ /**
545
+ * Middleware action types
546
+ *
547
+ * @remarks
548
+ * Defines all actions that middleware can intercept and process.
549
+ *
550
+ * @example
551
+ * ```ts
552
+ * import type { IMiddlewareAction } from '@synca/server'
553
+ *
554
+ * function handleAction(action: IMiddlewareAction) {
555
+ * switch (action) {
556
+ * case 'connect':
557
+ * console.log('Client connecting')
558
+ * break
559
+ * case 'disconnect':
560
+ * console.log('Client disconnecting')
561
+ * break
562
+ * case 'message':
563
+ * console.log('Message received')
564
+ * break
565
+ * case 'subscribe':
566
+ * console.log('Channel subscription')
567
+ * break
568
+ * case 'unsubscribe':
569
+ * console.log('Channel unsubscription')
570
+ * break
571
+ * }
572
+ * }
573
+ * ```
574
+ */
575
+ type IMiddlewareAction = 'connect' | 'disconnect' | 'message' | 'subscribe' | 'unsubscribe';
576
+ /**
577
+ * Next function type for middleware chain continuation
578
+ *
579
+ * @remarks
580
+ * Function passed to middleware to continue execution to the next
581
+ * middleware in the chain.
582
+ *
583
+ * @example
584
+ * ```ts
585
+ * const middleware: Middleware = async (context, next) => {
586
+ * // Pre-processing
587
+ * console.log('Before next')
588
+ *
589
+ * // Continue to next middleware
590
+ * await next()
591
+ *
592
+ * // Post-processing
593
+ * console.log('After next')
594
+ * }
595
+ * ```
596
+ */
597
+ type Next = () => Promise<void>;
598
+ /**
599
+ * Middleware context interface
600
+ *
601
+ * @remarks
602
+ * Provides middleware functions with access to request information,
603
+ * state management, and control flow methods. Inspired by Hono's context pattern.
604
+ *
605
+ * @template S - Type of the state object (default: Record<string, unknown>)
606
+ *
607
+ * @property req - Request information (client, message, channel, action)
608
+ * @property error - Optional error object
609
+ * @property finalized - Whether the context has been finalized
610
+ * @property res - Optional response data
611
+ * @property var - State object for storing custom data
612
+ * @property get - Get a value from state by key
613
+ * @property set - Set a value in state by key
614
+ * @property reject - Reject the action with a reason (throws)
615
+ *
616
+ * @example
617
+ * ### Basic usage
618
+ * ```ts
619
+ * const middleware: Middleware = async (context, next) => {
620
+ * console.log(`Action: ${context.req.action}`)
621
+ * console.log(`Client: ${context.req.client?.id}`)
622
+ * console.log(`Channel: ${context.req.channel}`)
623
+ *
624
+ * await next()
625
+ * }
626
+ * ```
627
+ *
628
+ * @example
629
+ * ### Using state
630
+ * ```ts
631
+ * interface MyState {
632
+ * user: { id: string; email: string }
633
+ * requestId: string
634
+ * }
635
+ *
636
+ * const middleware: Middleware<MyState> = async (context, next) => {
637
+ * // Set state
638
+ * context.set('requestId', generateId())
639
+ *
640
+ * // Get state
641
+ * const requestId = context.get('requestId')
642
+ *
643
+ * await next()
644
+ * }
645
+ * ```
646
+ *
647
+ * @example
648
+ * ### Rejecting actions
649
+ * ```ts
650
+ * const middleware: Middleware = async (context, next) => {
651
+ * if (context.req.action === 'connect') {
652
+ * // Check connection validity
653
+ * if (!isValidConnection(context.req.client)) {
654
+ * context.reject('Connection not allowed')
655
+ * // Function never returns (throws)
656
+ * }
657
+ * }
658
+ *
659
+ * await next()
660
+ * }
661
+ * ```
662
+ */
663
+ interface Context<S = Record<string, unknown>> {
664
+ /** Request information */
665
+ readonly req: {
666
+ /** The client connection (if applicable) */
667
+ readonly client?: IClientConnection;
668
+ /** The message being processed (if applicable) */
669
+ readonly message?: Message;
670
+ /** The channel name (if applicable) */
671
+ readonly channel?: ChannelName;
672
+ /** The action being performed */
673
+ readonly action: IMiddlewareAction;
674
+ };
675
+ /** Optional error object */
676
+ error?: Error;
677
+ /** Whether the context has been finalized */
678
+ finalized: boolean;
679
+ /** Optional response data */
680
+ res?: unknown;
681
+ /** State object for storing custom data */
682
+ readonly var: S;
683
+ /** Get a value from state by key */
684
+ get<K extends keyof S>(key: K): S[K];
685
+ /** Set a value in state by key */
686
+ set<K extends keyof S>(key: K, value: S[K]): void;
687
+ /** Reject the action with a reason (throws) */
688
+ reject(reason: string): never;
689
+ }
690
+ /**
691
+ * Middleware function signature
692
+ *
693
+ * @remarks
694
+ * Type definition for middleware functions. Middleware can be sync or async
695
+ * and can return any value (though the return value is typically ignored).
696
+ *
697
+ * @template S - Type of the state object (default: Record<string, unknown>)
698
+ *
699
+ * @param c - The middleware context
700
+ * @param next - Function to continue to the next middleware
701
+ *
702
+ * @example
703
+ * ```ts
704
+ * import type { Middleware } from '@synca/server'
705
+ *
706
+ * const authMiddleware: Middleware = async (context, next) => {
707
+ * const token = context.req.message?.data?.token
708
+ *
709
+ * if (!token) {
710
+ * context.reject('Authentication required')
711
+ * }
712
+ *
713
+ * const user = await verifyToken(token)
714
+ * context.set('user', user)
715
+ *
716
+ * await next()
717
+ * }
718
+ * ```
719
+ *
720
+ * @see {@link Context} for context interface
721
+ * @see {@link IMiddlewareAction} for available actions
722
+ */
723
+ type Middleware<S = Record<string, unknown>> = (
724
+ /** The middleware context */
725
+ c: Context<S>,
726
+ /** Function to continue to the next middleware */
727
+ next: Next) => void | Promise<void> | unknown;
728
+ /**
729
+ * Alias for Middleware type
730
+ *
731
+ * @remarks
732
+ * Alias maintained for backward compatibility. Prefer using `Middleware` directly.
733
+ *
734
+ * @template S - Type of the state object (default: Record<string, unknown>)
735
+ */
736
+ type IMiddleware<S = Record<string, unknown>> = Middleware<S>;
737
+ /**
738
+ * Middleware rejection error interface
739
+ *
740
+ * @remarks
741
+ * Interface for errors thrown when middleware rejects an action using
742
+ * `context.reject()`. This is a standard interface - actual errors should
743
+ * use the `MiddlewareRejectionError` class from the errors module.
744
+ *
745
+ * @property reason - Human-readable reason for rejection
746
+ * @property action - The action that was rejected
747
+ * @property name - Fixed value 'MiddlewareRejectionError' for interface compliance
748
+ *
749
+ * @example
750
+ * ```ts
751
+ * function isRejectionError(error: unknown): error is IMiddlewareRejectionError {
752
+ * return (
753
+ * typeof error === 'object' &&
754
+ * error !== null &&
755
+ * 'name' in error &&
756
+ * error.name === 'MiddlewareRejectionError'
757
+ * )
758
+ * }
759
+ *
760
+ * try {
761
+ * await someOperation()
762
+ * } catch (error) {
763
+ * if (isRejectionError(error)) {
764
+ * console.error(`Action '${error.action}' rejected: ${error.reason}`)
765
+ * }
766
+ * }
767
+ * ```
768
+ */
769
+ interface IMiddlewareRejectionError {
770
+ /** Human-readable reason for rejection */
771
+ reason: string;
772
+ /** The action that was rejected */
773
+ action: string;
774
+ /** Fixed value 'MiddlewareRejectionError' for interface compliance */
775
+ name: 'MiddlewareRejectionError';
776
+ }
777
+
778
+ /**
779
+ * Client Registry
780
+ * Manages connected clients, their subscriptions, and channel instances.
781
+ */
782
+ declare class ClientRegistry {
783
+ readonly connections: Map<ClientId, IClientConnection>;
784
+ private readonly subscriptions;
785
+ private readonly channels;
786
+ private readonly channelInstances;
787
+ readonly logger?: ILogger;
788
+ constructor(logger?: ILogger);
789
+ register(connection: IClientConnection): IClientConnection;
790
+ unregister(clientId: ClientId): boolean;
791
+ private clearSubscriptions;
792
+ get(clientId: ClientId): IClientConnection | undefined;
793
+ getAll(): IClientConnection[];
794
+ getCount(): number;
795
+ registerChannel(channel: BaseChannel<unknown>): void;
796
+ getChannel<T = unknown>(name: ChannelName): BaseChannel<T> | undefined;
797
+ removeChannel(name: ChannelName): boolean;
798
+ subscribe(clientId: ClientId, channel: ChannelName): boolean;
799
+ unsubscribe(clientId: ClientId, channel: ChannelName): boolean;
800
+ getSubscribers(channel: ChannelName): IClientConnection[];
801
+ getSubscriberCount(channel: ChannelName): number;
802
+ getChannels(): ChannelName[];
803
+ getTotalSubscriptionCount(): number;
804
+ isSubscribed(clientId: ClientId, channel: ChannelName): boolean;
805
+ getClientChannels(clientId: ClientId): Set<ChannelName>;
806
+ getChannelSubscribers(channel: ChannelName): Set<ClientId>;
807
+ clear(): void;
808
+ }
809
+
810
+ /**
811
+ * Config Module
812
+ * Single source of truth for all constant values and default configuration for the server.
813
+ *
814
+ * @module config
815
+ */
816
+ /**
817
+ * Broadcast channel name
818
+ * Messages sent to this channel reach ALL connected clients
819
+ * No subscription required for broadcast channel
820
+ */
821
+ declare const BROADCAST_CHANNEL: "__broadcast__";
822
+ /**
823
+ * WebSocket close codes
824
+ * Based on RFC 6455 and custom codes for application-specific closures
825
+ */
826
+ declare const CLOSE_CODES: {
827
+ /**
828
+ * Normal closure
829
+ */
830
+ readonly NORMAL: 1000;
831
+ /**
832
+ * Endpoint is going away
833
+ */
834
+ readonly GOING_AWAY: 1001;
835
+ /**
836
+ * Protocol error
837
+ */
838
+ readonly PROTOCOL_ERROR: 1002;
839
+ /**
840
+ * Unsupported data
841
+ */
842
+ readonly UNSUPPORTED_DATA: 1003;
843
+ /**
844
+ * No status received
845
+ */
846
+ readonly NO_STATUS: 1005;
847
+ /**
848
+ * Abnormal closure
849
+ */
850
+ readonly ABNORMAL: 1006;
851
+ /**
852
+ * Invalid frame payload data
853
+ */
854
+ readonly INVALID_PAYLOAD: 1007;
855
+ /**
856
+ * Policy violation
857
+ */
858
+ readonly POLICY_VIOLATION: 1008;
859
+ /**
860
+ * Message too big
861
+ */
862
+ readonly MESSAGE_TOO_BIG: 1009;
863
+ /**
864
+ * Missing extension
865
+ */
866
+ readonly MISSING_EXTENSION: 1010;
867
+ /**
868
+ * Internal error
869
+ */
870
+ readonly INTERNAL_ERROR: 1011;
871
+ /**
872
+ * Service restart
873
+ */
874
+ readonly SERVICE_RESTART: 1012;
875
+ /**
876
+ * Try again later
877
+ */
878
+ readonly TRY_AGAIN_LATER: 1013;
879
+ /**
880
+ * Connection rejected by middleware
881
+ */
882
+ readonly REJECTED: 4001;
883
+ /**
884
+ * Rate limit exceeded
885
+ */
886
+ readonly RATE_LIMITED: 4002;
887
+ /**
888
+ * Channel not found
889
+ */
890
+ readonly CHANNEL_NOT_FOUND: 4003;
891
+ /**
892
+ * Unauthorized
893
+ */
894
+ readonly UNAUTHORIZED: 4005;
895
+ };
896
+ /**
897
+ * Application error codes
898
+ * Used in error messages sent to clients
899
+ */
900
+ declare const ERROR_CODES: {
901
+ /**
902
+ * Action rejected by middleware
903
+ */
904
+ readonly REJECTED: "REJECTED";
905
+ /**
906
+ * Channel name missing from message
907
+ */
908
+ readonly MISSING_CHANNEL: "MISSING_CHANNEL";
909
+ /**
910
+ * Subscribe action rejected
911
+ */
912
+ readonly SUBSCRIBE_REJECTED: "SUBSCRIBE_REJECTED";
913
+ /**
914
+ * Unsubscribe action rejected
915
+ */
916
+ readonly UNSUBSCRIBE_REJECTED: "UNSUBSCRIBE_REJECTED";
917
+ /**
918
+ * Rate limit exceeded
919
+ */
920
+ readonly RATE_LIMITED: "RATE_LIMITED";
921
+ /**
922
+ * Authentication failed
923
+ */
924
+ readonly AUTH_FAILED: "AUTH_FAILED";
925
+ /**
926
+ * Authorization failed
927
+ */
928
+ readonly NOT_AUTHORIZED: "NOT_AUTHORIZED";
929
+ /**
930
+ * Channel not allowed
931
+ */
932
+ readonly CHANNEL_NOT_ALLOWED: "CHANNEL_NOT_ALLOWED";
933
+ /**
934
+ * Invalid message format
935
+ */
936
+ readonly INVALID_MESSAGE: "INVALID_MESSAGE";
937
+ /**
938
+ * Server error
939
+ */
940
+ readonly SERVER_ERROR: "SERVER_ERROR";
941
+ };
942
+
943
+ /**
944
+ * Channel state information
945
+ *
946
+ * @remarks
947
+ * Provides runtime information about a channel including its name,
948
+ * subscriber count, creation time, and last message timestamp.
949
+ *
950
+ * @property name - The channel name
951
+ * @property subscriberCount - Current number of subscribers
952
+ * @property createdAt - Unix timestamp (ms) when the channel was created
953
+ * @property lastMessageAt - Unix timestamp (ms) of the last published message
954
+ *
955
+ * @example
956
+ * ```ts
957
+ * const state: IChannelState = {
958
+ * name: 'chat',
959
+ * subscriberCount: 42,
960
+ * createdAt: 1699123456789,
961
+ * lastMessageAt: 1699123459999
962
+ * }
963
+ * ```
964
+ */
965
+ interface IChannelState {
966
+ /** The channel name */
967
+ name: string;
968
+ /** Current number of subscribers */
969
+ subscriberCount: number;
970
+ /** Unix timestamp (ms) when the channel was created */
971
+ createdAt: number;
972
+ /** Unix timestamp (ms) of the last published message */
973
+ lastMessageAt?: number;
974
+ }
975
+ /**
976
+ * Publish options for channel messages
977
+ *
978
+ * @remarks
979
+ * Controls which clients receive a published message through
980
+ * inclusion/exclusion filtering.
981
+ *
982
+ * @property to - Optional list of client IDs to receive the message (exclusive mode)
983
+ * @property exclude - Optional list of client IDs to exclude from receiving the message
984
+ *
985
+ * @example
986
+ * ```ts
987
+ * // Send to specific clients only
988
+ * channel.publish('Hello', { to: ['client-1', 'client-2'] })
989
+ *
990
+ * // Send to all except specific clients
991
+ * channel.publish('Hello', { exclude: ['client-123'] })
992
+ *
993
+ * // Both options can be combined (to takes precedence)
994
+ * channel.publish('Hello', { to: ['client-1'], exclude: ['client-2'] })
995
+ * ```
996
+ */
997
+ interface IPublishOptions {
998
+ /**
999
+ * Optional list of client IDs to receive the message
1000
+ *
1001
+ * @remarks
1002
+ * When provided, only clients in this list will receive the message.
1003
+ * This creates an "exclusive mode" where `exclude` is still applied
1004
+ * as a secondary filter.
1005
+ */
1006
+ to?: readonly ClientId[];
1007
+ /**
1008
+ * Optional list of client IDs to exclude from receiving the message
1009
+ *
1010
+ * @remarks
1011
+ * Clients in this list will not receive the message, even if they
1012
+ * are subscribed to the channel. Useful for excluding the sender.
1013
+ */
1014
+ exclude?: readonly ClientId[];
1015
+ }
1016
+ /**
1017
+ * Base message handler signature
1018
+ *
1019
+ * @remarks
1020
+ * Type-safe handler function for processing incoming messages on a channel.
1021
+ * Handlers receive the message data, sending client, and the original message object.
1022
+ *
1023
+ * @template T - Type of data expected in messages
1024
+ *
1025
+ * @example
1026
+ * ```ts
1027
+ * const handler: IMessageHandler<{ text: string }> = (data, client, message) => {
1028
+ * console.log(`${client.id} sent: ${data.text}`)
1029
+ * console.log(`Message ID: ${message.id}`)
1030
+ * }
1031
+ *
1032
+ * channel.onMessage(handler)
1033
+ * ```
1034
+ */
1035
+ type IMessageHandler<T> = (
1036
+ /** The message data payload */
1037
+ data: T,
1038
+ /** The client connection that sent the message */
1039
+ client: IClientConnection,
1040
+ /** The complete message object with metadata */
1041
+ message: DataMessage<T>) => void | Promise<void>;
1042
+
1043
+ /**
1044
+ * Base Channel implementation
1045
+ *
1046
+ * @remarks
1047
+ * Abstract base class for all channel types. Handles the complexities of
1048
+ * chunked publishing, client filtering, and message delivery. Provides
1049
+ * automatic chunking for large broadcasts to avoid event loop blocking.
1050
+ *
1051
+ * @template T - Type of data published on this channel (default: unknown)
1052
+ * @template N - Type of the channel name (default: ChannelName)
1053
+ *
1054
+ * @example
1055
+ * ```ts
1056
+ * // Extend BaseChannel for custom channel types
1057
+ * class CustomChannel<T> extends BaseChannel<T> {
1058
+ * get subscriberCount() { return 0 }
1059
+ * isEmpty() { return true }
1060
+ * getMiddlewares() { return [] }
1061
+ * protected getTargetClients() { return [] }
1062
+ * }
1063
+ * ```
1064
+ *
1065
+ * @see {@link BroadcastChannel} for channel that sends to all clients
1066
+ * @see {@link MulticastChannel} for channel that sends to subscribers only
1067
+ */
1068
+ declare abstract class BaseChannel<T = unknown, N extends ChannelName = ChannelName> {
1069
+ /** The channel name */
1070
+ readonly name: N;
1071
+ /** The client registry for connection management */
1072
+ protected readonly registry: ClientRegistry;
1073
+ /** Number of clients to process per chunk for large broadcasts (default: 500) */
1074
+ protected readonly chunkSize: number;
1075
+ /**
1076
+ * Creates a new BaseChannel instance
1077
+ *
1078
+ * @param name - The channel name
1079
+ * @param registry - The client registry for connection management
1080
+ * @param chunkSize - Number of clients to process per chunk for large broadcasts (default: 500)
1081
+ */
1082
+ constructor(
1083
+ /** The channel name */
1084
+ name: N,
1085
+ /** The client registry for connection management */
1086
+ registry: ClientRegistry,
1087
+ /** Number of clients to process per chunk for large broadcasts (default: 500) */
1088
+ chunkSize?: number);
1089
+ /**
1090
+ * Get the current subscriber count
1091
+ *
1092
+ * @remarks
1093
+ * Abstract method that must be implemented by subclasses.
1094
+ * Returns the number of clients currently receiving messages from this channel.
1095
+ */
1096
+ abstract get subscriberCount(): number;
1097
+ /**
1098
+ * Check if the channel has no subscribers
1099
+ *
1100
+ * @remarks
1101
+ * Abstract method that must be implemented by subclasses.
1102
+ * Returns `true` if the channel has no subscribers.
1103
+ */
1104
+ abstract isEmpty(): boolean;
1105
+ /**
1106
+ * Get the middleware for this channel
1107
+ *
1108
+ * @remarks
1109
+ * Abstract method that must be implemented by subclasses.
1110
+ * Returns an array of middleware functions applied to this channel.
1111
+ */
1112
+ abstract getMiddlewares(): IMiddleware[];
1113
+ /**
1114
+ * Publish data to the channel
1115
+ *
1116
+ * @remarks
1117
+ * Sends the data to all target clients. If the number of clients exceeds
1118
+ * `chunkSize`, messages are sent in chunks using `setImmediate` to avoid
1119
+ * blocking the event loop.
1120
+ *
1121
+ * @param data - The data to publish
1122
+ * @param options - Optional publish options for client filtering
1123
+ *
1124
+ * @example
1125
+ * ```ts
1126
+ * // Publish to all clients
1127
+ * channel.publish('Hello everyone!')
1128
+ *
1129
+ * // Publish excluding specific clients
1130
+ * channel.publish('Hello', { exclude: ['client-123'] })
1131
+ *
1132
+ * // Publish to specific clients only
1133
+ * channel.publish('Private message', { to: ['client-1', 'client-2'] })
1134
+ * ```
1135
+ */
1136
+ publish(data: T, options?: IPublishOptions): void;
1137
+ /**
1138
+ * Dispatch an incoming client message
1139
+ *
1140
+ * @remarks
1141
+ * Called when a client sends a message to this channel.
1142
+ * Override in subclasses to handle incoming messages.
1143
+ * Default implementation is a no-op (e.g., BroadcastChannel doesn't receive messages).
1144
+ *
1145
+ * @param data - The message data
1146
+ * @param client - The client that sent the message
1147
+ * @param message - The complete message object
1148
+ */
1149
+ dispatch(_data: T, _client: IClientConnection, _message: DataMessage<T>): Promise<void>;
1150
+ protected abstract getTargetClients(options?: IPublishOptions): ClientId[];
1151
+ protected publishToClients(data: T, clientIds: ClientId[], options?: IPublishOptions): void;
1152
+ protected publishInChunks(data: T, clientIds: ClientId[], options?: IPublishOptions): void;
1153
+ }
1154
+ /**
1155
+ * Broadcast Channel - sends messages to ALL connected clients
1156
+ *
1157
+ * @remarks
1158
+ * A special channel that delivers messages to every connected client,
1159
+ * regardless of subscription. This is useful for server-wide announcements,
1160
+ * system notifications, and global events.
1161
+ *
1162
+ * The broadcast channel is a singleton with the reserved name `__broadcast__`.
1163
+ * It is automatically created when the server starts and can be accessed
1164
+ * via `server.createBroadcast()`.
1165
+ *
1166
+ * @template T - Type of data to be broadcast (default: unknown)
1167
+ *
1168
+ * @example
1169
+ * ### Basic broadcasting
1170
+ * ```ts
1171
+ * const broadcast = server.createBroadcast<string>()
1172
+ * broadcast.publish('Server maintenance in 5 minutes')
1173
+ * ```
1174
+ *
1175
+ * @example
1176
+ * ### Broadcasting objects
1177
+ * ```ts
1178
+ * const alerts = server.createBroadcast<{ type: string; message: string }>()
1179
+ * alerts.publish({ type: 'warning', message: 'High load detected' })
1180
+ * ```
1181
+ *
1182
+ * @example
1183
+ * ### Client filtering
1184
+ * ```ts
1185
+ * // Send to all except specific clients
1186
+ * broadcast.publish('Admin message', { exclude: ['client-123'] })
1187
+ *
1188
+ * // Send to specific clients only
1189
+ * broadcast.publish('Private message', { to: ['client-1', 'client-2'] })
1190
+ * ```
1191
+ *
1192
+ * @example
1193
+ * ### System announcements
1194
+ * ```ts
1195
+ * const broadcast = server.createBroadcast<string>()
1196
+ *
1197
+ * // Send periodic announcements
1198
+ * setInterval(() => {
1199
+ * const time = new Date().toLocaleTimeString()
1200
+ * broadcast.publish(`Server time: ${time}`)
1201
+ * }, 60000)
1202
+ * ```
1203
+ *
1204
+ * @see {@link BROADCAST_CHANNEL} for the reserved channel name constant
1205
+ * @see {@link MulticastChannel} for subscription-based channels
1206
+ */
1207
+ declare class BroadcastChannel<T = unknown> extends BaseChannel<T, typeof BROADCAST_CHANNEL> {
1208
+ /**
1209
+ * Creates a new BroadcastChannel instance
1210
+ *
1211
+ * @remarks
1212
+ * BroadcastChannel is created automatically by the server and typically
1213
+ * accessed via `server.createBroadcast()` rather than instantiated directly.
1214
+ *
1215
+ * @param registry - The client registry for connection management
1216
+ * @param chunkSize - Number of clients to process per chunk for large broadcasts (default: 500)
1217
+ */
1218
+ constructor(registry: ClientRegistry, chunkSize?: number);
1219
+ /**
1220
+ * Get all connected clients as target recipients
1221
+ *
1222
+ * @remarks
1223
+ * Broadcast channel targets ALL connected clients. The `options` parameter
1224
+ * is still applied after this method returns for filtering.
1225
+ *
1226
+ * @param _options - Publish options (ignored for broadcast target selection)
1227
+ * @returns Array of all connected client IDs
1228
+ *
1229
+ * @internal
1230
+ */
1231
+ protected getTargetClients(_options?: IPublishOptions): ClientId[];
1232
+ /**
1233
+ * Get the number of connected clients
1234
+ *
1235
+ * @returns The total number of connected clients
1236
+ *
1237
+ * @example
1238
+ * ```ts
1239
+ * const broadcast = server.createBroadcast<string>()
1240
+ * console.log(`Connected clients: ${broadcast.subscriberCount}`)
1241
+ * ```
1242
+ */
1243
+ get subscriberCount(): number;
1244
+ /**
1245
+ * Check if there are no connected clients
1246
+ *
1247
+ * @returns `true` if no clients are connected, `false` otherwise
1248
+ *
1249
+ * @example
1250
+ * ```ts
1251
+ * const broadcast = server.createBroadcast<string>()
1252
+ * if (broadcast.isEmpty()) {
1253
+ * console.log('No clients connected')
1254
+ * }
1255
+ * ```
1256
+ */
1257
+ isEmpty(): boolean;
1258
+ /**
1259
+ * Get the middleware for this channel
1260
+ *
1261
+ * @remarks
1262
+ * Broadcast channel has no middleware by default. Returns an empty array.
1263
+ *
1264
+ * @returns Empty array (broadcast channels don't have middleware)
1265
+ */
1266
+ getMiddlewares(): IMiddleware[];
1267
+ }
1268
+ /**
1269
+ * Multicast channel options
1270
+ *
1271
+ * @remarks
1272
+ * Configuration options for creating a multicast channel.
1273
+ *
1274
+ * @property chunkSize - Number of clients to process per chunk for large broadcasts
1275
+ *
1276
+ * @example
1277
+ * ```ts
1278
+ * const chat = server.createMulticast('chat', { chunkSize: 1000 })
1279
+ * ```
1280
+ */
1281
+ interface MulticastChannelOptions {
1282
+ /**
1283
+ * Number of clients to process per chunk for large broadcasts
1284
+ *
1285
+ * @remarks
1286
+ * When broadcasting to more than this many subscribers, messages are sent
1287
+ * in chunks to avoid blocking the event loop.
1288
+ *
1289
+ * @default 500
1290
+ */
1291
+ chunkSize?: number;
1292
+ }
1293
+ /**
1294
+ * Multicast Channel - sends messages to subscribed clients only
1295
+ *
1296
+ * @remarks
1297
+ * A topic-based channel that delivers messages only to clients that have
1298
+ * explicitly subscribed. This is the standard channel type for implementing
1299
+ * chat rooms, notifications, and other subscription-based messaging patterns.
1300
+ *
1301
+ * Key features:
1302
+ * - Clients must subscribe to receive messages
1303
+ * - Supports message handlers for intercepting incoming messages
1304
+ * - Auto-relays messages to all subscribers (excluding sender) when no handler is set
1305
+ * - Supports per-channel middleware
1306
+ *
1307
+ * @template T - Type of data published on this channel (default: unknown)
1308
+ *
1309
+ * @example
1310
+ * ### Creating a channel
1311
+ * ```ts
1312
+ * const chat = server.createMulticast<{ text: string; user: string }>('chat')
1313
+ * ```
1314
+ *
1315
+ * @example
1316
+ * ### Publishing messages
1317
+ * ```ts
1318
+ * // Publish to all subscribers
1319
+ * chat.publish({ text: 'Hello everyone!', user: 'Alice' })
1320
+ *
1321
+ * // Publish excluding sender
1322
+ * chat.publish({ text: 'Welcome!', user: 'System' }, { exclude: ['client-123'] })
1323
+ *
1324
+ * // Publish to specific subscribers only
1325
+ * chat.publish({ text: 'Private message', user: 'Bob' }, { to: ['client-1'] })
1326
+ * ```
1327
+ *
1328
+ * @example
1329
+ * ### Handling incoming messages with auto-relay
1330
+ * ```ts
1331
+ * // When no handler is set, messages are auto-relayed to all subscribers (excluding sender)
1332
+ * // This is the default behavior for simple chat rooms
1333
+ * ```
1334
+ *
1335
+ * @example
1336
+ * ### Handling incoming messages with custom handler
1337
+ * ```ts
1338
+ * chat.onMessage((data, client) => {
1339
+ * console.log(`${data.user} (${client.id}): ${data.text}`)
1340
+ *
1341
+ * // Apply custom logic (filtering, transformation, persistence, etc.)
1342
+ * if (isProfane(data.text)) {
1343
+ * return // Don't relay
1344
+ * }
1345
+ *
1346
+ * // Manually publish to subscribers
1347
+ * chat.publish(data, { exclude: [client.id] })
1348
+ * })
1349
+ * ```
1350
+ *
1351
+ * @example
1352
+ * ### Managing subscriptions
1353
+ * ```ts
1354
+ * // Subscribe a client
1355
+ * chat.subscribe('client-123')
1356
+ *
1357
+ * // Unsubscribe a client
1358
+ * chat.unsubscribe('client-123')
1359
+ *
1360
+ * // Check if subscribed
1361
+ * if (chat.hasSubscriber('client-123')) {
1362
+ * console.log('Client is subscribed')
1363
+ * }
1364
+ *
1365
+ * // Get all subscribers
1366
+ * const subscribers = chat.getSubscribers()
1367
+ * console.log(`Subscribers: ${Array.from(subscribers).join(', ')}`)
1368
+ * ```
1369
+ *
1370
+ * @example
1371
+ * ### Channel middleware
1372
+ * ```ts
1373
+ * chat.use(async (context, next) => {
1374
+ * if (context.req.action === 'message') {
1375
+ * // Filter profanity
1376
+ * const data = context.req.message?.data as { text: string }
1377
+ * if (data?.text && isProfane(data.text)) {
1378
+ * context.reject('Profanity is not allowed')
1379
+ * }
1380
+ * }
1381
+ * await next()
1382
+ * })
1383
+ * ```
1384
+ *
1385
+ * @see {@link BroadcastChannel} for broadcasting to all clients
1386
+ */
1387
+ declare class MulticastChannel<T = unknown> extends BaseChannel<T> {
1388
+ private readonly middlewares;
1389
+ private readonly messageHandlers;
1390
+ /**
1391
+ * Creates a new MulticastChannel instance
1392
+ *
1393
+ * @remarks
1394
+ * MulticastChannel is typically created via `server.createMulticast()`
1395
+ * rather than instantiated directly.
1396
+ *
1397
+ * @param config.name - The channel name (must not start with `__`)
1398
+ * @param config.registry - The client registry for connection management
1399
+ * @param config.options - Optional channel configuration
1400
+ *
1401
+ * @throws {ValidationError} If the channel name is invalid (starts with `__`)
1402
+ */
1403
+ constructor(config: {
1404
+ /** The channel name (must not start with `__`) */
1405
+ name: ChannelName;
1406
+ /** The client registry for connection management */
1407
+ registry: ClientRegistry;
1408
+ /** Optional channel configuration */
1409
+ options?: MulticastChannelOptions;
1410
+ });
1411
+ /**
1412
+ * Get all subscribed clients as target recipients
1413
+ *
1414
+ * @remarks
1415
+ * Only clients that have subscribed to this channel will receive messages.
1416
+ * The `options` parameter is still applied after this method returns for filtering.
1417
+ *
1418
+ * @param _options - Publish options (ignored for multicast target selection)
1419
+ * @returns Array of subscribed client IDs
1420
+ *
1421
+ * @internal
1422
+ */
1423
+ protected getTargetClients(_options?: IPublishOptions): ClientId[];
1424
+ /**
1425
+ * Register a channel-specific middleware
1426
+ *
1427
+ * @remarks
1428
+ * Adds a middleware function that runs for all actions on this channel,
1429
+ * after global middleware. Channel middleware is useful for channel-specific
1430
+ * validation, filtering, or enrichment.
1431
+ *
1432
+ * @param middleware - The middleware function to register
1433
+ *
1434
+ * @example
1435
+ * ```ts
1436
+ * chat.use(async (context, next) => {
1437
+ * if (context.req.action === 'message') {
1438
+ * const data = context.req.message?.data as { text: string }
1439
+ * if (data?.text && isProfane(data.text)) {
1440
+ * context.reject('Profanity is not allowed')
1441
+ * }
1442
+ * }
1443
+ * await next()
1444
+ * })
1445
+ * ```
1446
+ *
1447
+ * @see {@link IMiddleware} for middleware interface
1448
+ */
1449
+ use(middleware: IMiddleware): void;
1450
+ /**
1451
+ * Get the middleware for this channel
1452
+ *
1453
+ * @returns Array of middleware functions registered on this channel
1454
+ */
1455
+ getMiddlewares(): IMiddleware[];
1456
+ /**
1457
+ * Get the number of subscribers
1458
+ *
1459
+ * @returns The number of clients subscribed to this channel
1460
+ *
1461
+ * @example
1462
+ * ```ts
1463
+ * console.log(`Chat subscribers: ${chat.subscriberCount}`)
1464
+ * ```
1465
+ */
1466
+ get subscriberCount(): number;
1467
+ /**
1468
+ * Register a message handler for incoming messages
1469
+ *
1470
+ * @remarks
1471
+ * Registers a handler that intercepts incoming messages to this channel.
1472
+ * When handlers are registered, they receive full control over message
1473
+ * processing - auto-relay is disabled.
1474
+ *
1475
+ * @param handler - The message handler function
1476
+ * @returns Unsubscribe function that removes the handler when called
1477
+ *
1478
+ * @example
1479
+ * ```ts
1480
+ * const unsubscribe = chat.onMessage((data, client) => {
1481
+ * console.log(`${client.id}: ${data.text}`)
1482
+ * // Custom processing logic
1483
+ * })
1484
+ *
1485
+ * // Later, remove the handler
1486
+ * unsubscribe()
1487
+ * ```
1488
+ *
1489
+ * @remarks
1490
+ * Multiple handlers can be registered. They will be executed in the order
1491
+ * they were registered. If any handler throws an error, it will be logged
1492
+ * but other handlers will still execute.
1493
+ */
1494
+ onMessage(handler: IMessageHandler<T>): () => void;
1495
+ /**
1496
+ * Subscribe a client to this channel
1497
+ *
1498
+ * @remarks
1499
+ * Subscribes a client to receive messages from this channel.
1500
+ * This is typically called automatically when a client sends a
1501
+ * subscribe signal, but can also be called manually.
1502
+ *
1503
+ * @param subscriber - The subscriber (client) ID to subscribe
1504
+ * @returns `true` if subscription was successful, `false` if already subscribed
1505
+ *
1506
+ * @example
1507
+ * ```ts
1508
+ * chat.subscribe('client-123')
1509
+ *
1510
+ * if (chat.subscribe('client-456')) {
1511
+ * console.log('Subscribed successfully')
1512
+ * }
1513
+ * ```
1514
+ */
1515
+ subscribe(subscriber: SubscriberId): boolean;
1516
+ /**
1517
+ * Unsubscribe a client from this channel
1518
+ *
1519
+ * @remarks
1520
+ * Removes a client's subscription from this channel.
1521
+ * This is typically called automatically when a client sends an
1522
+ * unsubscribe signal or disconnects, but can also be called manually.
1523
+ *
1524
+ * @param subscriber - The subscriber (client) ID to unsubscribe
1525
+ * @returns `true` if unsubscription was successful, `false` if not subscribed
1526
+ *
1527
+ * @example
1528
+ * ```ts
1529
+ * chat.unsubscribe('client-123')
1530
+ *
1531
+ * if (chat.unsubscribe('client-456')) {
1532
+ * console.log('Unsubscribed successfully')
1533
+ * }
1534
+ * ```
1535
+ */
1536
+ unsubscribe(subscriber: SubscriberId): boolean;
1537
+ /**
1538
+ * Dispatch an incoming client message
1539
+ *
1540
+ * @remarks
1541
+ * Processes incoming messages from clients. The behavior depends on
1542
+ * whether message handlers are registered:
1543
+ *
1544
+ * - **With handlers**: All registered handlers are executed with full
1545
+ * control over the message. No auto-relay occurs.
1546
+ *
1547
+ * - **Without handlers**: The message is automatically relayed to all
1548
+ * subscribers except the sender.
1549
+ *
1550
+ * @param data - The message data payload
1551
+ * @param client - The client that sent the message
1552
+ * @param message - The complete message object
1553
+ *
1554
+ * @example
1555
+ * ### Auto-relay mode (no handler)
1556
+ * ```ts
1557
+ * // Client sends message → automatically relayed to all subscribers
1558
+ * // No need to call publish()
1559
+ * ```
1560
+ *
1561
+ * @example
1562
+ * ### Intercept mode (with handler)
1563
+ * ```ts
1564
+ * chat.onMessage((data, client) => {
1565
+ * // Full control - implement custom logic
1566
+ * if (isValidMessage(data)) {
1567
+ * chat.publish(data, { exclude: [client.id] })
1568
+ * }
1569
+ * })
1570
+ * ```
1571
+ *
1572
+ * @internal
1573
+ */
1574
+ dispatch(data: T, client: IClientConnection, message: DataMessage<T>): Promise<void>;
1575
+ /**
1576
+ * Check if a client is subscribed to this channel
1577
+ *
1578
+ * @param subscriber - The subscriber (client) ID to check
1579
+ * @returns `true` if the client is subscribed, `false` otherwise
1580
+ *
1581
+ * @example
1582
+ * ```ts
1583
+ * if (chat.hasSubscriber('client-123')) {
1584
+ * console.log('Client is subscribed to chat')
1585
+ * }
1586
+ * ```
1587
+ */
1588
+ hasSubscriber(subscriber: SubscriberId): boolean;
1589
+ /**
1590
+ * Get all subscribers to this channel
1591
+ *
1592
+ * @remarks
1593
+ * Returns a copy of the subscribers set to prevent external modification.
1594
+ * The returned set is a snapshot and may not reflect future changes.
1595
+ *
1596
+ * @returns A Set of subscriber IDs
1597
+ *
1598
+ * @example
1599
+ * ```ts
1600
+ * const subscribers = chat.getSubscribers()
1601
+ * console.log(`Subscribers: ${Array.from(subscribers).join(', ')}`)
1602
+ *
1603
+ * // Check if a specific client is subscribed
1604
+ * if (subscribers.has('client-123')) {
1605
+ * console.log('Client 123 is subscribed')
1606
+ * }
1607
+ * ```
1608
+ */
1609
+ getSubscribers(): Set<SubscriberId>;
1610
+ /**
1611
+ * Check if the channel has no subscribers
1612
+ *
1613
+ * @returns `true` if no clients are subscribed, `false` otherwise
1614
+ *
1615
+ * @example
1616
+ * ```ts
1617
+ * if (chat.isEmpty()) {
1618
+ * console.log('No one is in the chat')
1619
+ * }
1620
+ * ```
1621
+ */
1622
+ isEmpty(): boolean;
1623
+ }
1624
+
1625
+ type ServerInstance = WebSocketServer;
1626
+ /**
1627
+ * WebSocket Server Transport Configuration
1628
+ *
1629
+ * @remarks
1630
+ * Configuration options for the WebSocket transport layer. Extends the
1631
+ * standard `ws` library options with Synca-specific settings.
1632
+ *
1633
+ * @example
1634
+ * ```ts
1635
+ * const config: WebSocketServerTransportConfig = {
1636
+ * server: httpServer,
1637
+ * path: '/ws',
1638
+ * enablePing: true,
1639
+ * pingInterval: 30000,
1640
+ * pingTimeout: 5000,
1641
+ * maxPayload: 1048576,
1642
+ * connections: new Map(),
1643
+ * generateId: (request) => extractUserId(request),
1644
+ * logger: console
1645
+ * }
1646
+ * ```
1647
+ */
1648
+ interface WebSocketServerTransportConfig extends ServerOptions {
1649
+ /**
1650
+ * Enable client ping/pong
1651
+ *
1652
+ * @remarks
1653
+ * When enabled, the server sends periodic ping frames to detect
1654
+ * dead connections and maintain keep-alive.
1655
+ *
1656
+ * @default true
1657
+ */
1658
+ enablePing?: boolean;
1659
+ /**
1660
+ * Ping interval in milliseconds
1661
+ *
1662
+ * @remarks
1663
+ * Time between ping frames when `enablePing` is true.
1664
+ *
1665
+ * @default 30000 (30 seconds)
1666
+ */
1667
+ pingInterval?: number;
1668
+ /**
1669
+ * Ping timeout in milliseconds
1670
+ *
1671
+ * @remarks
1672
+ * Time to wait for pong response before closing connection.
1673
+ *
1674
+ * @default 5000 (5 seconds)
1675
+ */
1676
+ pingTimeout?: number;
1677
+ /**
1678
+ * Shared connection map
1679
+ *
1680
+ * @remarks
1681
+ * Optional map for sharing connections across multiple server instances.
1682
+ * If not provided, a new map will be created.
1683
+ */
1684
+ connections?: Map<ClientId, IClientConnection>;
1685
+ /**
1686
+ * Custom ID generator for new connections
1687
+ *
1688
+ * @remarks
1689
+ * Function to generate unique client IDs from incoming HTTP requests.
1690
+ * Useful for implementing custom authentication or ID generation strategies.
1691
+ *
1692
+ * @example
1693
+ * ```ts
1694
+ * generateId: async (request) => {
1695
+ * const token = request.headers.authorization?.split(' ')[1]
1696
+ * return verifyToken(token).then(user => user.id)
1697
+ * }
1698
+ * ```
1699
+ */
1700
+ generateId?: IdGenerator;
1701
+ /**
1702
+ * Custom WebSocket Server constructor
1703
+ *
1704
+ * @remarks
1705
+ * Allows using a custom WebSocket server implementation.
1706
+ * Defaults to the standard `ws` WebSocketServer.
1707
+ */
1708
+ ServerConstructor?: new (config: ServerOptions) => ServerInstance;
1709
+ /**
1710
+ * Logger instance
1711
+ *
1712
+ * @remarks
1713
+ * Optional logger for transport-level logging.
1714
+ */
1715
+ logger?: ILogger;
1716
+ }
1717
+ /**
1718
+ * WebSocket Server Transport
1719
+ *
1720
+ * @remarks
1721
+ * Handles low-level WebSocket communication using the `ws` library.
1722
+ * Manages connections, message parsing, ping/pong keep-alive, and
1723
+ * emits high-level events for the server to consume.
1724
+ *
1725
+ * This transport:
1726
+ * - Wraps the `ws` WebSocketServer
1727
+ * - Generates unique client IDs
1728
+ * - Handles connection lifecycle (connect, disconnect, error)
1729
+ * - Parses incoming messages as JSON
1730
+ * - Manages ping/pong for connection health
1731
+ * - Emits typed events for server consumption
1732
+ *
1733
+ * @example
1734
+ * ### Basic usage
1735
+ * ```ts
1736
+ * import { WebSocketServerTransport } from '@synca/server'
1737
+ *
1738
+ * const transport = new WebSocketServerTransport({
1739
+ * server: httpServer,
1740
+ * path: '/ws',
1741
+ * enablePing: true,
1742
+ * pingInterval: 30000,
1743
+ * pingTimeout: 5000
1744
+ * })
1745
+ *
1746
+ * transport.on('connection', (client) => {
1747
+ * console.log(`Client connected: ${client.id}`)
1748
+ * })
1749
+ *
1750
+ * transport.on('message', (clientId, message) => {
1751
+ * console.log(`Message from ${clientId}:`, message)
1752
+ * })
1753
+ *
1754
+ * transport.on('disconnection', (clientId) => {
1755
+ * console.log(`Client disconnected: ${clientId}`)
1756
+ * })
1757
+ * ```
1758
+ *
1759
+ * @example
1760
+ * ### With custom ID generator
1761
+ * ```ts
1762
+ * const transport = new WebSocketServerTransport({
1763
+ * server: httpServer,
1764
+ * generateId: async (request) => {
1765
+ * const token = request.headers.authorization?.split(' ')[1]
1766
+ * const user = await verifyJwt(token)
1767
+ * return user.id
1768
+ * }
1769
+ * })
1770
+ * ```
1771
+ *
1772
+ * @example
1773
+ * ### With shared connections
1774
+ * ```ts
1775
+ * const sharedConnections = new Map()
1776
+ *
1777
+ * const transport1 = new WebSocketServerTransport({
1778
+ * connections: sharedConnections
1779
+ * })
1780
+ *
1781
+ * const transport2 = new WebSocketServerTransport({
1782
+ * connections: sharedConnections
1783
+ * })
1784
+ * ```
1785
+ *
1786
+ * @see {@link EventEmitter} for event methods (on, off, emit, etc.)
1787
+ */
1788
+ declare class WebSocketServerTransport extends EventEmitter {
1789
+ /**
1790
+ * Map of connected clients by ID
1791
+ *
1792
+ * @remarks
1793
+ * Public map of all active connections. Can be used to look up clients
1794
+ * by ID or iterate over all connections.
1795
+ */
1796
+ readonly connections: Map<ClientId, IClientConnection>;
1797
+ /** @internal */
1798
+ private readonly wsServer;
1799
+ /** @internal */
1800
+ private readonly config;
1801
+ /** @internal */
1802
+ private pingTimer?;
1803
+ /** @internal */
1804
+ private authenticator?;
1805
+ /**
1806
+ * Creates a new WebSocket Server Transport instance
1807
+ *
1808
+ * @remarks
1809
+ * Initializes the WebSocket transport layer with the provided configuration.
1810
+ * Sets up the underlying WebSocketServer, configures ping/pong, and
1811
+ * establishes event handlers.
1812
+ *
1813
+ * @param config - Transport configuration options
1814
+ *
1815
+ * @example
1816
+ * ```ts
1817
+ * const transport = new WebSocketServerTransport({
1818
+ * server: httpServer,
1819
+ * path: '/ws',
1820
+ * enablePing: true,
1821
+ * pingInterval: 30000,
1822
+ * pingTimeout: 5000
1823
+ * })
1824
+ * ```
1825
+ *
1826
+ * @emits connection When a new client connects
1827
+ * @emits disconnection When a client disconnects
1828
+ * @emits message When a message is received from a client
1829
+ * @emits error When an error occurs
1830
+ */
1831
+ constructor(config: WebSocketServerTransportConfig);
1832
+ /**
1833
+ * Set a custom authentication handler
1834
+ *
1835
+ * @remarks
1836
+ * Sets an authenticator function that receives the HTTP upgrade request
1837
+ * and returns a client ID. The authenticator can throw to reject the connection.
1838
+ *
1839
+ * @param authenticator - Function that receives the HTTP upgrade request
1840
+ * and returns a client ID (or throws to reject)
1841
+ *
1842
+ * @example
1843
+ * ```ts
1844
+ * transport.setAuthenticator(async (request) => {
1845
+ * const token = request.headers.authorization?.split(' ')[1]
1846
+ * if (!token) {
1847
+ * throw new Error('No token provided')
1848
+ * }
1849
+ * const user = await verifyJwt(token)
1850
+ * return user.id
1851
+ * })
1852
+ * ```
1853
+ */
1854
+ setAuthenticator(authenticator: (request: node_http.IncomingMessage) => string | Promise<string>): void;
1855
+ private setupEventHandlers;
1856
+ private handleConnection;
1857
+ private handleMessage;
1858
+ private handleDisconnection;
1859
+ private setupPingPong;
1860
+ private startPingTimer;
1861
+ private checkConnections;
1862
+ }
1863
+
1864
+ /**
1865
+ * Server statistics interface
1866
+ *
1867
+ * @remarks
1868
+ * Provides real-time statistics about the server state including
1869
+ * connected clients, active channels, and total subscriptions.
1870
+ *
1871
+ * @property clientCount - Number of currently connected clients
1872
+ * @property channelCount - Number of active channels
1873
+ * @property subscriptionCount - Total number of channel subscriptions across all channels
1874
+ * @property startedAt - Unix timestamp (ms) when the server was started
1875
+ *
1876
+ * @example
1877
+ * ```ts
1878
+ * const server = createSyncaServer({ port: 3000 })
1879
+ * await server.start()
1880
+ *
1881
+ * const stats = server.getStats()
1882
+ * console.log(`Clients: ${stats.clientCount}`)
1883
+ * console.log(`Channels: ${stats.channelCount}`)
1884
+ * console.log(`Started at: ${new Date(stats.startedAt!).toLocaleString()}`)
1885
+ * ```
1886
+ */
1887
+ interface IServerStats {
1888
+ /** Number of currently connected clients */
1889
+ clientCount: number;
1890
+ /** Number of active channels */
1891
+ channelCount: number;
1892
+ /** Total number of channel subscriptions across all channels */
1893
+ subscriptionCount: number;
1894
+ /** Unix timestamp (ms) when the server was started */
1895
+ startedAt?: number;
1896
+ }
1897
+
1898
+ /**
1899
+ * Server configuration options
1900
+ *
1901
+ * @remarks
1902
+ * Complete configuration interface for the Synca server. These options
1903
+ * control the WebSocket transport layer, connection handling, middleware,
1904
+ * and performance tuning parameters.
1905
+ *
1906
+ * @example
1907
+ * ```ts
1908
+ * import { createSyncaServer } from '@synca/server'
1909
+ *
1910
+ * const server = createSyncaServer({
1911
+ * port: 3000,
1912
+ * host: '0.0.0.0',
1913
+ * path: '/ws',
1914
+ * enablePing: true,
1915
+ * pingInterval: 30000,
1916
+ * pingTimeout: 5000,
1917
+ * broadcastChunkSize: 500,
1918
+ * })
1919
+ * ```
1920
+ *
1921
+ * @see {@link DEFAULT_SERVER_CONFIG} for default values
1922
+ */
1923
+ interface IServerOptions {
1924
+ /**
1925
+ * HTTP or HTTPS server instance
1926
+ *
1927
+ * @remarks
1928
+ * If provided, the WebSocket server will attach to this existing server.
1929
+ * If not provided, a new HTTP server will be created automatically.
1930
+ */
1931
+ server?: node_http.Server | node_https.Server;
1932
+ /**
1933
+ * Custom connection ID generator
1934
+ *
1935
+ * @remarks
1936
+ * Function to generate unique client IDs from incoming HTTP requests.
1937
+ * Useful for implementing custom authentication or ID generation strategies.
1938
+ *
1939
+ * @example
1940
+ * ```ts
1941
+ * generateId: (request) => {
1942
+ * const token = request.headers.authorization?.split(' ')[1]
1943
+ * return extractUserIdFromToken(token)
1944
+ * }
1945
+ * ```
1946
+ */
1947
+ generateId?: IdGenerator;
1948
+ /**
1949
+ * Custom logger instance
1950
+ *
1951
+ * @remarks
1952
+ * Logger conforming to the {@link ILogger} interface. Used for
1953
+ * debugging, error reporting, and operational monitoring.
1954
+ */
1955
+ logger: ILogger;
1956
+ /**
1957
+ * Port to listen on (default: 3000)
1958
+ *
1959
+ * @remarks
1960
+ * Only used when creating a new HTTP server. Ignored if `server`
1961
+ * option is provided.
1962
+ */
1963
+ port: number;
1964
+ /**
1965
+ * Host to bind to (default: '0.0.0.0')
1966
+ *
1967
+ * @remarks
1968
+ * Determines which network interface the server listens on.
1969
+ * Use 'localhost' for local-only access or '0.0.0.0' for all interfaces.
1970
+ */
1971
+ host: string;
1972
+ /**
1973
+ * WebSocket path (default: '/synca')
1974
+ *
1975
+ * @remarks
1976
+ * The URL path for WebSocket connections. Clients must connect to
1977
+ * `ws://host:port/path` to establish a connection.
1978
+ */
1979
+ path: string;
1980
+ /**
1981
+ * Transport implementation
1982
+ *
1983
+ * @remarks
1984
+ * Custom WebSocket transport layer. Defaults to {@link WebSocketServerTransport}
1985
+ * if not provided. Allows for custom transport implementations.
1986
+ */
1987
+ transport: WebSocketServerTransport;
1988
+ /**
1989
+ * Enable automatic ping/pong (default: true)
1990
+ *
1991
+ * @remarks
1992
+ * When enabled, the server sends periodic ping frames to detect
1993
+ * dead connections and maintain keep-alive.
1994
+ */
1995
+ enablePing: boolean;
1996
+ /**
1997
+ * Ping interval in ms (default: 30000)
1998
+ *
1999
+ * @remarks
2000
+ * Time between ping frames when `enablePing` is true.
2001
+ * Lower values detect dead connections faster but increase bandwidth.
2002
+ */
2003
+ pingInterval: number;
2004
+ /**
2005
+ * Ping timeout in ms (default: 5000)
2006
+ *
2007
+ * @remarks
2008
+ * Time to wait for pong response before closing connection.
2009
+ * Should be significantly less than `pingInterval`.
2010
+ */
2011
+ pingTimeout: number;
2012
+ /**
2013
+ * Client registry instance
2014
+ *
2015
+ * @remarks
2016
+ * Shared registry for tracking clients and subscriptions.
2017
+ * Allows multiple server instances to share state.
2018
+ */
2019
+ registry: ClientRegistry;
2020
+ /**
2021
+ * Global middleware chain
2022
+ *
2023
+ * @remarks
2024
+ * Middleware functions applied to all actions before channel-specific middleware.
2025
+ * Executed in the order they are defined.
2026
+ *
2027
+ * @example
2028
+ * ```ts
2029
+ * middleware: [
2030
+ * createAuthMiddleware({ verifyToken }),
2031
+ * createLoggingMiddleware(),
2032
+ * createRateLimitMiddleware({ maxRequests: 100 })
2033
+ * ]
2034
+ * ```
2035
+ */
2036
+ middleware: IMiddleware[];
2037
+ /**
2038
+ * Chunk size for large broadcasts (default: 500)
2039
+ *
2040
+ * @remarks
2041
+ * When broadcasting to more than this many clients, messages are sent
2042
+ * in chunks to avoid blocking the event loop. Lower values reduce latency
2043
+ * per chunk but increase total broadcast time.
2044
+ */
2045
+ broadcastChunkSize: number;
2046
+ }
2047
+ /**
2048
+ * Synca Server - Real-time WebSocket server with pub/sub channels
2049
+ *
2050
+ * @remarks
2051
+ * The main server class providing WebSocket communication with broadcast
2052
+ * and multicast channels, middleware support, and connection management.
2053
+ *
2054
+ * @example
2055
+ * ```ts
2056
+ * import { createSyncaServer } from '@synca/server'
2057
+ *
2058
+ * const server = createSyncaServer({ port: 3000 })
2059
+ * await server.start()
2060
+ *
2061
+ * // Create channels
2062
+ * const broadcast = server.createBroadcast<string>()
2063
+ * const chat = server.createMulticast<{ text: string }>('chat')
2064
+ *
2065
+ * // Listen for events
2066
+ * server.on('connection', (client) => {
2067
+ * console.log(`Client connected: ${client.id}`)
2068
+ * })
2069
+ *
2070
+ * // Publish messages
2071
+ * broadcast.publish('Hello everyone!')
2072
+ * chat.publish({ text: 'Welcome!' })
2073
+ * ```
2074
+ *
2075
+ * @see {@link createSyncaServer} for factory function
2076
+ */
2077
+ declare class SyncaServer {
2078
+ private readonly config;
2079
+ private transport;
2080
+ readonly registry: ClientRegistry;
2081
+ private readonly context;
2082
+ private readonly status;
2083
+ private connectionHandler;
2084
+ private messageHandler;
2085
+ private signalHandler;
2086
+ private broadcastChannel;
2087
+ constructor(config: IServerOptions);
2088
+ /**
2089
+ * Start the server and begin accepting connections
2090
+ *
2091
+ * @remarks
2092
+ * Initializes the WebSocket transport layer, sets up event handlers,
2093
+ * creates the broadcast channel, and prepares the server for connections.
2094
+ *
2095
+ * @throws {StateError} If the server is already started
2096
+ *
2097
+ * @example
2098
+ * ```ts
2099
+ * const server = createSyncaServer({ port: 3000 })
2100
+ * await server.start()
2101
+ * console.log('Server is running')
2102
+ * ```
2103
+ */
2104
+ start(): Promise<void>;
2105
+ /**
2106
+ * Stop the server and close all connections
2107
+ *
2108
+ * @remarks
2109
+ * Gracefully shuts down the server by clearing all handlers,
2110
+ * removing all channels, and allowing the transport layer to close.
2111
+ * Existing connections will be terminated.
2112
+ *
2113
+ * @example
2114
+ * ```ts
2115
+ * await server.stop()
2116
+ * console.log('Server stopped')
2117
+ * ```
2118
+ */
2119
+ stop(): Promise<void>;
2120
+ /**
2121
+ * Get or create the broadcast channel
2122
+ *
2123
+ * @remarks
2124
+ * Returns the singleton broadcast channel that sends messages to ALL
2125
+ * connected clients. No subscription is required - all clients receive
2126
+ * broadcast messages automatically.
2127
+ *
2128
+ * @template T - Type of data to be broadcast (default: unknown)
2129
+ * @returns The broadcast channel instance
2130
+ *
2131
+ * @throws {StateError} If the server hasn't been started yet
2132
+ *
2133
+ * @example
2134
+ * ```ts
2135
+ * // Broadcast a string to all clients
2136
+ * const broadcast = server.createBroadcast<string>()
2137
+ * broadcast.publish('Server maintenance in 5 minutes')
2138
+ *
2139
+ * // Broadcast an object
2140
+ * const alerts = server.createBroadcast<{ type: string; message: string }>()
2141
+ * alerts.publish({ type: 'warning', message: 'High load detected' })
2142
+ *
2143
+ * // Exclude specific clients
2144
+ * broadcast.publish('Admin message', { exclude: ['client-123'] })
2145
+ *
2146
+ * // Send to specific clients only
2147
+ * broadcast.publish('Private message', { to: ['client-1', 'client-2'] })
2148
+ * ```
2149
+ *
2150
+ * @see {@link BroadcastChannel} for channel API
2151
+ */
2152
+ createBroadcast<T = unknown>(): BroadcastChannel<T>;
2153
+ /**
2154
+ * Create or retrieve a multicast channel
2155
+ *
2156
+ * @remarks
2157
+ * Creates a named channel that delivers messages only to subscribed clients.
2158
+ * Clients must explicitly subscribe to receive messages. If a channel with
2159
+ * the given name already exists, it will be returned instead of creating a new one.
2160
+ *
2161
+ * @template T - Type of data to be published on this channel (default: unknown)
2162
+ * @param name - Unique channel name (must not start with `__` which is reserved)
2163
+ * @returns The multicast channel instance
2164
+ *
2165
+ * @throws {StateError} If the server hasn't been started yet
2166
+ * @throws {ValidationError} If the channel name is invalid (starts with `__`)
2167
+ *
2168
+ * @example
2169
+ * ```ts
2170
+ * // Create a chat channel
2171
+ * const chat = server.createMulticast<{ text: string; user: string }>('chat')
2172
+ *
2173
+ * // Handle incoming messages
2174
+ * chat.onMessage((data, client) => {
2175
+ * console.log(`${client.id}: ${data.text}`)
2176
+ * // Relay to all subscribers except sender
2177
+ * chat.publish(data, { exclude: [client.id] })
2178
+ * })
2179
+ *
2180
+ * // Publish to all subscribers
2181
+ * chat.publish({ text: 'Hello!', user: 'System' })
2182
+ *
2183
+ * // Check channel existence
2184
+ * if (server.hasChannel('chat')) {
2185
+ * const existingChat = server.createMulticast('chat')
2186
+ * }
2187
+ *
2188
+ * // Get all channel names
2189
+ * const channels = server.getChannels()
2190
+ * // ['chat', 'notifications', 'presence']
2191
+ * ```
2192
+ *
2193
+ * @see {@link MulticastChannel} for channel API
2194
+ * @see {@link BROADCAST_CHANNEL} for reserved channel name
2195
+ */
2196
+ createMulticast<T = unknown>(name: ChannelName): MulticastChannel<T>;
2197
+ /**
2198
+ * Check if a channel exists
2199
+ *
2200
+ * @param name - The channel name to check
2201
+ * @returns `true` if a channel with this name exists, `false` otherwise
2202
+ *
2203
+ * @example
2204
+ * ```ts
2205
+ * if (!server.hasChannel('chat')) {
2206
+ * const chat = server.createMulticast('chat')
2207
+ * }
2208
+ * ```
2209
+ */
2210
+ hasChannel(name: ChannelName): boolean;
2211
+ /**
2212
+ * Get all active channel names
2213
+ *
2214
+ * @returns Array of channel names currently registered on the server
2215
+ *
2216
+ * @example
2217
+ * ```ts
2218
+ * const channels = server.getChannels()
2219
+ * console.log('Active channels:', channels)
2220
+ * // ['chat', 'notifications', 'presence']
2221
+ * ```
2222
+ */
2223
+ getChannels(): ChannelName[];
2224
+ /**
2225
+ * Register a global middleware
2226
+ *
2227
+ * @remarks
2228
+ * Adds a middleware function to the global middleware chain.
2229
+ * Global middleware runs before channel-specific middleware for all actions.
2230
+ *
2231
+ * @param middleware - The middleware function to register
2232
+ *
2233
+ * @example
2234
+ * ```ts
2235
+ * import { createAuthMiddleware } from '@synca/server'
2236
+ *
2237
+ * server.use(createAuthMiddleware({
2238
+ * verifyToken: async (token) => jwt.verify(token, SECRET)
2239
+ * }))
2240
+ * ```
2241
+ *
2242
+ * @see {@link IMiddleware} for middleware interface
2243
+ */
2244
+ use(middleware: IMiddleware): void;
2245
+ /**
2246
+ * Set a custom authentication handler for connection validation
2247
+ *
2248
+ * @remarks
2249
+ * Sets an authenticator function that receives the HTTP upgrade request
2250
+ * and returns a client ID. The authenticator can throw to reject the connection.
2251
+ * This is useful for implementing token-based authentication during the
2252
+ * WebSocket handshake.
2253
+ *
2254
+ * @param authenticator - A function that receives the HTTP upgrade request
2255
+ * and returns a ClientId (or throws to reject the connection)
2256
+ *
2257
+ * @example
2258
+ * ```ts
2259
+ * server.authenticate(async (request) => {
2260
+ * const token = request.headers.authorization?.split(' ')[1]
2261
+ * if (!token) {
2262
+ * throw new Error('No token provided')
2263
+ * }
2264
+ * const user = await verifyJwt(token)
2265
+ * return user.id
2266
+ * })
2267
+ * ```
2268
+ */
2269
+ authenticate(authenticator: (request: node_http.IncomingMessage) => string | Promise<string>): void;
2270
+ /**
2271
+ * Get server statistics
2272
+ *
2273
+ * @returns Server statistics including client count, channel count,
2274
+ * subscription count, and start time
2275
+ *
2276
+ * @example
2277
+ * ```ts
2278
+ * const stats = server.getStats()
2279
+ * console.log(`Clients: ${stats.clientCount}`)
2280
+ * console.log(`Channels: ${stats.channelCount}`)
2281
+ * console.log(`Subscriptions: ${stats.subscriptionCount}`)
2282
+ * console.log(`Started: ${new Date(stats.startedAt!).toLocaleString()}`)
2283
+ * ```
2284
+ *
2285
+ * @see {@link IServerStats} for statistics structure
2286
+ */
2287
+ getStats(): IServerStats;
2288
+ /**
2289
+ * Get the server configuration (read-only)
2290
+ *
2291
+ * @returns Readonly copy of the server configuration options
2292
+ *
2293
+ * @example
2294
+ * ```ts
2295
+ * const config = server.getConfig()
2296
+ * console.log(`Port: ${config.port}`)
2297
+ * console.log(`Host: ${config.host}`)
2298
+ * console.log(`Path: ${config.path}`)
2299
+ * ```
2300
+ */
2301
+ getConfig(): Readonly<IServerOptions>;
2302
+ /**
2303
+ * Get the client registry
2304
+ *
2305
+ * @returns The client registry instance used by this server
2306
+ *
2307
+ * @remarks
2308
+ * The registry manages client connections and channel subscriptions.
2309
+ * Direct access allows for advanced operations like manual client
2310
+ * lookup or subscription management.
2311
+ *
2312
+ * @example
2313
+ * ```ts
2314
+ * const registry = server.getRegistry()
2315
+ * const client = registry.get('client-123')
2316
+ * if (client) {
2317
+ * console.log(`Client connected at: ${new Date(client.connectedAt).toLocaleString()}`)
2318
+ * }
2319
+ * ```
2320
+ *
2321
+ * @see {@link ClientRegistry} for registry API
2322
+ */
2323
+ getRegistry(): ClientRegistry;
2324
+ private setupTransportHandlers;
2325
+ }
2326
+ /**
2327
+ * Create a Synca server with automatic WebSocket transport setup
2328
+ *
2329
+ * @remarks
2330
+ * Factory function that creates a configured SyncaServer instance.
2331
+ * Automatically sets up the WebSocket transport layer if not provided,
2332
+ * merges user configuration with defaults, and creates the client registry.
2333
+ *
2334
+ * @param config - Optional partial server configuration. All properties are optional
2335
+ * and will be merged with {@link DEFAULT_SERVER_CONFIG}.
2336
+ *
2337
+ * @returns Configured Synca server instance ready to be started
2338
+ *
2339
+ * @example
2340
+ * ### Basic usage
2341
+ * ```ts
2342
+ * import { createSyncaServer } from '@synca/server'
2343
+ *
2344
+ * const server = createSyncaServer({ port: 3000 })
2345
+ * await server.start()
2346
+ * ```
2347
+ *
2348
+ * @example
2349
+ * ### With custom configuration
2350
+ * ```ts
2351
+ * const server = createSyncaServer({
2352
+ * port: 8080,
2353
+ * host: 'localhost',
2354
+ * path: '/ws',
2355
+ * enablePing: true,
2356
+ * pingInterval: 30000,
2357
+ * pingTimeout: 5000,
2358
+ * broadcastChunkSize: 1000,
2359
+ * })
2360
+ * await server.start()
2361
+ * ```
2362
+ *
2363
+ * @example
2364
+ * ### With existing HTTP server
2365
+ * ```ts
2366
+ * import { createServer } from 'node:http'
2367
+ * import { createSyncaServer } from '@synca/server'
2368
+ *
2369
+ * const httpServer = createServer((req, res) => {
2370
+ * res.writeHead(200)
2371
+ * res.end('OK')
2372
+ * })
2373
+ *
2374
+ * const server = createSyncaServer({
2375
+ * server: httpServer,
2376
+ * path: '/ws',
2377
+ * })
2378
+ *
2379
+ * await server.start()
2380
+ * ```
2381
+ *
2382
+ * @example
2383
+ * ### With Express
2384
+ * ```ts
2385
+ * import express from 'express'
2386
+ * import { createSyncaServer } from '@synca/server'
2387
+ *
2388
+ * const app = express()
2389
+ * const httpServer = app.listen(3000)
2390
+ *
2391
+ * const server = createSyncaServer({
2392
+ * server: httpServer,
2393
+ * path: '/ws',
2394
+ * })
2395
+ *
2396
+ * await server.start()
2397
+ * ```
2398
+ *
2399
+ * @example
2400
+ * ### With custom logger
2401
+ * ```ts
2402
+ * import { createSyncaServer } from '@synca/server'
2403
+ *
2404
+ * const server = createSyncaServer({
2405
+ * port: 3000,
2406
+ * logger: {
2407
+ * debug: (msg, ...args) => console.debug('[DEBUG]', msg, ...args),
2408
+ * info: (msg, ...args) => console.info('[INFO]', msg, ...args),
2409
+ * warn: (msg, ...args) => console.warn('[WARN]', msg, ...args),
2410
+ * error: (msg, ...args) => console.error('[ERROR]', msg, ...args),
2411
+ * },
2412
+ * })
2413
+ * ```
2414
+ *
2415
+ * @example
2416
+ * ### With middleware
2417
+ * ```ts
2418
+ * import { createSyncaServer, createLoggingMiddleware } from '@synca/server'
2419
+ *
2420
+ * const server = createSyncaServer({
2421
+ * port: 3000,
2422
+ * middleware: [
2423
+ * createLoggingMiddleware(),
2424
+ * ],
2425
+ * })
2426
+ *
2427
+ * await server.start()
2428
+ * ```
2429
+ *
2430
+ * @see {@link SyncaServer} for server class API
2431
+ * @see {@link DEFAULT_SERVER_CONFIG} for default configuration values
2432
+ */
2433
+ declare function createSyncaServer(config?: Partial<IServerOptions>): SyncaServer;
2434
+
2435
+ /**
2436
+ * Middleware Factories
2437
+ * Factory functions for creating common middleware implementations.
2438
+ *
2439
+ * @module middleware/factories
2440
+ */
2441
+
2442
+ /**
2443
+ * Auth middleware options
2444
+ *
2445
+ * @example
2446
+ * ```ts
2447
+ * const options: AuthMiddlewareOptions = {
2448
+ * verifyToken: async (token) => {
2449
+ * const user = await verifyJwt(token)
2450
+ * return { id: user.id, email: user.email }
2451
+ * },
2452
+ * getToken: (context) => {
2453
+ * // Extract token from message or connection
2454
+ * return context.message?.data?.token
2455
+ * },
2456
+ * attachProperty: 'user'
2457
+ * }
2458
+ * ```
2459
+ */
2460
+ interface AuthMiddlewareOptions {
2461
+ /**
2462
+ * Verify and decode a token
2463
+ * Returns the user data to attach to the client
2464
+ *
2465
+ * @param token - The token to verify
2466
+ * @returns User data to attach
2467
+ * @throws Error if token is invalid
2468
+ */
2469
+ verifyToken: (token: string) => Promise<unknown> | unknown;
2470
+ /**
2471
+ * Extract token from the middleware context
2472
+ *
2473
+ * @param c - The middleware context
2474
+ * @returns The token string or undefined if not found
2475
+ */
2476
+ getToken?: (c: Context) => string | undefined;
2477
+ /**
2478
+ * Property name to attach verified user data
2479
+ * @default 'user'
2480
+ */
2481
+ attachProperty?: string;
2482
+ /**
2483
+ * Actions to require authentication
2484
+ * @default All actions require auth
2485
+ */
2486
+ actions?: IMiddlewareAction[];
2487
+ }
2488
+ /**
2489
+ * Create an authentication middleware
2490
+ *
2491
+ * This middleware verifies tokens and attaches user data to clients.
2492
+ * Rejects connections that fail authentication.
2493
+ *
2494
+ * @param options - Authentication options
2495
+ * @returns Middleware function
2496
+ *
2497
+ * @example
2498
+ * ```ts
2499
+ * import { createAuthMiddleware } from '@synca/server/middleware'
2500
+ *
2501
+ * const authMiddleware = createAuthMiddleware({
2502
+ * verifyToken: async (token) => {
2503
+ * const user = await jwt.verify(token, SECRET)
2504
+ * return { id: user.sub, email: user.email }
2505
+ * },
2506
+ * getToken: (context) => context.message?.data?.token,
2507
+ * attachProperty: 'user'
2508
+ * })
2509
+ *
2510
+ * server.use(authMiddleware)
2511
+ * ```
2512
+ */
2513
+ declare function createAuthMiddleware(options: AuthMiddlewareOptions): IMiddleware;
2514
+ /**
2515
+ * Logging middleware options
2516
+ *
2517
+ * @example
2518
+ * ```ts
2519
+ * const options: LoggingMiddlewareOptions = {
2520
+ * logger: console,
2521
+ * logLevel: 'info',
2522
+ * includeMessageData: false
2523
+ * }
2524
+ * ```
2525
+ */
2526
+ interface LoggingMiddlewareOptions {
2527
+ /**
2528
+ * Logger instance to use
2529
+ * @default console
2530
+ */
2531
+ logger?: Pick<Console, 'log' | 'info' | 'warn' | 'error'>;
2532
+ /**
2533
+ * Log level
2534
+ * @default 'info'
2535
+ */
2536
+ logLevel?: 'log' | 'info' | 'warn' | 'error';
2537
+ /**
2538
+ * Whether to include message data in logs
2539
+ * @default false
2540
+ */
2541
+ includeMessageData?: boolean;
2542
+ /**
2543
+ * Custom format function for log output
2544
+ *
2545
+ * @param context - The middleware context
2546
+ * @returns Formatted log string
2547
+ */
2548
+ format?: (context: {
2549
+ action: string;
2550
+ clientId?: string;
2551
+ channel?: string;
2552
+ message?: unknown;
2553
+ duration?: number;
2554
+ }) => string;
2555
+ /**
2556
+ * Actions to log
2557
+ * @default All actions are logged
2558
+ */
2559
+ actions?: IMiddlewareAction[];
2560
+ }
2561
+ /**
2562
+ * Create a logging middleware
2563
+ *
2564
+ * Logs all middleware actions with client and action information.
2565
+ *
2566
+ * @param options - Logging options
2567
+ * @returns Middleware function
2568
+ *
2569
+ * @example
2570
+ * ```ts
2571
+ * import { createLoggingMiddleware } from '@synca/server/middleware'
2572
+ *
2573
+ * const loggingMiddleware = createLoggingMiddleware({
2574
+ * logger: console,
2575
+ * logLevel: 'info',
2576
+ * includeMessageData: false
2577
+ * })
2578
+ *
2579
+ * server.use(loggingMiddleware)
2580
+ * ```
2581
+ */
2582
+ declare function createLoggingMiddleware(options?: LoggingMiddlewareOptions): IMiddleware;
2583
+ /**
2584
+ * Rate limit middleware options
2585
+ *
2586
+ * @example
2587
+ * ```ts
2588
+ * const options: RateLimitMiddlewareOptions = {
2589
+ * maxRequests: 100,
2590
+ * windowMs: 60000,
2591
+ * getMessageId: (context) => context.client?.id ?? ''
2592
+ * }
2593
+ * ```
2594
+ */
2595
+ interface RateLimitMiddlewareOptions {
2596
+ /**
2597
+ * Maximum number of requests allowed per window
2598
+ * @default 100
2599
+ */
2600
+ maxRequests?: number;
2601
+ /**
2602
+ * Time window in milliseconds
2603
+ * @default 60000 (1 minute)
2604
+ */
2605
+ windowMs?: number;
2606
+ /**
2607
+ * Extract a unique identifier for rate limiting
2608
+ * Defaults to client ID
2609
+ *
2610
+ * @param c - The middleware context
2611
+ * @returns Unique identifier for rate limiting
2612
+ */
2613
+ getMessageId?: (c: Context) => string;
2614
+ /**
2615
+ * Actions to rate limit
2616
+ * @default 'message' only
2617
+ */
2618
+ actions?: IMiddlewareAction[];
2619
+ }
2620
+ /**
2621
+ * Create a rate limiting middleware
2622
+ *
2623
+ * Limits the rate of requests per client within a time window.
2624
+ *
2625
+ * @param options - Rate limit options
2626
+ * @returns Middleware function
2627
+ *
2628
+ * @example
2629
+ * ```ts
2630
+ * import { createRateLimitMiddleware } from '@synca/server/middleware'
2631
+ *
2632
+ * const rateLimitMiddleware = createRateLimitMiddleware({
2633
+ * maxRequests: 100,
2634
+ * windowMs: 60000
2635
+ * })
2636
+ *
2637
+ * server.use(rateLimitMiddleware)
2638
+ * ```
2639
+ */
2640
+ declare function createRateLimitMiddleware(options?: RateLimitMiddlewareOptions): IMiddleware;
2641
+ /**
2642
+ * Channel whitelist middleware options
2643
+ *
2644
+ * @example
2645
+ * ```ts
2646
+ * const options: ChannelWhitelistMiddlewareOptions = {
2647
+ * allowedChannels: ['chat', 'notifications'],
2648
+ * isDynamic: false
2649
+ * }
2650
+ * ```
2651
+ */
2652
+ interface ChannelWhitelistMiddlewareOptions {
2653
+ /**
2654
+ * List of allowed channels
2655
+ * If isDynamic is true, this is used as a fallback
2656
+ */
2657
+ allowedChannels?: ChannelName[];
2658
+ /**
2659
+ * Dynamic check function for channel access
2660
+ * If provided, this takes precedence over allowedChannels
2661
+ *
2662
+ * @param channel - The channel name to check
2663
+ * @param client - The client attempting to access the channel
2664
+ * @returns true if channel is allowed
2665
+ *
2666
+ * @example
2667
+ * ```ts
2668
+ * const isDynamic: (channel, client) => {
2669
+ * // Check if user has permission for this channel
2670
+ * return user.permissions.includes(channel)
2671
+ * }
2672
+ * ```
2673
+ */
2674
+ isDynamic?: (channel: ChannelName, client?: IClientConnection) => boolean;
2675
+ /**
2676
+ * Whether to also check unsubscribe actions
2677
+ * @default false (only restrict subscribe)
2678
+ */
2679
+ restrictUnsubscribe?: boolean;
2680
+ }
2681
+ /**
2682
+ * Create a channel whitelist middleware
2683
+ *
2684
+ * Restricts which channels clients can subscribe to.
2685
+ *
2686
+ * @param options - Channel whitelist options
2687
+ * @returns Middleware function
2688
+ *
2689
+ * @example
2690
+ * ```ts
2691
+ * import { createChannelWhitelistMiddleware } from '@synca/server/middleware'
2692
+ *
2693
+ * const whitelistMiddleware = createChannelWhitelistMiddleware({
2694
+ * allowedChannels: ['chat', 'notifications']
2695
+ * })
2696
+ *
2697
+ * server.use(whitelistMiddleware)
2698
+ * ```
2699
+ */
2700
+ declare function createChannelWhitelistMiddleware(options?: ChannelWhitelistMiddlewareOptions): IMiddleware;
2701
+
2702
+ /**
2703
+ * Context Data Options
2704
+ *
2705
+ * @remarks
2706
+ * Options for creating a new middleware context with pre-populated state.
2707
+ *
2708
+ * @template S - Type of the state object (default: Record<string, unknown>)
2709
+ *
2710
+ * @property action - The middleware action being performed
2711
+ * @property client - Optional client connection
2712
+ * @property message - Optional message being processed
2713
+ * @property channel - Optional channel name
2714
+ * @property initialState - Optional initial state values
2715
+ *
2716
+ * @example
2717
+ * ```ts
2718
+ * const options: ContextOptions<{ user: UserInfo }> = {
2719
+ * action: 'message',
2720
+ * client: connection,
2721
+ * message: dataMessage,
2722
+ * channel: 'chat',
2723
+ * initialState: { user: { id: '123', name: 'Alice' } }
2724
+ * }
2725
+ * ```
2726
+ */
2727
+ interface ContextOptions<S = Record<string, unknown>> {
2728
+ /** The middleware action being performed */
2729
+ action: IMiddlewareAction;
2730
+ /** Optional client connection */
2731
+ client?: IClientConnection;
2732
+ /** Optional message being processed */
2733
+ message?: Message;
2734
+ /** Optional channel name */
2735
+ channel?: ChannelName;
2736
+ /** Optional initial state values */
2737
+ initialState?: S;
2738
+ }
2739
+ /**
2740
+ * Create a new Hono-style middleware context
2741
+ *
2742
+ * @remarks
2743
+ * Creates a lightweight middleware context using closures to ensure properties
2744
+ * can be destructured safely. The context provides access to request information,
2745
+ * state management, and control flow methods.
2746
+ *
2747
+ * @template S - Type of the state object (default: Record<string, unknown>)
2748
+ *
2749
+ * @param options - Context initialization options
2750
+ * @returns A new Context object
2751
+ *
2752
+ * @example
2753
+ * ### Basic usage
2754
+ * ```ts
2755
+ * const context = createContext({
2756
+ * action: 'message',
2757
+ * client: connection,
2758
+ * message: dataMessage,
2759
+ * channel: 'chat'
2760
+ * })
2761
+ * ```
2762
+ *
2763
+ * @example
2764
+ * ### With initial state
2765
+ * ```ts
2766
+ * interface AppState {
2767
+ * user: { id: string; name: string }
2768
+ * requestId: string
2769
+ * }
2770
+ *
2771
+ * const context = createContext<AppState>({
2772
+ * action: 'message',
2773
+ * initialState: {
2774
+ * user: { id: '123', name: 'Alice' },
2775
+ * requestId: generateId()
2776
+ * }
2777
+ * })
2778
+ *
2779
+ * // Access state
2780
+ * const user = context.get('user')
2781
+ * const requestId = context.get('requestId')
2782
+ * ```
2783
+ *
2784
+ * @see {@link Context} for the context interface
2785
+ */
2786
+ declare function createContext<S = Record<string, unknown>>(options: ContextOptions<S>): Context<S>;
2787
+ /**
2788
+ * Context Manager - manages and executes middleware functions
2789
+ *
2790
+ * @remarks
2791
+ * The ContextManager is responsible for registering middleware functions
2792
+ * and executing them in the correct order for various actions (connect,
2793
+ * disconnect, message, subscribe, unsubscribe).
2794
+ *
2795
+ * Key features:
2796
+ * - Global middleware registration
2797
+ * - Per-action middleware execution
2798
+ * - Channel-specific middleware support
2799
+ * - Automatic error wrapping and reporting
2800
+ *
2801
+ * @example
2802
+ * ```ts
2803
+ * import { ContextManager } from '@synca/server'
2804
+ *
2805
+ * const manager = new ContextManager()
2806
+ *
2807
+ * // Register middleware
2808
+ * manager.use(async (context, next) => {
2809
+ * console.log(`Action: ${context.req.action}`)
2810
+ * await next()
2811
+ * })
2812
+ *
2813
+ * // Execute middleware for an action
2814
+ * const context = await manager.executeConnection(client, 'connect')
2815
+ * ```
2816
+ *
2817
+ * @see {@link createSyncaServer} for server-level context management
2818
+ */
2819
+ declare class ContextManager {
2820
+ /** Registered middleware functions */
2821
+ protected readonly middlewares: IMiddleware[];
2822
+ /**
2823
+ * Register a middleware function
2824
+ *
2825
+ * @remarks
2826
+ * Adds a middleware function to the global middleware chain.
2827
+ * Middleware is executed in the order it is registered.
2828
+ *
2829
+ * @param middleware - The middleware function to register
2830
+ *
2831
+ * @example
2832
+ * ```ts
2833
+ * manager.use(async (context, next) => {
2834
+ * console.log('Before')
2835
+ * await next()
2836
+ * console.log('After')
2837
+ * })
2838
+ * ```
2839
+ */
2840
+ use(middleware: IMiddleware): void;
2841
+ /**
2842
+ * Remove a middleware function
2843
+ *
2844
+ * @remarks
2845
+ * Removes a previously registered middleware function from the chain.
2846
+ *
2847
+ * @param middleware - The middleware function to remove
2848
+ * @returns `true` if the middleware was found and removed, `false` otherwise
2849
+ *
2850
+ * @example
2851
+ * ```ts
2852
+ * const middleware = async (context, next) => { /* ... *\/ }
2853
+ * manager.use(middleware)
2854
+ *
2855
+ * // Later, remove it
2856
+ * if (manager.remove(middleware)) {
2857
+ * console.log('Middleware removed')
2858
+ * }
2859
+ * ```
2860
+ */
2861
+ remove(middleware: IMiddleware): boolean;
2862
+ /**
2863
+ * Clear all middleware
2864
+ *
2865
+ * @remarks
2866
+ * Removes all registered middleware functions from the chain.
2867
+ *
2868
+ * @example
2869
+ * ```ts
2870
+ * manager.clear()
2871
+ * console.log('All middleware cleared')
2872
+ * ```
2873
+ */
2874
+ clear(): void;
2875
+ /**
2876
+ * Get all registered middleware
2877
+ *
2878
+ * @remarks
2879
+ * Returns a shallow copy of the middleware array to prevent
2880
+ * external modification.
2881
+ *
2882
+ * @returns Array of middleware functions
2883
+ *
2884
+ * @example
2885
+ * ```ts
2886
+ * const allMiddleware = manager.getMiddlewares()
2887
+ * console.log(`Registered middleware: ${allMiddleware.length}`)
2888
+ * ```
2889
+ */
2890
+ getMiddlewares(): IMiddleware[];
2891
+ /**
2892
+ * Get the complete middleware pipeline
2893
+ *
2894
+ * @remarks
2895
+ * Returns the combined middleware pipeline including global middleware
2896
+ * and any channel-specific middleware from the provided channel instance.
2897
+ *
2898
+ * @param channelInstance - Optional channel instance with middleware
2899
+ * @returns Combined array of middleware functions
2900
+ *
2901
+ * @example
2902
+ * ```ts
2903
+ * const chat = server.createMulticast('chat')
2904
+ * const pipeline = manager.getPipeline(chat)
2905
+ * // Returns global middleware + chat channel middleware
2906
+ * ```
2907
+ *
2908
+ * @internal
2909
+ */
2910
+ getPipeline(channelInstance?: {
2911
+ getMiddlewares?: () => IMiddleware[];
2912
+ }): IMiddleware[];
2913
+ /**
2914
+ * Execute middleware for connection actions
2915
+ *
2916
+ * @remarks
2917
+ * Creates a connection context and executes the middleware pipeline
2918
+ * for connect or disconnect actions.
2919
+ *
2920
+ * @param client - The client connection
2921
+ * @param action - The action ('connect' or 'disconnect')
2922
+ * @returns The executed context
2923
+ *
2924
+ * @example
2925
+ * ```ts
2926
+ * await manager.executeConnection(client, 'connect')
2927
+ * await manager.executeConnection(client, 'disconnect')
2928
+ * ```
2929
+ *
2930
+ * @internal
2931
+ */
2932
+ executeConnection(client: IClientConnection, action: 'connect' | 'disconnect'): Promise<Context>;
2933
+ /**
2934
+ * Execute middleware for message actions
2935
+ *
2936
+ * @remarks
2937
+ * Creates a message context and executes the middleware pipeline
2938
+ * for incoming client messages.
2939
+ *
2940
+ * @param client - The client connection
2941
+ * @param message - The message being processed
2942
+ * @returns The executed context
2943
+ *
2944
+ * @example
2945
+ * ```ts
2946
+ * await manager.executeMessage(client, dataMessage)
2947
+ * ```
2948
+ *
2949
+ * @internal
2950
+ */
2951
+ executeMessage(client: IClientConnection, message: Message): Promise<Context>;
2952
+ /**
2953
+ * Execute middleware for subscribe actions
2954
+ *
2955
+ * @remarks
2956
+ * Creates a subscribe context and executes the middleware pipeline
2957
+ * for channel subscription requests.
2958
+ *
2959
+ * @param client - The client connection
2960
+ * @param channel - The channel name
2961
+ * @param finalHandler - Optional final handler to execute after middleware
2962
+ * @returns The executed context
2963
+ *
2964
+ * @example
2965
+ * ```ts
2966
+ * await manager.executeSubscribe(client, 'chat', async () => {
2967
+ * // Final handler - perform the actual subscription
2968
+ * channel.subscribe(client.id)
2969
+ * })
2970
+ * ```
2971
+ *
2972
+ * @internal
2973
+ */
2974
+ executeSubscribe(client: IClientConnection, channel: ChannelName, finalHandler?: () => Promise<void>): Promise<Context>;
2975
+ /**
2976
+ * Execute middleware for unsubscribe actions
2977
+ *
2978
+ * @remarks
2979
+ * Creates an unsubscribe context and executes the middleware pipeline
2980
+ * for channel unsubscription requests.
2981
+ *
2982
+ * @param client - The client connection
2983
+ * @param channel - The channel name
2984
+ * @param finalHandler - Optional final handler to execute after middleware
2985
+ * @returns The executed context
2986
+ *
2987
+ * @example
2988
+ * ```ts
2989
+ * await manager.executeUnsubscribe(client, 'chat', async () => {
2990
+ * // Final handler - perform the actual unsubscription
2991
+ * channel.unsubscribe(client.id)
2992
+ * })
2993
+ * ```
2994
+ *
2995
+ * @internal
2996
+ */
2997
+ executeUnsubscribe(client: IClientConnection, channel: ChannelName, finalHandler?: () => Promise<void>): Promise<Context>;
2998
+ /**
2999
+ * Execute middleware pipeline
3000
+ *
3001
+ * @remarks
3002
+ * Executes the provided middleware functions in order, wrapping
3003
+ * each to capture and report errors appropriately.
3004
+ *
3005
+ * @param context - The middleware context
3006
+ * @param middlewares - The middleware functions to execute (defaults to registered middleware)
3007
+ * @param finalHandler - Optional final handler to execute after all middleware
3008
+ * @returns The executed context
3009
+ *
3010
+ * @throws {MiddlewareExecutionError} If a middleware function throws an unexpected error
3011
+ *
3012
+ * @example
3013
+ * ```ts
3014
+ * const context = createContext({ action: 'message' })
3015
+ * await manager.execute(context)
3016
+ * ```
3017
+ *
3018
+ * @internal
3019
+ */
3020
+ execute(context: Context, middlewares?: IMiddleware[], finalHandler?: () => Promise<void>): Promise<Context>;
3021
+ /**
3022
+ * Create a connection context
3023
+ *
3024
+ * @remarks
3025
+ * Creates a middleware context for connection or disconnect actions.
3026
+ *
3027
+ * @param client - The client connection
3028
+ * @param action - The action ('connect' or 'disconnect')
3029
+ * @returns A new connection context
3030
+ *
3031
+ * @example
3032
+ * ```ts
3033
+ * const context = manager.createConnectionContext(client, 'connect')
3034
+ * ```
3035
+ *
3036
+ * @internal
3037
+ */
3038
+ createConnectionContext(client: IClientConnection, action: 'connect' | 'disconnect'): Context;
3039
+ /**
3040
+ * Create a message context
3041
+ *
3042
+ * @remarks
3043
+ * Creates a middleware context for message processing.
3044
+ *
3045
+ * @param client - The client connection
3046
+ * @param message - The message being processed
3047
+ * @returns A new message context
3048
+ *
3049
+ * @example
3050
+ * ```ts
3051
+ * const context = manager.createMessageContext(client, dataMessage)
3052
+ * ```
3053
+ *
3054
+ * @internal
3055
+ */
3056
+ createMessageContext(client: IClientConnection, message: Message): Context;
3057
+ /**
3058
+ * Create a subscribe context
3059
+ *
3060
+ * @remarks
3061
+ * Creates a middleware context for channel subscription.
3062
+ *
3063
+ * @param client - The client connection
3064
+ * @param channel - The channel name
3065
+ * @returns A new subscribe context
3066
+ *
3067
+ * @example
3068
+ * ```ts
3069
+ * const context = manager.createSubscribeContext(client, 'chat')
3070
+ * ```
3071
+ *
3072
+ * @internal
3073
+ */
3074
+ createSubscribeContext(client: IClientConnection, channel: ChannelName): Context;
3075
+ /**
3076
+ * Create an unsubscribe context
3077
+ *
3078
+ * @remarks
3079
+ * Creates a middleware context for channel unsubscription.
3080
+ *
3081
+ * @param client - The client connection
3082
+ * @param channel - The channel name
3083
+ * @returns A new unsubscribe context
3084
+ *
3085
+ * @example
3086
+ * ```ts
3087
+ * const context = manager.createUnsubscribeContext(client, 'chat')
3088
+ * ```
3089
+ *
3090
+ * @internal
3091
+ */
3092
+ createUnsubscribeContext(client: IClientConnection, channel: ChannelName): Context;
3093
+ /**
3094
+ * Get the number of registered middleware
3095
+ *
3096
+ * @returns The count of middleware functions
3097
+ *
3098
+ * @example
3099
+ * ```ts
3100
+ * console.log(`Middleware count: ${manager.getCount()}`)
3101
+ * ```
3102
+ */
3103
+ getCount(): number;
3104
+ /**
3105
+ * Check if any middleware is registered
3106
+ *
3107
+ * @returns `true` if at least one middleware is registered, `false` otherwise
3108
+ *
3109
+ * @example
3110
+ * ```ts
3111
+ * if (manager.hasMiddleware()) {
3112
+ * console.log('Middleware is configured')
3113
+ * }
3114
+ * ```
3115
+ */
3116
+ hasMiddleware(): boolean;
3117
+ }
3118
+
3119
+ /**
3120
+ * Errors Module
3121
+ *
3122
+ * @description
3123
+ * Custom error classes for the Synca server. All errors extend from
3124
+ * {@link SyncaError} and include error codes for programmatic handling.
3125
+ *
3126
+ * @remarks
3127
+ * The error hierarchy:
3128
+ *
3129
+ * - {@link SyncaError} - Base error class
3130
+ * - {@link ConfigError} - Server configuration issues
3131
+ * - {@link TransportError} - WebSocket transport issues
3132
+ * - {@link ChannelError} - Channel operation failures
3133
+ * - {@link ClientError} - Client operation failures
3134
+ * - {@link MessageError} - Message processing failures
3135
+ * - {@link ValidationError} - Input validation failures
3136
+ * - {@link StateError} - Invalid state operations
3137
+ * - {@link MiddlewareRejectionError} - Explicit middleware rejections
3138
+ * - {@link MiddlewareExecutionError} - Unexpected middleware errors
3139
+ *
3140
+ * @example
3141
+ * ### Throwing errors
3142
+ * ```ts
3143
+ * import { StateError, ValidationError } from '@synca/server'
3144
+ *
3145
+ * function createChannel(name: string) {
3146
+ * if (!name) {
3147
+ * throw new ValidationError('Channel name is required')
3148
+ * }
3149
+ * if (!server.started) {
3150
+ * throw new StateError('Server must be started first')
3151
+ * }
3152
+ * }
3153
+ * ```
3154
+ *
3155
+ * @example
3156
+ * ### Catching errors
3157
+ * ```ts
3158
+ * import {
3159
+ * SyncaError,
3160
+ * MiddlewareRejectionError,
3161
+ * StateError
3162
+ * } from '@synca/server'
3163
+ *
3164
+ * try {
3165
+ * await server.start()
3166
+ * } catch (error) {
3167
+ * if (error instanceof StateError) {
3168
+ * console.error('Invalid state:', error.message)
3169
+ * } else if (error instanceof MiddlewareRejectionError) {
3170
+ * console.error(`Action rejected: ${error.reason}`)
3171
+ * } else if (error instanceof SyncaError) {
3172
+ * console.error(`[${error.code}] ${error.message}`)
3173
+ * }
3174
+ * }
3175
+ * ```
3176
+ *
3177
+ * @module errors
3178
+ */
3179
+
3180
+ /**
3181
+ * Base Synca error class
3182
+ *
3183
+ * @remarks
3184
+ * All custom errors in the Synca server extend this class. Provides
3185
+ * consistent error handling with error codes, context, and serialization.
3186
+ *
3187
+ * @property code - Error code for programmatic handling
3188
+ * @property context - Optional additional error context
3189
+ *
3190
+ * @example
3191
+ * ```ts
3192
+ * throw new SyncaError('Something went wrong', 'CUSTOM_ERROR', { userId: '123' })
3193
+ * ```
3194
+ *
3195
+ * @example
3196
+ * ### Error handling
3197
+ * ```ts
3198
+ * try {
3199
+ * // ...
3200
+ * } catch (error) {
3201
+ * if (error instanceof SyncaError) {
3202
+ * console.log(error.code) // 'CUSTOM_ERROR'
3203
+ * console.log(error.message) // 'Something went wrong'
3204
+ * console.log(error.context) // { userId: '123' }
3205
+ * console.log(error.toJSON()) // Serialized error
3206
+ * }
3207
+ * }
3208
+ * ```
3209
+ */
3210
+ declare class SyncaError extends Error {
3211
+ /**
3212
+ * Error code for programmatic error handling
3213
+ *
3214
+ * @remarks
3215
+ * Machine-readable error code that can be used for conditional
3216
+ * error handling and error response generation.
3217
+ */
3218
+ readonly code: string;
3219
+ /**
3220
+ * Additional error context (optional)
3221
+ *
3222
+ * @remarks
3223
+ * Arbitrary data attached to the error for debugging or logging.
3224
+ * Common uses include user IDs, request IDs, or validation details.
3225
+ */
3226
+ readonly context?: Record<string, unknown>;
3227
+ /**
3228
+ * Creates a new SyncaError
3229
+ *
3230
+ * @param message - Human-readable error message
3231
+ * @param code - Error code for programmatic handling (default: 'SYNNEL_ERROR')
3232
+ * @param context - Optional additional error context
3233
+ */
3234
+ constructor(message: string, code?: string, context?: Record<string, unknown>);
3235
+ /**
3236
+ * Convert error to JSON for logging/serialization
3237
+ *
3238
+ * @returns JSON representation of the error
3239
+ *
3240
+ * @example
3241
+ * ```ts
3242
+ * const error = new SyncaError('Failed', 'FAIL', { id: 123 })
3243
+ * console.log(JSON.stringify(error.toJSON(), null, 2))
3244
+ * // {
3245
+ * // "name": "SyncaError",
3246
+ * // "message": "Failed",
3247
+ * // "code": "FAIL",
3248
+ * // "context": { "id": 123 },
3249
+ * // "stack": "..."
3250
+ * // }
3251
+ * ```
3252
+ */
3253
+ toJSON(): {
3254
+ name: string;
3255
+ message: string;
3256
+ code: string;
3257
+ context?: Record<string, unknown>;
3258
+ stack?: string;
3259
+ };
3260
+ /**
3261
+ * Get a summary of the error for logging
3262
+ *
3263
+ * @returns Formatted error summary string
3264
+ *
3265
+ * @example
3266
+ * ```ts
3267
+ * const error = new SyncaError('Failed', 'FAIL')
3268
+ * console.log(error.toString())
3269
+ * // "[SyncaError:FAIL] Failed"
3270
+ * ```
3271
+ */
3272
+ toString(): string;
3273
+ }
3274
+ /**
3275
+ * Configuration error
3276
+ *
3277
+ * @remarks
3278
+ * Thrown when server configuration is invalid or missing required values.
3279
+ *
3280
+ * @example
3281
+ * ```ts
3282
+ * if (!config.port) {
3283
+ * throw new ConfigError('Port is required', { config })
3284
+ * }
3285
+ * ```
3286
+ */
3287
+ declare class ConfigError extends SyncaError {
3288
+ constructor(message: string, context?: Record<string, unknown>);
3289
+ }
3290
+ /**
3291
+ * Transport error
3292
+ *
3293
+ * @remarks
3294
+ * Thrown when the transport layer fails (WebSocket connection issues, etc.).
3295
+ *
3296
+ * @example
3297
+ * ```ts
3298
+ * if (!wsServer) {
3299
+ * throw new TransportError('WebSocket server not initialized')
3300
+ * }
3301
+ * ```
3302
+ */
3303
+ declare class TransportError extends SyncaError {
3304
+ constructor(message: string, context?: Record<string, unknown>);
3305
+ }
3306
+ /**
3307
+ * Channel error
3308
+ *
3309
+ * @remarks
3310
+ * Thrown when channel operations fail (invalid channel name, etc.).
3311
+ *
3312
+ * @example
3313
+ * ```ts
3314
+ * if (channelName.startsWith('__')) {
3315
+ * throw new ChannelError('Reserved channel name', { channelName })
3316
+ * }
3317
+ * ```
3318
+ */
3319
+ declare class ChannelError extends SyncaError {
3320
+ constructor(message: string, context?: Record<string, unknown>);
3321
+ }
3322
+ /**
3323
+ * Client error
3324
+ *
3325
+ * @remarks
3326
+ * Thrown when client operations fail (client not found, etc.).
3327
+ *
3328
+ * @example
3329
+ * ```ts
3330
+ * if (!registry.has(clientId)) {
3331
+ * throw new ClientError('Client not found', { clientId })
3332
+ * }
3333
+ * ```
3334
+ */
3335
+ declare class ClientError extends SyncaError {
3336
+ constructor(message: string, context?: Record<string, unknown>);
3337
+ }
3338
+ /**
3339
+ * Message error
3340
+ *
3341
+ * @remarks
3342
+ * Thrown when message processing fails (invalid format, etc.).
3343
+ *
3344
+ * @example
3345
+ * ```ts
3346
+ * if (!message.type) {
3347
+ * throw new MessageError('Invalid message format', { message })
3348
+ * }
3349
+ * ```
3350
+ */
3351
+ declare class MessageError extends SyncaError {
3352
+ constructor(message: string, context?: Record<string, unknown>);
3353
+ }
3354
+ /**
3355
+ * Validation error
3356
+ *
3357
+ * @remarks
3358
+ * Thrown when input validation fails.
3359
+ *
3360
+ * @example
3361
+ * ```ts
3362
+ * if (!isValidChannelName(name)) {
3363
+ * throw new ValidationError('Invalid channel name', { name })
3364
+ * }
3365
+ * ```
3366
+ */
3367
+ declare class ValidationError extends SyncaError {
3368
+ constructor(message: string, context?: Record<string, unknown>);
3369
+ }
3370
+ /**
3371
+ * State error
3372
+ *
3373
+ * @remarks
3374
+ * Thrown when an operation is invalid for the current state.
3375
+ *
3376
+ * @example
3377
+ * ```ts
3378
+ * if (server.started) {
3379
+ * throw new StateError('Server is already started')
3380
+ * }
3381
+ *
3382
+ * if (!server.started) {
3383
+ * throw new StateError('Server must be started first')
3384
+ * }
3385
+ * ```
3386
+ */
3387
+ declare class StateError extends SyncaError {
3388
+ constructor(message: string, context?: Record<string, unknown>);
3389
+ }
3390
+ /**
3391
+ * Middleware rejection error
3392
+ *
3393
+ * @remarks
3394
+ * Thrown when middleware explicitly rejects an action using the
3395
+ * `context.reject()` function. This is an expected error type that
3396
+ * indicates intentional rejection rather than a failure.
3397
+ *
3398
+ * @property reason - Human-readable reason for the rejection
3399
+ * @property action - The action that was rejected
3400
+ * @property code - Optional error code for programmatic handling
3401
+ * @property context - Additional context about the rejection
3402
+ *
3403
+ * @example
3404
+ * ### Throwing from middleware
3405
+ * ```ts
3406
+ * const middleware: Middleware = async (context, next) => {
3407
+ * if (!context.req.client) {
3408
+ * context.reject('Client is required')
3409
+ * // Function never returns (throws MiddlewareRejectionError)
3410
+ * }
3411
+ * await next()
3412
+ * }
3413
+ * ```
3414
+ *
3415
+ * @example
3416
+ * ### Catching rejections
3417
+ * ```ts
3418
+ * try {
3419
+ * await manager.executeConnection(client, 'connect')
3420
+ * } catch (error) {
3421
+ * if (error instanceof MiddlewareRejectionError) {
3422
+ * console.log(`Action '${error.action}' rejected: ${error.reason}`)
3423
+ * // Send error to client
3424
+ * client.socket.send(JSON.stringify({
3425
+ * type: 'error',
3426
+ * data: { message: error.reason, code: error.code }
3427
+ * }))
3428
+ * }
3429
+ * }
3430
+ * ```
3431
+ */
3432
+ declare class MiddlewareRejectionError extends Error implements IMiddlewareRejectionError {
3433
+ /**
3434
+ * The reason for rejection
3435
+ *
3436
+ * @remarks
3437
+ * Human-readable explanation of why the action was rejected.
3438
+ */
3439
+ readonly reason: string;
3440
+ /**
3441
+ * The action that was rejected
3442
+ *
3443
+ * @remarks
3444
+ * One of: 'connect', 'disconnect', 'message', 'subscribe', 'unsubscribe'
3445
+ */
3446
+ readonly action: string;
3447
+ /**
3448
+ * Error name (fixed value for interface compliance)
3449
+ */
3450
+ readonly name = "MiddlewareRejectionError";
3451
+ /**
3452
+ * Optional error code for programmatic handling
3453
+ *
3454
+ * @remarks
3455
+ * Can be used for mapping to client-facing error codes.
3456
+ */
3457
+ readonly code?: string;
3458
+ /**
3459
+ * Additional context about the rejection
3460
+ *
3461
+ * @remarks
3462
+ * Arbitrary data for debugging or logging.
3463
+ */
3464
+ readonly context?: Record<string, unknown>;
3465
+ /**
3466
+ * Creates a new MiddlewareRejectionError
3467
+ *
3468
+ * @param reason - Human-readable reason for the rejection
3469
+ * @param action - The action that was rejected
3470
+ * @param code - Optional error code for programmatic handling
3471
+ * @param context - Additional context about the rejection
3472
+ */
3473
+ constructor(reason: string, action: IMiddlewareAction | string, code?: string, context?: Record<string, unknown>);
3474
+ /**
3475
+ * Convert error to JSON for logging/serialization
3476
+ *
3477
+ * @returns JSON representation of the rejection error
3478
+ *
3479
+ * @example
3480
+ * ```ts
3481
+ * const error = new MiddlewareRejectionError('Not allowed', 'subscribe', 'FORBIDDEN')
3482
+ * console.log(JSON.stringify(error.toJSON(), null, 2))
3483
+ * // {
3484
+ * // "name": "MiddlewareRejectionError",
3485
+ * // "reason": "Not allowed",
3486
+ * // "action": "subscribe",
3487
+ * // "code": "FORBIDDEN",
3488
+ * // "message": "Action 'subscribe' rejected: Not allowed",
3489
+ * // "stack": "..."
3490
+ * // }
3491
+ * ```
3492
+ */
3493
+ toJSON(): {
3494
+ name: string;
3495
+ reason: string;
3496
+ action: string;
3497
+ code?: string;
3498
+ context?: Record<string, unknown>;
3499
+ message: string;
3500
+ stack?: string;
3501
+ };
3502
+ /**
3503
+ * Get a summary of the rejection for logging
3504
+ *
3505
+ * @returns Formatted error summary string
3506
+ *
3507
+ * @example
3508
+ * ```ts
3509
+ * const error = new MiddlewareRejectionError('Not allowed', 'subscribe')
3510
+ * console.log(error.toString())
3511
+ * // "[MiddlewareRejectionError:subscribe] Not allowed"
3512
+ * ```
3513
+ */
3514
+ toString(): string;
3515
+ }
3516
+ /**
3517
+ * Middleware execution error
3518
+ *
3519
+ * @remarks
3520
+ * Thrown when a middleware function throws an unexpected error
3521
+ * (not using `context.reject()`). This indicates a bug or failure
3522
+ * in the middleware rather than an intentional rejection.
3523
+ *
3524
+ * @property action - The action being processed when the error occurred
3525
+ * @property middleware - The name/index of the middleware that failed
3526
+ * @property cause - The original error thrown by the middleware
3527
+ *
3528
+ * @example
3529
+ * ### Error scenario
3530
+ * ```ts
3531
+ * const buggyMiddleware: Middleware = async (context, next) => {
3532
+ * // This throws an unexpected error
3533
+ * JSON.parse(context.req.message as string)
3534
+ * await next()
3535
+ * }
3536
+ * // Results in MiddlewareExecutionError
3537
+ * ```
3538
+ *
3539
+ * @example
3540
+ * ### Catching execution errors
3541
+ * ```ts
3542
+ * try {
3543
+ * await manager.execute(context)
3544
+ * } catch (error) {
3545
+ * if (error instanceof MiddlewareExecutionError) {
3546
+ * console.error(`${error.middleware} failed during ${error.action}:`)
3547
+ * console.error(error.cause)
3548
+ * }
3549
+ * }
3550
+ * ```
3551
+ */
3552
+ declare class MiddlewareExecutionError extends Error {
3553
+ /**
3554
+ * The action being processed when the error occurred
3555
+ */
3556
+ readonly action: string;
3557
+ /**
3558
+ * The name/index of the middleware that failed
3559
+ */
3560
+ readonly middleware: string;
3561
+ /**
3562
+ * The original error thrown by the middleware
3563
+ */
3564
+ readonly cause: Error;
3565
+ /**
3566
+ * Creates a new MiddlewareExecutionError
3567
+ *
3568
+ * @param action - The action being processed
3569
+ * @param middleware - The name/index of the middleware
3570
+ * @param cause - The original error
3571
+ */
3572
+ constructor(action: string, middleware: string, cause: Error);
3573
+ /**
3574
+ * Get the original error cause
3575
+ *
3576
+ * @returns The original error thrown by the middleware
3577
+ *
3578
+ * @example
3579
+ * ```ts
3580
+ * if (error instanceof MiddlewareExecutionError) {
3581
+ * const originalError = error.getCause()
3582
+ * console.error('Original error:', originalError.message)
3583
+ * }
3584
+ * ```
3585
+ */
3586
+ getCause(): Error;
3587
+ /**
3588
+ * Get a summary of the error for logging
3589
+ *
3590
+ * @returns Formatted error summary string
3591
+ *
3592
+ * @example
3593
+ * ```ts
3594
+ * const error = new MiddlewareExecutionError('message', 'auth', originalError)
3595
+ * console.log(error.toString())
3596
+ * // "[MiddlewareExecutionError] auth failed during message: Invalid token"
3597
+ * ```
3598
+ */
3599
+ toString(): string;
3600
+ }
3601
+
3602
+ export { type AckMessage, BROADCAST_CHANNEL, BroadcastChannel, CLOSE_CODES, ChannelError, type ChannelName, ClientError, type ClientId, ConfigError, type Context, ContextManager, type DataMessage, ERROR_CODES, ErrorCode, type ErrorMessage, type IChannelState, type IClientConnection, type IMessageHandler, type IPublishOptions, type IServerOptions, type IServerStats, type Message, MessageError, type MessageId, MessageType, type Middleware, MiddlewareExecutionError, MiddlewareRejectionError, MulticastChannel, type SignalMessage, SignalType, StateError, type SubscriberId, SyncaServer as Synca, SyncaError, SyncaServer, type Timestamp, TransportError, ValidationError, WebSocketServerTransport, createAuthMiddleware, createChannelWhitelistMiddleware, createContext, createLoggingMiddleware, createRateLimitMiddleware, createSyncaServer };