@syncar/server 1.0.0-alpha.2 → 1.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,944 +1,9 @@
1
1
  import * as node_https from 'node:https';
2
2
  import * as node_http from 'node:http';
3
- import { IncomingMessage } from 'node:http';
4
- import WebSocket, { ServerOptions, WebSocketServer } from 'ws';
3
+ import { C as ChannelName, a as ClientId, I as IClientConnection, D as DataMessage, b as IMiddleware, c as ILogger, d as IContext, M as Message, e as IMiddlewareAction, f as IMiddlewareRejectionError } from './types-DeE5vqYX.js';
4
+ export { A as AckMessage, E as ErrorCode, g as ErrorMessage, h as MessageId, i as MessageType, S as SignalMessage, j as SignalType, T as Timestamp } from './types-DeE5vqYX.js';
5
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 Syncar 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 Syncar 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 '@syncar/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 '@syncar/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
- };
6
+ import { ServerOptions, WebSocketServer } from 'ws';
942
7
 
943
8
  /**
944
9
  * Channel state information
@@ -951,16 +16,6 @@ declare const ERROR_CODES: {
951
16
  * @property subscriberCount - Current number of subscribers
952
17
  * @property createdAt - Unix timestamp (ms) when the channel was created
953
18
  * @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
19
  */
965
20
  interface IChannelState {
966
21
  /** The channel name */
@@ -972,678 +27,162 @@ interface IChannelState {
972
27
  /** Unix timestamp (ms) of the last published message */
973
28
  lastMessageAt?: number;
974
29
  }
30
+ type ChannelFlow = 'bidirectional' | 'send-only' | 'receive-only';
975
31
  /**
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
32
+ * Channel creation options
1275
33
  *
1276
34
  * @example
1277
35
  * ```ts
1278
- * const chat = server.createMulticast('chat', { chunkSize: 1000 })
36
+ * const options: ChannelOptions = { flow: 'receive-only' }
1279
37
  * ```
1280
38
  */
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;
39
+ interface ChannelOptions {
40
+ /** Message direction: 'bidirectional', 'send-only', or 'receive-only' */
41
+ flow?: ChannelFlow;
1292
42
  }
1293
43
  /**
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')
44
+ * Base message handler signature
1359
45
  *
1360
- * // Check if subscribed
1361
- * if (chat.hasSubscriber('client-123')) {
1362
- * console.log('Client is subscribed')
1363
- * }
46
+ * @remarks
47
+ * Type-safe handler function for processing incoming messages on a channel.
48
+ * Handlers receive the message data, sending client, and the original message object.
1364
49
  *
1365
- * // Get all subscribers
1366
- * const subscribers = chat.getSubscribers()
1367
- * console.log(`Subscribers: ${Array.from(subscribers).join(', ')}`)
1368
- * ```
50
+ * @template T - Type of data expected in messages
51
+ */
52
+ type IMessageHandler<T> = (
53
+ /** The message data payload */
54
+ data: T,
55
+ /** The client connection that sent the message */
56
+ client: IClientConnection,
57
+ /** The complete message object with metadata */
58
+ message: DataMessage<T>) => void | Promise<void>;
59
+ /**
60
+ * Unified Channel implementation
1369
61
  *
1370
62
  * @example
1371
- * ### Channel middleware
1372
63
  * ```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()
64
+ * const chat = server.createChannel('chat')
65
+ * chat.onMessage((data, client) => {
66
+ * chat.publish(data, [client.id])
1382
67
  * })
1383
68
  * ```
1384
- *
1385
- * @see {@link BroadcastChannel} for broadcasting to all clients
1386
69
  */
1387
- declare class MulticastChannel<T = unknown> extends BaseChannel<T> {
70
+ declare class Channel<T = unknown> {
71
+ /** The channel name */
72
+ readonly name: ChannelName;
73
+ /** The channel flow: 'bidirectional', 'send-only', or 'receive-only' */
74
+ readonly flow: ChannelFlow;
75
+ /** @internal */
76
+ protected readonly registry: ClientRegistry;
77
+ /** @internal */
78
+ protected readonly chunkSize: number;
1388
79
  private readonly middlewares;
1389
80
  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
- */
81
+ private _lastMessageAt?;
82
+ private _createdAt;
83
+ /** @internal */
1403
84
  constructor(config: {
1404
- /** The channel name (must not start with `__`) */
1405
85
  name: ChannelName;
1406
- /** The client registry for connection management */
1407
86
  registry: ClientRegistry;
1408
- /** Optional channel configuration */
1409
- options?: MulticastChannelOptions;
87
+ options?: ChannelOptions;
88
+ chunkSize?: number;
1410
89
  });
1411
90
  /**
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
91
+ * Subscribe a client to this channel
92
+ * @param subscriber - Client ID to subscribe
93
+ * @returns `true` if subscribed successfully
1422
94
  */
1423
- protected getTargetClients(_options?: IPublishOptions): ClientId[];
95
+ subscribe(subscriber: ClientId): boolean;
1424
96
  /**
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
97
+ * Unsubscribe a client from this channel
98
+ * @param subscriber - Client ID to unsubscribe
99
+ * @returns `true` if unsubscribed successfully
1448
100
  */
1449
- use(middleware: IMiddleware): void;
101
+ unsubscribe(subscriber: ClientId): boolean;
1450
102
  /**
1451
- * Get the middleware for this channel
1452
- *
1453
- * @returns Array of middleware functions registered on this channel
103
+ * Check if a client is subscribed to this channel
104
+ * @param subscriber - The client ID to check
1454
105
  */
1455
- getMiddlewares(): IMiddleware[];
106
+ hasSubscriber(subscriber: ClientId): boolean;
1456
107
  /**
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
- * ```
108
+ * Get all subscriber IDs for this channel
109
+ */
110
+ getSubscribers(): Set<ClientId>;
111
+ /**
112
+ * Get the current count of subscribers
1465
113
  */
1466
114
  get subscriberCount(): number;
1467
115
  /**
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.
116
+ * Check if the channel has no subscribers
1493
117
  */
1494
- onMessage(handler: IMessageHandler<T>): () => void;
118
+ isEmpty(): boolean;
1495
119
  /**
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
- * ```
120
+ * Publish data to all subscribers
121
+ * @param data - The data to publish
122
+ * @param exclude - Optional array of client IDs to exclude
1514
123
  */
1515
- subscribe(subscriber: SubscriberId): boolean;
124
+ publish(data: T, exclude?: ClientId[]): void;
1516
125
  /**
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')
126
+ * Register a message handler for incoming client data
1530
127
  *
1531
- * if (chat.unsubscribe('client-456')) {
1532
- * console.log('Unsubscribed successfully')
1533
- * }
1534
- * ```
128
+ * @returns Function to remove the handler
1535
129
  */
1536
- unsubscribe(subscriber: SubscriberId): boolean;
130
+ onMessage(handler: IMessageHandler<T>): () => void;
1537
131
  /**
1538
132
  * Dispatch an incoming client message
1539
133
  *
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
134
  * @internal
1573
135
  */
1574
136
  dispatch(data: T, client: IClientConnection, message: DataMessage<T>): Promise<void>;
1575
137
  /**
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
- * ```
138
+ * Register channel-specific middleware
1587
139
  */
1588
- hasSubscriber(subscriber: SubscriberId): boolean;
140
+ use(middleware: IMiddleware): void;
1589
141
  /**
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
- * ```
142
+ * Get registered middleware for this channel
1608
143
  */
1609
- getSubscribers(): Set<SubscriberId>;
144
+ getMiddlewares(): IMiddleware[];
1610
145
  /**
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
- * ```
146
+ * Get runtime channel statistics
1621
147
  */
1622
- isEmpty(): boolean;
148
+ getState(): IChannelState;
149
+ }
150
+
151
+ /**
152
+ * Client Registry
153
+ * Manages connected clients, their subscriptions, and channel instances.
154
+ */
155
+ declare class ClientRegistry {
156
+ readonly connections: Map<ClientId, IClientConnection>;
157
+ private readonly subscriptions;
158
+ private readonly channels;
159
+ private readonly channelInstances;
160
+ readonly logger?: ILogger;
161
+ constructor(logger?: ILogger);
162
+ register(connection: IClientConnection): IClientConnection;
163
+ unregister(clientId: ClientId): boolean;
164
+ private clearSubscriptions;
165
+ get(clientId: ClientId): IClientConnection | undefined;
166
+ getAll(): IClientConnection[];
167
+ getCount(): number;
168
+ registerChannel(channel: Channel<any>): void;
169
+ getChannel<T = unknown>(name: ChannelName): Channel<T> | undefined;
170
+ removeChannel(name: ChannelName): boolean;
171
+ subscribe(clientId: ClientId, channel: ChannelName): boolean;
172
+ unsubscribe(clientId: ClientId, channel: ChannelName): boolean;
173
+ getSubscribers(channel: ChannelName): IClientConnection[];
174
+ getSubscriberCount(channel: ChannelName): number;
175
+ getChannels(): ChannelName[];
176
+ getTotalSubscriptionCount(): number;
177
+ isSubscribed(clientId: ClientId, channel: ChannelName): boolean;
178
+ getClientChannels(clientId: ClientId): Set<ChannelName>;
179
+ getChannelSubscribers(channel: ChannelName): Set<ClientId>;
180
+ clear(): void;
1623
181
  }
1624
182
 
1625
183
  type ServerInstance = WebSocketServer;
1626
184
  /**
1627
185
  * WebSocket Server Transport Configuration
1628
- *
1629
- * @remarks
1630
- * Configuration options for the WebSocket transport layer. Extends the
1631
- * standard `ws` library options with Syncar-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
186
  */
1648
187
  interface WebSocketServerTransportConfig extends ServerOptions {
1649
188
  /**
@@ -1682,22 +221,6 @@ interface WebSocketServerTransportConfig extends ServerOptions {
1682
221
  * If not provided, a new map will be created.
1683
222
  */
1684
223
  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
224
  /**
1702
225
  * Custom WebSocket Server constructor
1703
226
  *
@@ -1706,84 +229,15 @@ interface WebSocketServerTransportConfig extends ServerOptions {
1706
229
  * Defaults to the standard `ws` WebSocketServer.
1707
230
  */
1708
231
  ServerConstructor?: new (config: ServerOptions) => ServerInstance;
1709
- /**
1710
- * Logger instance
1711
- *
1712
- * @remarks
1713
- * Optional logger for transport-level logging.
1714
- */
1715
- logger?: ILogger;
1716
232
  }
1717
233
  /**
1718
234
  * WebSocket Server Transport
1719
235
  *
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 '@syncar/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
236
  * @example
1773
- * ### With shared connections
1774
237
  * ```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
- * })
238
+ * const transport = new WebSocketServerTransport({ server: httpServer, path: '/ws' })
239
+ * transport.on('connection', (client) => console.log(client.id))
1784
240
  * ```
1785
- *
1786
- * @see {@link EventEmitter} for event methods (on, off, emit, etc.)
1787
241
  */
1788
242
  declare class WebSocketServerTransport extends EventEmitter {
1789
243
  /**
@@ -1800,8 +254,6 @@ declare class WebSocketServerTransport extends EventEmitter {
1800
254
  private readonly config;
1801
255
  /** @internal */
1802
256
  private pingTimer?;
1803
- /** @internal */
1804
- private authenticator?;
1805
257
  /**
1806
258
  * Creates a new WebSocket Server Transport instance
1807
259
  *
@@ -1829,29 +281,6 @@ declare class WebSocketServerTransport extends EventEmitter {
1829
281
  * @emits error When an error occurs
1830
282
  */
1831
283
  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
284
  private setupEventHandlers;
1856
285
  private handleConnection;
1857
286
  private handleMessage;
@@ -1861,29 +290,6 @@ declare class WebSocketServerTransport extends EventEmitter {
1861
290
  private checkConnections;
1862
291
  }
1863
292
 
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 = createSyncarServer({ 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
293
  interface IServerStats {
1888
294
  /** Number of currently connected clients */
1889
295
  clientCount: number;
@@ -1894,30 +300,8 @@ interface IServerStats {
1894
300
  /** Unix timestamp (ms) when the server was started */
1895
301
  startedAt?: number;
1896
302
  }
1897
-
1898
303
  /**
1899
304
  * Server configuration options
1900
- *
1901
- * @remarks
1902
- * Complete configuration interface for the Syncar server. These options
1903
- * control the WebSocket transport layer, connection handling, middleware,
1904
- * and performance tuning parameters.
1905
- *
1906
- * @example
1907
- * ```ts
1908
- * import { createSyncarServer } from '@syncar/server'
1909
- *
1910
- * const server = createSyncarServer({
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
305
  * @see {@link DEFAULT_SERVER_CONFIG} for default values
1922
306
  */
1923
307
  interface IServerOptions {
@@ -1928,23 +312,7 @@ interface IServerOptions {
1928
312
  * If provided, the WebSocket server will attach to this existing server.
1929
313
  * If not provided, a new HTTP server will be created automatically.
1930
314
  */
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;
315
+ server?: node_http.Server | node_https.Server;
1948
316
  /**
1949
317
  * Custom logger instance
1950
318
  *
@@ -2027,9 +395,9 @@ interface IServerOptions {
2027
395
  * @example
2028
396
  * ```ts
2029
397
  * middleware: [
2030
- * createAuthMiddleware({ verifyToken }),
2031
- * createLoggingMiddleware(),
2032
- * createRateLimitMiddleware({ maxRequests: 100 })
398
+ * authenticate({ verifyToken }),
399
+ * logger(),
400
+ * rateLimit({ maxRequests: 100 })
2033
401
  * ]
2034
402
  * ```
2035
403
  */
@@ -2042,37 +410,19 @@ interface IServerOptions {
2042
410
  * in chunks to avoid blocking the event loop. Lower values reduce latency
2043
411
  * per chunk but increase total broadcast time.
2044
412
  */
2045
- broadcastChunkSize: number;
413
+ chunkSize: number;
2046
414
  }
2047
415
  /**
2048
416
  * Syncar Server - Real-time WebSocket server with pub/sub channels
2049
417
  *
2050
- * @remarks
2051
- * The main server class providing WebSocket communication with broadcast
2052
- * and multicast channels, middleware support, and connection management.
2053
- *
2054
418
  * @example
2055
419
  * ```ts
2056
- * import { createSyncarServer } from '@syncar/server'
2057
- *
2058
420
  * const server = createSyncarServer({ port: 3000 })
2059
421
  * await server.start()
2060
422
  *
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!' })
423
+ * const chat = server.createChannel('chat')
424
+ * chat.publish('Hello!')
2073
425
  * ```
2074
- *
2075
- * @see {@link createSyncarServer} for factory function
2076
426
  */
2077
427
  declare class SyncarServer {
2078
428
  private readonly config;
@@ -2083,25 +433,24 @@ declare class SyncarServer {
2083
433
  private connectionHandler;
2084
434
  private messageHandler;
2085
435
  private signalHandler;
2086
- private broadcastChannel;
2087
436
  constructor(config: IServerOptions);
2088
437
  /**
2089
438
  * Start the server and begin accepting connections
2090
439
  *
2091
440
  * @remarks
2092
441
  * Initializes the WebSocket transport layer, sets up event handlers,
2093
- * creates the broadcast channel, and prepares the server for connections.
442
+ * and prepares the server for connections.
2094
443
  *
2095
444
  * @throws {StateError} If the server is already started
2096
445
  *
2097
446
  * @example
2098
447
  * ```ts
2099
448
  * const server = createSyncarServer({ port: 3000 })
2100
- * await server.start()
449
+ * server.start()
2101
450
  * console.log('Server is running')
2102
451
  * ```
2103
452
  */
2104
- start(): Promise<void>;
453
+ start(): void;
2105
454
  /**
2106
455
  * Stop the server and close all connections
2107
456
  *
@@ -2112,88 +461,34 @@ declare class SyncarServer {
2112
461
  *
2113
462
  * @example
2114
463
  * ```ts
2115
- * await server.stop()
464
+ * server.stop()
2116
465
  * console.log('Server stopped')
2117
466
  * ```
2118
467
  */
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>;
468
+ stop(): void;
2153
469
  /**
2154
- * Create or retrieve a multicast channel
470
+ * Create or retrieve a channel
2155
471
  *
2156
472
  * @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.
473
+ * Unified channel creation.
2160
474
  *
2161
475
  * @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
476
+ * @param name - Unique channel name
477
+ * @param options - Channel configuration options
478
+ * @returns The channel instance
2164
479
  *
2165
480
  * @throws {StateError} If the server hasn't been started yet
2166
- * @throws {ValidationError} If the channel name is invalid (starts with `__`)
2167
481
  *
2168
482
  * @example
483
+ * ### Default: bidirectional (chat room)
2169
484
  * ```ts
2170
- * // Create a chat channel
2171
- * const chat = server.createMulticast<{ text: string; user: string }>('chat')
2172
- *
2173
- * // Handle incoming messages
485
+ * const chat = server.createChannel('chat')
2174
486
  * 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] })
487
+ * chat.publish(data, [client.id]) // Relay to others, excluding sender
2178
488
  * })
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
489
  * ```
2192
- *
2193
- * @see {@link MulticastChannel} for channel API
2194
- * @see {@link BROADCAST_CHANNEL} for reserved channel name
2195
490
  */
2196
- createMulticast<T = unknown>(name: ChannelName): MulticastChannel<T>;
491
+ createChannel<T = unknown>(name: ChannelName, options?: ChannelOptions): Channel<T>;
2197
492
  /**
2198
493
  * Check if a channel exists
2199
494
  *
@@ -2203,525 +498,100 @@ declare class SyncarServer {
2203
498
  * @example
2204
499
  * ```ts
2205
500
  * if (!server.hasChannel('chat')) {
2206
- * const chat = server.createMulticast('chat')
501
+ * const chat = server.createChannel('chat')
2207
502
  * }
2208
503
  * ```
2209
504
  */
2210
505
  hasChannel(name: ChannelName): boolean;
2211
506
  /**
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 '@syncar/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
507
+ * Broadcast data to all connected clients
2247
508
  *
2248
509
  * @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.
510
+ * A convenience method to send data to every client currently connected
511
+ * to the server. This uses the `publishInChunks` utility to efficiently
512
+ * broadcast messages without blocking the event loop.
2253
513
  *
2254
- * @param authenticator - A function that receives the HTTP upgrade request
2255
- * and returns a ClientId (or throws to reject the connection)
514
+ * @param data - The data to broadcast to all clients
2256
515
  *
2257
516
  * @example
2258
517
  * ```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
518
+ * server.broadcast({
519
+ * type: 'announcement',
520
+ * content: 'Server update in progress'
2266
521
  * })
2267
522
  * ```
2268
523
  */
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 Syncar server with automatic WebSocket transport setup
2328
- *
2329
- * @remarks
2330
- * Factory function that creates a configured SyncarServer 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 Syncar server instance ready to be started
2338
- *
2339
- * @example
2340
- * ### Basic usage
2341
- * ```ts
2342
- * import { createSyncarServer } from '@syncar/server'
2343
- *
2344
- * const server = createSyncarServer({ port: 3000 })
2345
- * await server.start()
2346
- * ```
2347
- *
2348
- * @example
2349
- * ### With custom configuration
2350
- * ```ts
2351
- * const server = createSyncarServer({
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 { createSyncarServer } from '@syncar/server'
2368
- *
2369
- * const httpServer = createServer((req, res) => {
2370
- * res.writeHead(200)
2371
- * res.end('OK')
2372
- * })
2373
- *
2374
- * const server = createSyncarServer({
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 { createSyncarServer } from '@syncar/server'
2387
- *
2388
- * const app = express()
2389
- * const httpServer = app.listen(3000)
2390
- *
2391
- * const server = createSyncarServer({
2392
- * server: httpServer,
2393
- * path: '/ws',
2394
- * })
2395
- *
2396
- * await server.start()
2397
- * ```
2398
- *
2399
- * @example
2400
- * ### With custom logger
2401
- * ```ts
2402
- * import { createSyncarServer } from '@syncar/server'
2403
- *
2404
- * const server = createSyncarServer({
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 { createSyncarServer, createLoggingMiddleware } from '@syncar/server'
2419
- *
2420
- * const server = createSyncarServer({
2421
- * port: 3000,
2422
- * middleware: [
2423
- * createLoggingMiddleware(),
2424
- * ],
2425
- * })
2426
- *
2427
- * await server.start()
2428
- * ```
2429
- *
2430
- * @see {@link SyncarServer} for server class API
2431
- * @see {@link DEFAULT_SERVER_CONFIG} for default configuration values
2432
- */
2433
- declare function createSyncarServer(config?: Partial<IServerOptions>): SyncarServer;
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 '@syncar/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 '@syncar/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 '@syncar/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 {
524
+ broadcast<T = unknown>(data: T): void;
2653
525
  /**
2654
- * List of allowed channels
2655
- * If isDynamic is true, this is used as a fallback
526
+ * Get all active channel names
527
+ *
528
+ * @returns Array of channel names currently registered on the server
529
+ *
530
+ * @example
531
+ * ```ts
532
+ * const channels = server.getChannels()
533
+ * console.log('Active channels:', channels)
534
+ * // ['chat', 'notifications', 'presence']
535
+ * ```
2656
536
  */
2657
- allowedChannels?: ChannelName[];
537
+ getChannels(): ChannelName[];
2658
538
  /**
2659
- * Dynamic check function for channel access
2660
- * If provided, this takes precedence over allowedChannels
539
+ * Register a global middleware
540
+ *
541
+ * @remarks
542
+ * Adds a middleware function to the global middleware chain.
543
+ * Global middleware runs before channel-specific middleware for all actions.
2661
544
  *
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
545
+ * @param middleware - The middleware function to register
2665
546
  *
2666
547
  * @example
2667
548
  * ```ts
2668
- * const isDynamic: (channel, client) => {
2669
- * // Check if user has permission for this channel
2670
- * return user.permissions.includes(channel)
2671
- * }
549
+ * import { authenticate } from '@syncar/server'
550
+ *
551
+ * server.use(authenticate({
552
+ * verifyToken: async (token) => jwt.verify(token, SECRET)
553
+ * }))
2672
554
  * ```
555
+ *
556
+ * @see {@link IMiddleware} for middleware interface
557
+ */
558
+ use(middleware: IMiddleware): void;
559
+ /**
560
+ * Get server statistics
561
+ *
562
+ * @returns Server statistics including client count, channel count,
563
+ * subscription count, and start time
564
+ * @see {@link IServerStats} for statistics structure
2673
565
  */
2674
- isDynamic?: (channel: ChannelName, client?: IClientConnection) => boolean;
566
+ getStats(): IServerStats;
2675
567
  /**
2676
- * Whether to also check unsubscribe actions
2677
- * @default false (only restrict subscribe)
568
+ * Get the server configuration (read-only)
569
+ *
570
+ * @returns Readonly copy of the server configuration options
2678
571
  */
2679
- restrictUnsubscribe?: boolean;
572
+ getConfig(): Readonly<IServerOptions>;
573
+ private setupTransportHandlers;
2680
574
  }
2681
575
  /**
2682
- * Create a channel whitelist middleware
2683
- *
2684
- * Restricts which channels clients can subscribe to.
576
+ * Create a Syncar server with automatic WebSocket transport setup
2685
577
  *
2686
- * @param options - Channel whitelist options
2687
- * @returns Middleware function
578
+ * @param config - Optional partial server configuration
579
+ * @returns Configured Syncar server instance
2688
580
  *
2689
581
  * @example
2690
582
  * ```ts
2691
- * import { createChannelWhitelistMiddleware } from '@syncar/server/middleware'
2692
- *
2693
- * const whitelistMiddleware = createChannelWhitelistMiddleware({
2694
- * allowedChannels: ['chat', 'notifications']
2695
- * })
2696
- *
2697
- * server.use(whitelistMiddleware)
583
+ * const server = createSyncarServer({ port: 3000 })
584
+ * server.start()
2698
585
  * ```
2699
586
  */
2700
- declare function createChannelWhitelistMiddleware(options?: ChannelWhitelistMiddlewareOptions): IMiddleware;
587
+ declare function createSyncarServer(config?: Partial<IServerOptions>): SyncarServer;
2701
588
 
2702
589
  /**
2703
590
  * Context Data Options
2704
591
  *
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
592
  * @example
2717
593
  * ```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
- * }
594
+ * const options: ContextOptions = { action: 'message', channel: 'chat' }
2725
595
  * ```
2726
596
  */
2727
597
  interface ContextOptions<S = Record<string, unknown>> {
@@ -2737,105 +607,32 @@ interface ContextOptions<S = Record<string, unknown>> {
2737
607
  initialState?: S;
2738
608
  }
2739
609
  /**
2740
- * Create onion pattern 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
- * ```
610
+ * Create a new Onion-style middleware context
2762
611
  *
2763
612
  * @example
2764
- * ### With initial state
2765
613
  * ```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')
614
+ * const context = createContext({ action: 'message', channel: 'chat' })
2782
615
  * ```
2783
- *
2784
- * @see {@link Context} for the context interface
2785
616
  */
2786
- declare function createContext<S = Record<string, unknown>>(options: ContextOptions<S>): Context<S>;
617
+ declare function createContext<S = Record<string, unknown>>(options: ContextOptions<S>): IContext<S>;
2787
618
  /**
2788
619
  * Context Manager - manages and executes middleware functions
2789
620
  *
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
621
  * @example
2802
622
  * ```ts
2803
- * import { ContextManager } from '@syncar/server'
2804
- *
2805
623
  * const manager = new ContextManager()
2806
- *
2807
- * // Register middleware
2808
- * manager.use(async (context, next) => {
2809
- * console.log(`Action: ${context.req.action}`)
624
+ * manager.use(async (ctx, next) => {
625
+ * console.log(ctx.req.action)
2810
626
  * await next()
2811
627
  * })
2812
- *
2813
- * // Execute middleware for an action
2814
- * const context = await manager.executeConnection(client, 'connect')
2815
628
  * ```
2816
- *
2817
- * @see {@link createSyncarServer} for server-level context management
2818
629
  */
2819
630
  declare class ContextManager {
2820
631
  /** Registered middleware functions */
2821
632
  protected readonly middlewares: IMiddleware[];
2822
633
  /**
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
- * ```
634
+ * Register a global middleware function
635
+ * @param middleware - The middleware function
2839
636
  */
2840
637
  use(middleware: IMiddleware): void;
2841
638
  /**
@@ -2929,7 +726,7 @@ declare class ContextManager {
2929
726
  *
2930
727
  * @internal
2931
728
  */
2932
- executeConnection(client: IClientConnection, action: 'connect' | 'disconnect'): Promise<Context>;
729
+ executeConnection(client: IClientConnection, action: 'connect' | 'disconnect'): Promise<IContext>;
2933
730
  /**
2934
731
  * Execute middleware for message actions
2935
732
  *
@@ -2948,7 +745,7 @@ declare class ContextManager {
2948
745
  *
2949
746
  * @internal
2950
747
  */
2951
- executeMessage(client: IClientConnection, message: Message): Promise<Context>;
748
+ executeMessage(client: IClientConnection, message: Message): Promise<IContext>;
2952
749
  /**
2953
750
  * Execute middleware for subscribe actions
2954
751
  *
@@ -2971,7 +768,7 @@ declare class ContextManager {
2971
768
  *
2972
769
  * @internal
2973
770
  */
2974
- executeSubscribe(client: IClientConnection, channel: ChannelName, finalHandler?: () => Promise<void>): Promise<Context>;
771
+ executeSubscribe(client: IClientConnection, channel: ChannelName, finalHandler?: () => Promise<void>): Promise<IContext>;
2975
772
  /**
2976
773
  * Execute middleware for unsubscribe actions
2977
774
  *
@@ -2994,7 +791,7 @@ declare class ContextManager {
2994
791
  *
2995
792
  * @internal
2996
793
  */
2997
- executeUnsubscribe(client: IClientConnection, channel: ChannelName, finalHandler?: () => Promise<void>): Promise<Context>;
794
+ executeUnsubscribe(client: IClientConnection, channel: ChannelName, finalHandler?: () => Promise<void>): Promise<IContext>;
2998
795
  /**
2999
796
  * Execute middleware pipeline
3000
797
  *
@@ -3017,7 +814,7 @@ declare class ContextManager {
3017
814
  *
3018
815
  * @internal
3019
816
  */
3020
- execute(context: Context, middlewares?: IMiddleware[], finalHandler?: () => Promise<void>): Promise<Context>;
817
+ execute(context: IContext, middlewares?: IMiddleware[], finalHandler?: () => Promise<void>): Promise<IContext>;
3021
818
  /**
3022
819
  * Create a connection context
3023
820
  *
@@ -3035,7 +832,7 @@ declare class ContextManager {
3035
832
  *
3036
833
  * @internal
3037
834
  */
3038
- createConnectionContext(client: IClientConnection, action: 'connect' | 'disconnect'): Context;
835
+ createConnectionContext(client: IClientConnection, action: 'connect' | 'disconnect'): IContext;
3039
836
  /**
3040
837
  * Create a message context
3041
838
  *
@@ -3053,7 +850,7 @@ declare class ContextManager {
3053
850
  *
3054
851
  * @internal
3055
852
  */
3056
- createMessageContext(client: IClientConnection, message: Message): Context;
853
+ createMessageContext(client: IClientConnection, message: Message): IContext;
3057
854
  /**
3058
855
  * Create a subscribe context
3059
856
  *
@@ -3071,7 +868,7 @@ declare class ContextManager {
3071
868
  *
3072
869
  * @internal
3073
870
  */
3074
- createSubscribeContext(client: IClientConnection, channel: ChannelName): Context;
871
+ createSubscribeContext(client: IClientConnection, channel: ChannelName): IContext;
3075
872
  /**
3076
873
  * Create an unsubscribe context
3077
874
  *
@@ -3089,7 +886,7 @@ declare class ContextManager {
3089
886
  *
3090
887
  * @internal
3091
888
  */
3092
- createUnsubscribeContext(client: IClientConnection, channel: ChannelName): Context;
889
+ createUnsubscribeContext(client: IClientConnection, channel: ChannelName): IContext;
3093
890
  /**
3094
891
  * Get the number of registered middleware
3095
892
  *
@@ -3118,93 +915,14 @@ declare class ContextManager {
3118
915
 
3119
916
  /**
3120
917
  * Errors Module
3121
- *
3122
- * @description
3123
- * Custom error classes for the Syncar server. All errors extend from
3124
- * {@link SyncarError} and include error codes for programmatic handling.
3125
- *
3126
- * @remarks
3127
- * The error hierarchy:
3128
- *
3129
- * - {@link SyncarError} - 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 '@syncar/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
- * SyncarError,
3160
- * MiddlewareRejectionError,
3161
- * StateError
3162
- * } from '@syncar/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 SyncarError) {
3172
- * console.error(`[${error.code}] ${error.message}`)
3173
- * }
3174
- * }
3175
- * ```
3176
- *
3177
- * @module errors
918
+ * @description Custom error classes for the Syncar server.
3178
919
  */
3179
920
 
3180
921
  /**
3181
922
  * Base Syncar error class
3182
- *
3183
- * @remarks
3184
- * All custom errors in the Syncar 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 SyncarError('Something went wrong', 'CUSTOM_ERROR', { userId: '123' })
3193
- * ```
3194
- *
3195
923
  * @example
3196
- * ### Error handling
3197
924
  * ```ts
3198
- * try {
3199
- * // ...
3200
- * } catch (error) {
3201
- * if (error instanceof SyncarError) {
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
- * }
925
+ * throw new SyncarError('Something went wrong', 'INTERNAL_ERROR')
3208
926
  * ```
3209
927
  */
3210
928
  declare class SyncarError extends Error {
@@ -3389,44 +1107,9 @@ declare class StateError extends SyncarError {
3389
1107
  }
3390
1108
  /**
3391
1109
  * 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
1110
  * @example
3416
- * ### Catching rejections
3417
1111
  * ```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
- * }
1112
+ * if (!context.req.client) context.reject('Client is required')
3430
1113
  * ```
3431
1114
  */
3432
1115
  declare class MiddlewareRejectionError extends Error implements IMiddlewareRejectionError {
@@ -3599,4 +1282,131 @@ declare class MiddlewareExecutionError extends Error {
3599
1282
  toString(): string;
3600
1283
  }
3601
1284
 
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, SyncarServer as Syncar, SyncarError, SyncarServer, type Timestamp, TransportError, ValidationError, WebSocketServerTransport, createAuthMiddleware, createChannelWhitelistMiddleware, createContext, createLoggingMiddleware, createRateLimitMiddleware, createSyncarServer };
1285
+ /**
1286
+ * Config Module
1287
+ * Single source of truth for all constant values and default configuration for the server.
1288
+ *
1289
+ * @module config
1290
+ */
1291
+ /**
1292
+ * WebSocket close codes
1293
+ * Based on RFC 6455 and custom codes for application-specific closures
1294
+ */
1295
+ declare const CLOSE_CODES: {
1296
+ /**
1297
+ * Normal closure
1298
+ */
1299
+ readonly NORMAL: 1000;
1300
+ /**
1301
+ * Endpoint is going away
1302
+ */
1303
+ readonly GOING_AWAY: 1001;
1304
+ /**
1305
+ * Protocol error
1306
+ */
1307
+ readonly PROTOCOL_ERROR: 1002;
1308
+ /**
1309
+ * Unsupported data
1310
+ */
1311
+ readonly UNSUPPORTED_DATA: 1003;
1312
+ /**
1313
+ * No status received
1314
+ */
1315
+ readonly NO_STATUS: 1005;
1316
+ /**
1317
+ * Abnormal closure
1318
+ */
1319
+ readonly ABNORMAL: 1006;
1320
+ /**
1321
+ * Invalid frame payload data
1322
+ */
1323
+ readonly INVALID_PAYLOAD: 1007;
1324
+ /**
1325
+ * Policy violation
1326
+ */
1327
+ readonly POLICY_VIOLATION: 1008;
1328
+ /**
1329
+ * Message too big
1330
+ */
1331
+ readonly MESSAGE_TOO_BIG: 1009;
1332
+ /**
1333
+ * Missing extension
1334
+ */
1335
+ readonly MISSING_EXTENSION: 1010;
1336
+ /**
1337
+ * Internal error
1338
+ */
1339
+ readonly INTERNAL_ERROR: 1011;
1340
+ /**
1341
+ * Service restart
1342
+ */
1343
+ readonly SERVICE_RESTART: 1012;
1344
+ /**
1345
+ * Try again later
1346
+ */
1347
+ readonly TRY_AGAIN_LATER: 1013;
1348
+ /**
1349
+ * Connection rejected by middleware
1350
+ */
1351
+ readonly REJECTED: 4001;
1352
+ /**
1353
+ * Rate limit exceeded
1354
+ */
1355
+ readonly RATE_LIMITED: 4002;
1356
+ /**
1357
+ * Channel not found
1358
+ */
1359
+ readonly CHANNEL_NOT_FOUND: 4003;
1360
+ /**
1361
+ * Unauthorized
1362
+ */
1363
+ readonly UNAUTHORIZED: 4005;
1364
+ };
1365
+ /**
1366
+ * Application error codes
1367
+ * Used in error messages sent to clients
1368
+ */
1369
+ declare const ERROR_CODES: {
1370
+ /**
1371
+ * Action rejected by middleware
1372
+ */
1373
+ readonly REJECTED: "REJECTED";
1374
+ /**
1375
+ * Channel name missing from message
1376
+ */
1377
+ readonly MISSING_CHANNEL: "MISSING_CHANNEL";
1378
+ /**
1379
+ * Subscribe action rejected
1380
+ */
1381
+ readonly SUBSCRIBE_REJECTED: "SUBSCRIBE_REJECTED";
1382
+ /**
1383
+ * Unsubscribe action rejected
1384
+ */
1385
+ readonly UNSUBSCRIBE_REJECTED: "UNSUBSCRIBE_REJECTED";
1386
+ /**
1387
+ * Rate limit exceeded
1388
+ */
1389
+ readonly RATE_LIMITED: "RATE_LIMITED";
1390
+ /**
1391
+ * Authentication failed
1392
+ */
1393
+ readonly AUTH_FAILED: "AUTH_FAILED";
1394
+ /**
1395
+ * Authorization failed
1396
+ */
1397
+ readonly NOT_AUTHORIZED: "NOT_AUTHORIZED";
1398
+ /**
1399
+ * Channel not allowed
1400
+ */
1401
+ readonly CHANNEL_NOT_ALLOWED: "CHANNEL_NOT_ALLOWED";
1402
+ /**
1403
+ * Invalid message format
1404
+ */
1405
+ readonly INVALID_MESSAGE: "INVALID_MESSAGE";
1406
+ /**
1407
+ * Server error
1408
+ */
1409
+ readonly SERVER_ERROR: "SERVER_ERROR";
1410
+ };
1411
+
1412
+ export { CLOSE_CODES, Channel, ChannelError, type ChannelFlow, ChannelName, type ChannelOptions, ClientError, ClientId, ConfigError, ContextManager, DataMessage, ERROR_CODES, type IChannelState, IContext, type IMessageHandler, IMiddleware, type IServerOptions, type IServerStats, Message, MessageError, MiddlewareExecutionError, MiddlewareRejectionError, StateError, SyncarServer as Syncar, SyncarError, SyncarServer, TransportError, ValidationError, WebSocketServerTransport, createContext, createSyncarServer };