@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.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/config.ts","../src/channel.ts","../src/errors.ts","../src/handlers/signal.ts","../src/handlers/messages.ts","../src/handlers/connections.ts","../src/compose.ts","../src/context.ts","../src/registry.ts","../src/websocket.ts","../src/server.ts","../src/middleware/factories.ts"],"sourcesContent":["import type {\n MessageId,\n ClientId,\n ChannelName,\n Message,\n DataMessage,\n SignalMessage,\n SignalType,\n ILogger,\n} from './types'\nimport { MessageType } from './types'\n\n// ============================================================\n// ID GENERATION UTILITIES\n// ============================================================\n\n/**\n * Generate a unique message ID\n * Format: timestamp-randomString\n */\nexport function generateMessageId(): MessageId {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`\n}\n\n/**\n * Generate a unique client ID\n * Format: client-timestamp-randomString\n */\nexport function generateClientId(): ClientId {\n return `client-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n}\n\n// ============================================================\n// VALIDATION UTILITIES\n// ============================================================\n\nconst CHANNEL_NAME_MIN_LENGTH = 1\nconst CHANNEL_NAME_MAX_LENGTH = 128\nconst RESERVED_PREFIX = '__'\n\n/**\n * Validate channel name\n * Channel names must be non-empty strings with reasonable length\n */\nexport function isValidChannelName(name: ChannelName): boolean {\n return (\n typeof name === 'string' &&\n name.length >= CHANNEL_NAME_MIN_LENGTH &&\n name.length <= CHANNEL_NAME_MAX_LENGTH\n )\n}\n\n/**\n * Check if channel name is reserved for system use\n * Reserved channel names start with '__'\n */\nexport function isReservedChannelName(name: ChannelName): boolean {\n return name.startsWith(RESERVED_PREFIX)\n}\n\n/**\n * Assert that channel name is valid\n * @throws Error if channel name is invalid\n */\nexport function assertValidChannelName(name: ChannelName): void {\n if (!isValidChannelName(name)) {\n throw new Error(\n `Invalid channel name: must be between ${CHANNEL_NAME_MIN_LENGTH} and ${CHANNEL_NAME_MAX_LENGTH} characters`,\n )\n }\n if (isReservedChannelName(name)) {\n throw new Error(\n `Reserved channel name: channel names starting with '${RESERVED_PREFIX}' are reserved for system use`,\n )\n }\n}\n\n// ============================================================\n// MESSAGE UTILITIES\n// ============================================================\n\n/**\n * Guard to check if message is a DataMessage\n */\nexport function isDataMessage<T = unknown>(\n message: Message<T>,\n): message is DataMessage<T> {\n return message.type === MessageType.DATA\n}\n\n/**\n * Create a data message\n */\nexport function createDataMessage<T>(\n channel: ChannelName,\n data: T,\n id?: MessageId,\n): DataMessage<T> {\n return {\n id: id || generateMessageId(),\n type: MessageType.DATA,\n channel,\n data,\n timestamp: Date.now(),\n }\n}\n\n/**\n * Create a signal message\n */\nexport function createSignalMessage(\n channel: ChannelName,\n signal: SignalType,\n data?: any,\n id?: MessageId,\n): SignalMessage {\n return {\n id: id || generateMessageId(),\n type: MessageType.SIGNAL,\n channel,\n signal,\n data,\n timestamp: Date.now(),\n }\n}\n\n// ============================================================\n// LOGGING UTILITIES\n// ============================================================\n\n/**\n * Log levels\n */\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug'\n\n/**\n * Logger function signature\n */\nexport type LoggerFn = (\n level: LogLevel,\n message: string,\n ...args: unknown[]\n) => void\n\n/**\n * Default log level names for display\n */\nconst LOG_LEVEL_NAMES: Record<LogLevel, string> = {\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n debug: 'DEBUG',\n}\n\n/**\n * Create a timestamp for log messages\n */\nexport function createLogTimestamp(): string {\n return new Date().toISOString()\n}\n\n/**\n * Default logger implementation\n * Logs to console with timestamp and level\n */\nexport function createDefaultLogger(\n prefix = 'Syncar',\n enabled: { [K in LogLevel]?: boolean } = {},\n): ILogger {\n const logEnabled = {\n debug: false,\n info: true,\n warn: true,\n error: true,\n ...enabled,\n }\n\n const formatMessage = (level: LogLevel, message: string): string => {\n const timestamp = createLogTimestamp()\n return `[${prefix} ${timestamp}] [${LOG_LEVEL_NAMES[level]}] ${message}`\n }\n\n return {\n debug: (message: string, ...args: unknown[]) => {\n if (logEnabled.debug)\n console.log(formatMessage('debug', message), ...args)\n },\n info: (message: string, ...args: unknown[]) => {\n if (logEnabled.info)\n console.log(formatMessage('info', message), ...args)\n },\n warn: (message: string, ...args: unknown[]) => {\n if (logEnabled.warn)\n console.warn(formatMessage('warn', message), ...args)\n },\n error: (message: string, ...args: unknown[]) => {\n if (logEnabled.error)\n console.error(formatMessage('error', message), ...args)\n },\n }\n}\n","/**\n * Config Module\n * Single source of truth for all constant values and default configuration for the server.\n *\n * @module config\n */\n\n// ============================================================\n// CONSTANTS\n// ============================================================\n\n/**\n * Broadcast channel name\n * Messages sent to this channel reach ALL connected clients\n * No subscription required for broadcast channel\n */\nexport const BROADCAST_CHANNEL = '__broadcast__' as const\n\n/**\n * Type for the broadcast channel name\n */\nexport type BroadcastChannel = typeof BROADCAST_CHANNEL\n\n/**\n * WebSocket close codes\n * Based on RFC 6455 and custom codes for application-specific closures\n */\nexport const CLOSE_CODES = {\n /**\n * Normal closure\n */\n NORMAL: 1000,\n\n /**\n * Endpoint is going away\n */\n GOING_AWAY: 1001,\n\n /**\n * Protocol error\n */\n PROTOCOL_ERROR: 1002,\n\n /**\n * Unsupported data\n */\n UNSUPPORTED_DATA: 1003,\n\n /**\n * No status received\n */\n NO_STATUS: 1005,\n\n /**\n * Abnormal closure\n */\n ABNORMAL: 1006,\n\n /**\n * Invalid frame payload data\n */\n INVALID_PAYLOAD: 1007,\n\n /**\n * Policy violation\n */\n POLICY_VIOLATION: 1008,\n\n /**\n * Message too big\n */\n MESSAGE_TOO_BIG: 1009,\n\n /**\n * Missing extension\n */\n MISSING_EXTENSION: 1010,\n\n /**\n * Internal error\n */\n INTERNAL_ERROR: 1011,\n\n /**\n * Service restart\n */\n SERVICE_RESTART: 1012,\n\n /**\n * Try again later\n */\n TRY_AGAIN_LATER: 1013,\n\n /**\n * Connection rejected by middleware\n */\n REJECTED: 4001,\n\n /**\n * Rate limit exceeded\n */\n RATE_LIMITED: 4002,\n\n /**\n * Channel not found\n */\n CHANNEL_NOT_FOUND: 4003,\n\n /**\n * Unauthorized\n */\n UNAUTHORIZED: 4005,\n} as const\n\n/**\n * Type for WebSocket close code values\n */\nexport type CloseCode = (typeof CLOSE_CODES)[keyof typeof CLOSE_CODES]\n\n/**\n * Application error codes\n * Used in error messages sent to clients\n */\nexport const ERROR_CODES = {\n /**\n * Action rejected by middleware\n */\n REJECTED: 'REJECTED',\n\n /**\n * Channel name missing from message\n */\n MISSING_CHANNEL: 'MISSING_CHANNEL',\n\n /**\n * Subscribe action rejected\n */\n SUBSCRIBE_REJECTED: 'SUBSCRIBE_REJECTED',\n\n /**\n * Unsubscribe action rejected\n */\n UNSUBSCRIBE_REJECTED: 'UNSUBSCRIBE_REJECTED',\n\n /**\n * Rate limit exceeded\n */\n RATE_LIMITED: 'RATE_LIMITED',\n\n /**\n * Authentication failed\n */\n AUTH_FAILED: 'AUTH_FAILED',\n\n /**\n * Authorization failed\n */\n NOT_AUTHORIZED: 'NOT_AUTHORIZED',\n\n /**\n * Channel not allowed\n */\n CHANNEL_NOT_ALLOWED: 'CHANNEL_NOT_ALLOWED',\n\n /**\n * Invalid message format\n */\n INVALID_MESSAGE: 'INVALID_MESSAGE',\n\n /**\n * Server error\n */\n SERVER_ERROR: 'SERVER_ERROR',\n} as const\n\n/**\n * Type for error code values\n */\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES]\n\n// ============================================================\n// DEFAULTS\n// ============================================================\n\n/**\n * Default WebSocket path\n */\nexport const DEFAULT_WS_PATH = '/syncar'\n\n/**\n * Default maximum message payload size in bytes (1MB)\n */\nexport const DEFAULT_MAX_PAYLOAD = 1048576\n\n/**\n * Default ping interval in milliseconds (30 seconds)\n */\nexport const DEFAULT_PING_INTERVAL = 30000\n\n/**\n * Default ping timeout in milliseconds (5 seconds)\n */\nexport const DEFAULT_PING_TIMEOUT = 5000\n\n/**\n * Default server port\n */\nexport const DEFAULT_PORT = 3000\n\n/**\n * Default server host\n */\nexport const DEFAULT_HOST = '0.0.0.0'\n\n/**\n * Default WebSocket path\n */\nexport const DEFAULT_PATH = '/syncar'\n\n/**\n * Default ping enabled\n */\nexport const DEFAULT_ENABLE_PING = true\n\n/**\n * Default server configuration\n */\nexport const DEFAULT_SERVER_CONFIG = {\n port: DEFAULT_PORT,\n host: DEFAULT_HOST,\n path: DEFAULT_PATH,\n enablePing: DEFAULT_ENABLE_PING,\n pingInterval: DEFAULT_PING_INTERVAL,\n pingTimeout: DEFAULT_PING_TIMEOUT,\n broadcastChunkSize: 500,\n} as const\n\n/**\n * Default rate limit settings\n */\nexport const DEFAULT_RATE_LIMIT = {\n /**\n * Default maximum messages per time window\n */\n maxMessages: 100,\n\n /**\n * Default time window in milliseconds (1 minute)\n */\n windowMs: 60000,\n} as const\n\n/**\n * All defaults as a single object\n */\nexport const DEFAULTS = {\n server: DEFAULT_SERVER_CONFIG,\n rateLimit: DEFAULT_RATE_LIMIT,\n} as const\n","import {\n type ChannelName,\n type ClientId,\n type SubscriberId,\n type DataMessage,\n type IClientConnection,\n type IMiddleware,\n} from './types'\n\n/**\n * Channel state information\n *\n * @remarks\n * Provides runtime information about a channel including its name,\n * subscriber count, creation time, and last message timestamp.\n *\n * @property name - The channel name\n * @property subscriberCount - Current number of subscribers\n * @property createdAt - Unix timestamp (ms) when the channel was created\n * @property lastMessageAt - Unix timestamp (ms) of the last published message\n *\n * @example\n * ```ts\n * const state: IChannelState = {\n * name: 'chat',\n * subscriberCount: 42,\n * createdAt: 1699123456789,\n * lastMessageAt: 1699123459999\n * }\n * ```\n */\nexport interface IChannelState {\n /** The channel name */\n name: string\n /** Current number of subscribers */\n subscriberCount: number\n /** Unix timestamp (ms) when the channel was created */\n createdAt: number\n /** Unix timestamp (ms) of the last published message */\n lastMessageAt?: number\n}\n\n/**\n * Publish options for channel messages\n *\n * @remarks\n * Controls which clients receive a published message through\n * inclusion/exclusion filtering.\n *\n * @property to - Optional list of client IDs to receive the message (exclusive mode)\n * @property exclude - Optional list of client IDs to exclude from receiving the message\n *\n * @example\n * ```ts\n * // Send to specific clients only\n * channel.publish('Hello', { to: ['client-1', 'client-2'] })\n *\n * // Send to all except specific clients\n * channel.publish('Hello', { exclude: ['client-123'] })\n *\n * // Both options can be combined (to takes precedence)\n * channel.publish('Hello', { to: ['client-1'], exclude: ['client-2'] })\n * ```\n */\nexport interface IPublishOptions {\n /**\n * Optional list of client IDs to receive the message\n *\n * @remarks\n * When provided, only clients in this list will receive the message.\n * This creates an \"exclusive mode\" where `exclude` is still applied\n * as a secondary filter.\n */\n to?: readonly ClientId[]\n /**\n * Optional list of client IDs to exclude from receiving the message\n *\n * @remarks\n * Clients in this list will not receive the message, even if they\n * are subscribed to the channel. Useful for excluding the sender.\n */\n exclude?: readonly ClientId[]\n}\n\n/**\n * Base message handler signature\n *\n * @remarks\n * Type-safe handler function for processing incoming messages on a channel.\n * Handlers receive the message data, sending client, and the original message object.\n *\n * @template T - Type of data expected in messages\n *\n * @example\n * ```ts\n * const handler: IMessageHandler<{ text: string }> = (data, client, message) => {\n * console.log(`${client.id} sent: ${data.text}`)\n * console.log(`Message ID: ${message.id}`)\n * }\n *\n * channel.onMessage(handler)\n * ```\n */\nexport type IMessageHandler<T> = (\n /** The message data payload */\n data: T,\n /** The client connection that sent the message */\n client: IClientConnection,\n /** The complete message object with metadata */\n message: DataMessage<T>,\n) => void | Promise<void>\n\nimport { createDataMessage, assertValidChannelName } from './utils'\nimport { ClientRegistry } from './registry'\nimport { BROADCAST_CHANNEL } from './config'\n\n/**\n * Base Channel implementation\n *\n * @remarks\n * Abstract base class for all channel types. Handles the complexities of\n * chunked publishing, client filtering, and message delivery. Provides\n * automatic chunking for large broadcasts to avoid event loop blocking.\n *\n * @template T - Type of data published on this channel (default: unknown)\n * @template N - Type of the channel name (default: ChannelName)\n *\n * @example\n * ```ts\n * // Extend BaseChannel for custom channel types\n * class CustomChannel<T> extends BaseChannel<T> {\n * get subscriberCount() { return 0 }\n * isEmpty() { return true }\n * getMiddlewares() { return [] }\n * protected getTargetClients() { return [] }\n * }\n * ```\n *\n * @see {@link BroadcastChannel} for channel that sends to all clients\n * @see {@link MulticastChannel} for channel that sends to subscribers only\n */\nexport abstract class BaseChannel<\n T = unknown,\n N extends ChannelName = ChannelName,\n> {\n /**\n * Creates a new BaseChannel instance\n *\n * @param name - The channel name\n * @param registry - The client registry for connection management\n * @param chunkSize - Number of clients to process per chunk for large broadcasts (default: 500)\n */\n constructor(\n /** The channel name */\n public readonly name: N,\n /** The client registry for connection management */\n protected readonly registry: ClientRegistry,\n /** Number of clients to process per chunk for large broadcasts (default: 500) */\n protected readonly chunkSize: number = 500,\n ) {}\n\n /**\n * Get the current subscriber count\n *\n * @remarks\n * Abstract method that must be implemented by subclasses.\n * Returns the number of clients currently receiving messages from this channel.\n */\n abstract get subscriberCount(): number\n\n /**\n * Check if the channel has no subscribers\n *\n * @remarks\n * Abstract method that must be implemented by subclasses.\n * Returns `true` if the channel has no subscribers.\n */\n abstract isEmpty(): boolean\n\n /**\n * Get the middleware for this channel\n *\n * @remarks\n * Abstract method that must be implemented by subclasses.\n * Returns an array of middleware functions applied to this channel.\n */\n abstract getMiddlewares(): IMiddleware[]\n\n /**\n * Publish data to the channel\n *\n * @remarks\n * Sends the data to all target clients. If the number of clients exceeds\n * `chunkSize`, messages are sent in chunks using `setImmediate` to avoid\n * blocking the event loop.\n *\n * @param data - The data to publish\n * @param options - Optional publish options for client filtering\n *\n * @example\n * ```ts\n * // Publish to all clients\n * channel.publish('Hello everyone!')\n *\n * // Publish excluding specific clients\n * channel.publish('Hello', { exclude: ['client-123'] })\n *\n * // Publish to specific clients only\n * channel.publish('Private message', { to: ['client-1', 'client-2'] })\n * ```\n */\n publish(data: T, options?: IPublishOptions): void {\n const clients = this.getTargetClients(options)\n if (clients.length > this.chunkSize) {\n this.publishInChunks(data, clients, options)\n } else {\n this.publishToClients(data, clients, options)\n }\n }\n\n /**\n * Dispatch an incoming client message\n *\n * @remarks\n * Called when a client sends a message to this channel.\n * Override in subclasses to handle incoming messages.\n * Default implementation is a no-op (e.g., BroadcastChannel doesn't receive messages).\n *\n * @param data - The message data\n * @param client - The client that sent the message\n * @param message - The complete message object\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async dispatch(\n _data: T,\n _client: IClientConnection,\n _message: DataMessage<T>,\n ): Promise<void> {}\n\n protected abstract getTargetClients(options?: IPublishOptions): ClientId[]\n\n protected publishToClients(\n data: T,\n clientIds: ClientId[],\n options?: IPublishOptions,\n ): void {\n const message = createDataMessage<T>(this.name, data)\n\n for (const clientId of clientIds) {\n // Apply filters (if present in options)\n if (options?.to && !options.to.includes(clientId)) continue\n if (options?.exclude && options.exclude.includes(clientId)) continue\n\n const client = this.registry.connections.get(clientId)\n if (client) {\n try {\n client.socket.send(JSON.stringify(message))\n } catch (error) {\n this.registry.logger?.error(\n `[${this.name}] Failed to send to ${clientId}:`,\n error as Error,\n )\n }\n }\n }\n }\n\n protected publishInChunks(\n data: T,\n clientIds: ClientId[],\n options?: IPublishOptions,\n ): void {\n let index = 0\n\n const nextChunk = () => {\n const chunk = clientIds.slice(index, index + this.chunkSize)\n if (chunk.length === 0) return\n\n this.publishToClients(data, chunk, options)\n index += this.chunkSize\n\n if (index < clientIds.length) {\n setImmediate(nextChunk)\n }\n }\n nextChunk()\n }\n}\n\n/**\n * Broadcast Channel - sends messages to ALL connected clients\n *\n * @remarks\n * A special channel that delivers messages to every connected client,\n * regardless of subscription. This is useful for server-wide announcements,\n * system notifications, and global events.\n *\n * The broadcast channel is a singleton with the reserved name `__broadcast__`.\n * It is automatically created when the server starts and can be accessed\n * via `server.createBroadcast()`.\n *\n * @template T - Type of data to be broadcast (default: unknown)\n *\n * @example\n * ### Basic broadcasting\n * ```ts\n * const broadcast = server.createBroadcast<string>()\n * broadcast.publish('Server maintenance in 5 minutes')\n * ```\n *\n * @example\n * ### Broadcasting objects\n * ```ts\n * const alerts = server.createBroadcast<{ type: string; message: string }>()\n * alerts.publish({ type: 'warning', message: 'High load detected' })\n * ```\n *\n * @example\n * ### Client filtering\n * ```ts\n * // Send to all except specific clients\n * broadcast.publish('Admin message', { exclude: ['client-123'] })\n *\n * // Send to specific clients only\n * broadcast.publish('Private message', { to: ['client-1', 'client-2'] })\n * ```\n *\n * @example\n * ### System announcements\n * ```ts\n * const broadcast = server.createBroadcast<string>()\n *\n * // Send periodic announcements\n * setInterval(() => {\n * const time = new Date().toLocaleTimeString()\n * broadcast.publish(`Server time: ${time}`)\n * }, 60000)\n * ```\n *\n * @see {@link BROADCAST_CHANNEL} for the reserved channel name constant\n * @see {@link MulticastChannel} for subscription-based channels\n */\nexport class BroadcastChannel<T = unknown> extends BaseChannel<\n T,\n typeof BROADCAST_CHANNEL\n> {\n /**\n * Creates a new BroadcastChannel instance\n *\n * @remarks\n * BroadcastChannel is created automatically by the server and typically\n * accessed via `server.createBroadcast()` rather than instantiated directly.\n *\n * @param registry - The client registry for connection management\n * @param chunkSize - Number of clients to process per chunk for large broadcasts (default: 500)\n */\n constructor(registry: ClientRegistry, chunkSize: number = 500) {\n super(BROADCAST_CHANNEL, registry, chunkSize)\n }\n\n /**\n * Get all connected clients as target recipients\n *\n * @remarks\n * Broadcast channel targets ALL connected clients. The `options` parameter\n * is still applied after this method returns for filtering.\n *\n * @param _options - Publish options (ignored for broadcast target selection)\n * @returns Array of all connected client IDs\n *\n * @internal\n */\n protected getTargetClients(_options?: IPublishOptions): ClientId[] {\n return Array.from(this.registry.connections.keys())\n }\n\n /**\n * Get the number of connected clients\n *\n * @returns The total number of connected clients\n *\n * @example\n * ```ts\n * const broadcast = server.createBroadcast<string>()\n * console.log(`Connected clients: ${broadcast.subscriberCount}`)\n * ```\n */\n get subscriberCount(): number {\n return this.registry.connections.size\n }\n\n /**\n * Check if there are no connected clients\n *\n * @returns `true` if no clients are connected, `false` otherwise\n *\n * @example\n * ```ts\n * const broadcast = server.createBroadcast<string>()\n * if (broadcast.isEmpty()) {\n * console.log('No clients connected')\n * }\n * ```\n */\n isEmpty(): boolean {\n return this.registry.connections.size === 0\n }\n\n /**\n * Get the middleware for this channel\n *\n * @remarks\n * Broadcast channel has no middleware by default. Returns an empty array.\n *\n * @returns Empty array (broadcast channels don't have middleware)\n */\n getMiddlewares(): IMiddleware[] {\n return []\n }\n}\n\n/**\n * Multicast channel options\n *\n * @remarks\n * Configuration options for creating a multicast channel.\n *\n * @property chunkSize - Number of clients to process per chunk for large broadcasts\n *\n * @example\n * ```ts\n * const chat = server.createMulticast('chat', { chunkSize: 1000 })\n * ```\n */\nexport interface MulticastChannelOptions {\n /**\n * Number of clients to process per chunk for large broadcasts\n *\n * @remarks\n * When broadcasting to more than this many subscribers, messages are sent\n * in chunks to avoid blocking the event loop.\n *\n * @default 500\n */\n chunkSize?: number\n}\n\n/**\n * Multicast Channel - sends messages to subscribed clients only\n *\n * @remarks\n * A topic-based channel that delivers messages only to clients that have\n * explicitly subscribed. This is the standard channel type for implementing\n * chat rooms, notifications, and other subscription-based messaging patterns.\n *\n * Key features:\n * - Clients must subscribe to receive messages\n * - Supports message handlers for intercepting incoming messages\n * - Auto-relays messages to all subscribers (excluding sender) when no handler is set\n * - Supports per-channel middleware\n *\n * @template T - Type of data published on this channel (default: unknown)\n *\n * @example\n * ### Creating a channel\n * ```ts\n * const chat = server.createMulticast<{ text: string; user: string }>('chat')\n * ```\n *\n * @example\n * ### Publishing messages\n * ```ts\n * // Publish to all subscribers\n * chat.publish({ text: 'Hello everyone!', user: 'Alice' })\n *\n * // Publish excluding sender\n * chat.publish({ text: 'Welcome!', user: 'System' }, { exclude: ['client-123'] })\n *\n * // Publish to specific subscribers only\n * chat.publish({ text: 'Private message', user: 'Bob' }, { to: ['client-1'] })\n * ```\n *\n * @example\n * ### Handling incoming messages with auto-relay\n * ```ts\n * // When no handler is set, messages are auto-relayed to all subscribers (excluding sender)\n * // This is the default behavior for simple chat rooms\n * ```\n *\n * @example\n * ### Handling incoming messages with custom handler\n * ```ts\n * chat.onMessage((data, client) => {\n * console.log(`${data.user} (${client.id}): ${data.text}`)\n *\n * // Apply custom logic (filtering, transformation, persistence, etc.)\n * if (isProfane(data.text)) {\n * return // Don't relay\n * }\n *\n * // Manually publish to subscribers\n * chat.publish(data, { exclude: [client.id] })\n * })\n * ```\n *\n * @example\n * ### Managing subscriptions\n * ```ts\n * // Subscribe a client\n * chat.subscribe('client-123')\n *\n * // Unsubscribe a client\n * chat.unsubscribe('client-123')\n *\n * // Check if subscribed\n * if (chat.hasSubscriber('client-123')) {\n * console.log('Client is subscribed')\n * }\n *\n * // Get all subscribers\n * const subscribers = chat.getSubscribers()\n * console.log(`Subscribers: ${Array.from(subscribers).join(', ')}`)\n * ```\n *\n * @example\n * ### Channel middleware\n * ```ts\n * chat.use(async (context, next) => {\n * if (context.req.action === 'message') {\n * // Filter profanity\n * const data = context.req.message?.data as { text: string }\n * if (data?.text && isProfane(data.text)) {\n * context.reject('Profanity is not allowed')\n * }\n * }\n * await next()\n * })\n * ```\n *\n * @see {@link BroadcastChannel} for broadcasting to all clients\n */\nexport class MulticastChannel<T = unknown> extends BaseChannel<T> {\n private readonly middlewares: IMiddleware[] = []\n\n private readonly messageHandlers: Set<IMessageHandler<T>> = new Set()\n\n /**\n * Creates a new MulticastChannel instance\n *\n * @remarks\n * MulticastChannel is typically created via `server.createMulticast()`\n * rather than instantiated directly.\n *\n * @param config.name - The channel name (must not start with `__`)\n * @param config.registry - The client registry for connection management\n * @param config.options - Optional channel configuration\n *\n * @throws {ValidationError} If the channel name is invalid (starts with `__`)\n */\n constructor(config: {\n /** The channel name (must not start with `__`) */\n name: ChannelName\n /** The client registry for connection management */\n registry: ClientRegistry\n /** Optional channel configuration */\n options?: MulticastChannelOptions\n }) {\n assertValidChannelName(config.name)\n super(config.name, config.registry, config.options?.chunkSize)\n }\n\n /**\n * Get all subscribed clients as target recipients\n *\n * @remarks\n * Only clients that have subscribed to this channel will receive messages.\n * The `options` parameter is still applied after this method returns for filtering.\n *\n * @param _options - Publish options (ignored for multicast target selection)\n * @returns Array of subscribed client IDs\n *\n * @internal\n */\n protected getTargetClients(_options?: IPublishOptions): ClientId[] {\n return Array.from(this.registry.getChannelSubscribers(this.name))\n }\n\n /**\n * Register a channel-specific middleware\n *\n * @remarks\n * Adds a middleware function that runs for all actions on this channel,\n * after global middleware. Channel middleware is useful for channel-specific\n * validation, filtering, or enrichment.\n *\n * @param middleware - The middleware function to register\n *\n * @example\n * ```ts\n * chat.use(async (context, next) => {\n * if (context.req.action === 'message') {\n * const data = context.req.message?.data as { text: string }\n * if (data?.text && isProfane(data.text)) {\n * context.reject('Profanity is not allowed')\n * }\n * }\n * await next()\n * })\n * ```\n *\n * @see {@link IMiddleware} for middleware interface\n */\n use(middleware: IMiddleware): void {\n this.middlewares.push(middleware)\n }\n\n /**\n * Get the middleware for this channel\n *\n * @returns Array of middleware functions registered on this channel\n */\n getMiddlewares(): IMiddleware[] {\n return [...this.middlewares]\n }\n\n /**\n * Get the number of subscribers\n *\n * @returns The number of clients subscribed to this channel\n *\n * @example\n * ```ts\n * console.log(`Chat subscribers: ${chat.subscriberCount}`)\n * ```\n */\n get subscriberCount(): number {\n return this.registry.getChannelSubscribers(this.name).size\n }\n\n /**\n * Register a message handler for incoming messages\n *\n * @remarks\n * Registers a handler that intercepts incoming messages to this channel.\n * When handlers are registered, they receive full control over message\n * processing - auto-relay is disabled.\n *\n * @param handler - The message handler function\n * @returns Unsubscribe function that removes the handler when called\n *\n * @example\n * ```ts\n * const unsubscribe = chat.onMessage((data, client) => {\n * console.log(`${client.id}: ${data.text}`)\n * // Custom processing logic\n * })\n *\n * // Later, remove the handler\n * unsubscribe()\n * ```\n *\n * @remarks\n * Multiple handlers can be registered. They will be executed in the order\n * they were registered. If any handler throws an error, it will be logged\n * but other handlers will still execute.\n */\n onMessage(handler: IMessageHandler<T>): () => void {\n this.messageHandlers.add(handler)\n return () => this.messageHandlers.delete(handler)\n }\n\n /**\n * Subscribe a client to this channel\n *\n * @remarks\n * Subscribes a client to receive messages from this channel.\n * This is typically called automatically when a client sends a\n * subscribe signal, but can also be called manually.\n *\n * @param subscriber - The subscriber (client) ID to subscribe\n * @returns `true` if subscription was successful, `false` if already subscribed\n *\n * @example\n * ```ts\n * chat.subscribe('client-123')\n *\n * if (chat.subscribe('client-456')) {\n * console.log('Subscribed successfully')\n * }\n * ```\n */\n subscribe(subscriber: SubscriberId): boolean {\n return this.registry.subscribe(subscriber, this.name)\n }\n\n /**\n * Unsubscribe a client from this channel\n *\n * @remarks\n * Removes a client's subscription from this channel.\n * This is typically called automatically when a client sends an\n * unsubscribe signal or disconnects, but can also be called manually.\n *\n * @param subscriber - The subscriber (client) ID to unsubscribe\n * @returns `true` if unsubscription was successful, `false` if not subscribed\n *\n * @example\n * ```ts\n * chat.unsubscribe('client-123')\n *\n * if (chat.unsubscribe('client-456')) {\n * console.log('Unsubscribed successfully')\n * }\n * ```\n */\n unsubscribe(subscriber: SubscriberId): boolean {\n return this.registry.unsubscribe(subscriber, this.name)\n }\n\n /**\n * Dispatch an incoming client message\n *\n * @remarks\n * Processes incoming messages from clients. The behavior depends on\n * whether message handlers are registered:\n *\n * - **With handlers**: All registered handlers are executed with full\n * control over the message. No auto-relay occurs.\n *\n * - **Without handlers**: The message is automatically relayed to all\n * subscribers except the sender.\n *\n * @param data - The message data payload\n * @param client - The client that sent the message\n * @param message - The complete message object\n *\n * @example\n * ### Auto-relay mode (no handler)\n * ```ts\n * // Client sends message → automatically relayed to all subscribers\n * // No need to call publish()\n * ```\n *\n * @example\n * ### Intercept mode (with handler)\n * ```ts\n * chat.onMessage((data, client) => {\n * // Full control - implement custom logic\n * if (isValidMessage(data)) {\n * chat.publish(data, { exclude: [client.id] })\n * }\n * })\n * ```\n *\n * @internal\n */\n override async dispatch(\n data: T,\n client: IClientConnection,\n message: DataMessage<T>,\n ): Promise<void> {\n if (this.messageHandlers.size > 0) {\n // Intercept mode: developer handles everything manually\n for (const handler of this.messageHandlers) {\n try {\n await handler(data, client, message)\n } catch (error) {\n this.registry.logger?.error(\n `[${this.name}] Error in message handler:`,\n error as Error,\n )\n }\n }\n } else {\n // Auto-relay mode: forward to all subscribers, excluding sender\n this.publish(data, { exclude: [client.id] })\n }\n }\n\n /**\n * Check if a client is subscribed to this channel\n *\n * @param subscriber - The subscriber (client) ID to check\n * @returns `true` if the client is subscribed, `false` otherwise\n *\n * @example\n * ```ts\n * if (chat.hasSubscriber('client-123')) {\n * console.log('Client is subscribed to chat')\n * }\n * ```\n */\n hasSubscriber(subscriber: SubscriberId): boolean {\n return this.registry.getChannelSubscribers(this.name).has(subscriber)\n }\n\n /**\n * Get all subscribers to this channel\n *\n * @remarks\n * Returns a copy of the subscribers set to prevent external modification.\n * The returned set is a snapshot and may not reflect future changes.\n *\n * @returns A Set of subscriber IDs\n *\n * @example\n * ```ts\n * const subscribers = chat.getSubscribers()\n * console.log(`Subscribers: ${Array.from(subscribers).join(', ')}`)\n *\n * // Check if a specific client is subscribed\n * if (subscribers.has('client-123')) {\n * console.log('Client 123 is subscribed')\n * }\n * ```\n */\n getSubscribers(): Set<SubscriberId> {\n // Return a copy to prevent external modification\n return new Set(this.registry.getChannelSubscribers(this.name))\n }\n\n /**\n * Check if the channel has no subscribers\n *\n * @returns `true` if no clients are subscribed, `false` otherwise\n *\n * @example\n * ```ts\n * if (chat.isEmpty()) {\n * console.log('No one is in the chat')\n * }\n * ```\n */\n isEmpty(): boolean {\n return this.registry.getChannelSubscribers(this.name).size === 0\n }\n}\n","/**\n * Errors Module\n *\n * @description\n * Custom error classes for the Syncar server. All errors extend from\n * {@link SyncarError} and include error codes for programmatic handling.\n *\n * @remarks\n * The error hierarchy:\n *\n * - {@link SyncarError} - Base error class\n * - {@link ConfigError} - Server configuration issues\n * - {@link TransportError} - WebSocket transport issues\n * - {@link ChannelError} - Channel operation failures\n * - {@link ClientError} - Client operation failures\n * - {@link MessageError} - Message processing failures\n * - {@link ValidationError} - Input validation failures\n * - {@link StateError} - Invalid state operations\n * - {@link MiddlewareRejectionError} - Explicit middleware rejections\n * - {@link MiddlewareExecutionError} - Unexpected middleware errors\n *\n * @example\n * ### Throwing errors\n * ```ts\n * import { StateError, ValidationError } from '@syncar/server'\n *\n * function createChannel(name: string) {\n * if (!name) {\n * throw new ValidationError('Channel name is required')\n * }\n * if (!server.started) {\n * throw new StateError('Server must be started first')\n * }\n * }\n * ```\n *\n * @example\n * ### Catching errors\n * ```ts\n * import {\n * SyncarError,\n * MiddlewareRejectionError,\n * StateError\n * } from '@syncar/server'\n *\n * try {\n * await server.start()\n * } catch (error) {\n * if (error instanceof StateError) {\n * console.error('Invalid state:', error.message)\n * } else if (error instanceof MiddlewareRejectionError) {\n * console.error(`Action rejected: ${error.reason}`)\n * } else if (error instanceof SyncarError) {\n * console.error(`[${error.code}] ${error.message}`)\n * }\n * }\n * ```\n *\n * @module errors\n */\n\nimport type { IMiddlewareRejectionError, IMiddlewareAction } from './types'\n\n// ============================================================\n// BASE SYNNEL ERROR\n// ============================================================\n\n/**\n * Base Syncar error class\n *\n * @remarks\n * All custom errors in the Syncar server extend this class. Provides\n * consistent error handling with error codes, context, and serialization.\n *\n * @property code - Error code for programmatic handling\n * @property context - Optional additional error context\n *\n * @example\n * ```ts\n * throw new SyncarError('Something went wrong', 'CUSTOM_ERROR', { userId: '123' })\n * ```\n *\n * @example\n * ### Error handling\n * ```ts\n * try {\n * // ...\n * } catch (error) {\n * if (error instanceof SyncarError) {\n * console.log(error.code) // 'CUSTOM_ERROR'\n * console.log(error.message) // 'Something went wrong'\n * console.log(error.context) // { userId: '123' }\n * console.log(error.toJSON()) // Serialized error\n * }\n * }\n * ```\n */\nexport class SyncarError extends Error {\n /**\n * Error code for programmatic error handling\n *\n * @remarks\n * Machine-readable error code that can be used for conditional\n * error handling and error response generation.\n */\n public readonly code: string\n\n /**\n * Additional error context (optional)\n *\n * @remarks\n * Arbitrary data attached to the error for debugging or logging.\n * Common uses include user IDs, request IDs, or validation details.\n */\n public readonly context?: Record<string, unknown>\n\n /**\n * Creates a new SyncarError\n *\n * @param message - Human-readable error message\n * @param code - Error code for programmatic handling (default: 'SYNNEL_ERROR')\n * @param context - Optional additional error context\n */\n constructor(\n message: string,\n code: string = 'SYNNEL_ERROR',\n context?: Record<string, unknown>,\n ) {\n super(message)\n this.name = 'SyncarError'\n this.code = code\n this.context = context\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, SyncarError)\n }\n }\n\n /**\n * Convert error to JSON for logging/serialization\n *\n * @returns JSON representation of the error\n *\n * @example\n * ```ts\n * const error = new SyncarError('Failed', 'FAIL', { id: 123 })\n * console.log(JSON.stringify(error.toJSON(), null, 2))\n * // {\n * // \"name\": \"SyncarError\",\n * // \"message\": \"Failed\",\n * // \"code\": \"FAIL\",\n * // \"context\": { \"id\": 123 },\n * // \"stack\": \"...\"\n * // }\n * ```\n */\n toJSON(): {\n name: string\n message: string\n code: string\n context?: Record<string, unknown>\n stack?: string\n } {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n context: this.context,\n stack: this.stack,\n }\n }\n\n /**\n * Get a summary of the error for logging\n *\n * @returns Formatted error summary string\n *\n * @example\n * ```ts\n * const error = new SyncarError('Failed', 'FAIL')\n * console.log(error.toString())\n * // \"[SyncarError:FAIL] Failed\"\n * ```\n */\n override toString(): string {\n return `[${this.name}:${this.code}] ${this.message}`\n }\n}\n\n// ============================================================\n// COMMON ERROR CLASSES\n// ============================================================\n\n/**\n * Configuration error\n *\n * @remarks\n * Thrown when server configuration is invalid or missing required values.\n *\n * @example\n * ```ts\n * if (!config.port) {\n * throw new ConfigError('Port is required', { config })\n * }\n * ```\n */\nexport class ConfigError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'CONFIG_ERROR', context)\n this.name = 'ConfigError'\n }\n}\n\n/**\n * Transport error\n *\n * @remarks\n * Thrown when the transport layer fails (WebSocket connection issues, etc.).\n *\n * @example\n * ```ts\n * if (!wsServer) {\n * throw new TransportError('WebSocket server not initialized')\n * }\n * ```\n */\nexport class TransportError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'TRANSPORT_ERROR', context)\n this.name = 'TransportError'\n }\n}\n\n/**\n * Channel error\n *\n * @remarks\n * Thrown when channel operations fail (invalid channel name, etc.).\n *\n * @example\n * ```ts\n * if (channelName.startsWith('__')) {\n * throw new ChannelError('Reserved channel name', { channelName })\n * }\n * ```\n */\nexport class ChannelError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'CHANNEL_ERROR', context)\n this.name = 'ChannelError'\n }\n}\n\n/**\n * Client error\n *\n * @remarks\n * Thrown when client operations fail (client not found, etc.).\n *\n * @example\n * ```ts\n * if (!registry.has(clientId)) {\n * throw new ClientError('Client not found', { clientId })\n * }\n * ```\n */\nexport class ClientError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'CLIENT_ERROR', context)\n this.name = 'ClientError'\n }\n}\n\n/**\n * Message error\n *\n * @remarks\n * Thrown when message processing fails (invalid format, etc.).\n *\n * @example\n * ```ts\n * if (!message.type) {\n * throw new MessageError('Invalid message format', { message })\n * }\n * ```\n */\nexport class MessageError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'MESSAGE_ERROR', context)\n this.name = 'MessageError'\n }\n}\n\n/**\n * Validation error\n *\n * @remarks\n * Thrown when input validation fails.\n *\n * @example\n * ```ts\n * if (!isValidChannelName(name)) {\n * throw new ValidationError('Invalid channel name', { name })\n * }\n * ```\n */\nexport class ValidationError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'VALIDATION_ERROR', context)\n this.name = 'ValidationError'\n }\n}\n\n/**\n * State error\n *\n * @remarks\n * Thrown when an operation is invalid for the current state.\n *\n * @example\n * ```ts\n * if (server.started) {\n * throw new StateError('Server is already started')\n * }\n *\n * if (!server.started) {\n * throw new StateError('Server must be started first')\n * }\n * ```\n */\nexport class StateError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'STATE_ERROR', context)\n this.name = 'StateError'\n }\n}\n\n// ============================================================\n// MIDDLEWARE REJECTION ERROR\n// ============================================================\n\n/**\n * Middleware rejection error\n *\n * @remarks\n * Thrown when middleware explicitly rejects an action using the\n * `context.reject()` function. This is an expected error type that\n * indicates intentional rejection rather than a failure.\n *\n * @property reason - Human-readable reason for the rejection\n * @property action - The action that was rejected\n * @property code - Optional error code for programmatic handling\n * @property context - Additional context about the rejection\n *\n * @example\n * ### Throwing from middleware\n * ```ts\n * const middleware: Middleware = async (context, next) => {\n * if (!context.req.client) {\n * context.reject('Client is required')\n * // Function never returns (throws MiddlewareRejectionError)\n * }\n * await next()\n * }\n * ```\n *\n * @example\n * ### Catching rejections\n * ```ts\n * try {\n * await manager.executeConnection(client, 'connect')\n * } catch (error) {\n * if (error instanceof MiddlewareRejectionError) {\n * console.log(`Action '${error.action}' rejected: ${error.reason}`)\n * // Send error to client\n * client.socket.send(JSON.stringify({\n * type: 'error',\n * data: { message: error.reason, code: error.code }\n * }))\n * }\n * }\n * ```\n */\nexport class MiddlewareRejectionError\n extends Error\n implements IMiddlewareRejectionError\n{\n /**\n * The reason for rejection\n *\n * @remarks\n * Human-readable explanation of why the action was rejected.\n */\n public readonly reason: string\n\n /**\n * The action that was rejected\n *\n * @remarks\n * One of: 'connect', 'disconnect', 'message', 'subscribe', 'unsubscribe'\n */\n public readonly action: string\n\n /**\n * Error name (fixed value for interface compliance)\n */\n public override readonly name = 'MiddlewareRejectionError'\n\n /**\n * Optional error code for programmatic handling\n *\n * @remarks\n * Can be used for mapping to client-facing error codes.\n */\n public readonly code?: string\n\n /**\n * Additional context about the rejection\n *\n * @remarks\n * Arbitrary data for debugging or logging.\n */\n public readonly context?: Record<string, unknown>\n\n /**\n * Creates a new MiddlewareRejectionError\n *\n * @param reason - Human-readable reason for the rejection\n * @param action - The action that was rejected\n * @param code - Optional error code for programmatic handling\n * @param context - Additional context about the rejection\n */\n constructor(\n reason: string,\n action: IMiddlewareAction | string,\n code?: string,\n context?: Record<string, unknown>,\n ) {\n super(`Action '${action}' rejected: ${reason}`)\n this.reason = reason\n this.action = typeof action === 'string' ? action : action\n\n // Set error name for instanceof checks\n this.name = 'MiddlewareRejectionError'\n\n // Optional code and context\n this.code = code\n this.context = context\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, MiddlewareRejectionError)\n }\n }\n\n /**\n * Convert error to JSON for logging/serialization\n *\n * @returns JSON representation of the rejection error\n *\n * @example\n * ```ts\n * const error = new MiddlewareRejectionError('Not allowed', 'subscribe', 'FORBIDDEN')\n * console.log(JSON.stringify(error.toJSON(), null, 2))\n * // {\n * // \"name\": \"MiddlewareRejectionError\",\n * // \"reason\": \"Not allowed\",\n * // \"action\": \"subscribe\",\n * // \"code\": \"FORBIDDEN\",\n * // \"message\": \"Action 'subscribe' rejected: Not allowed\",\n * // \"stack\": \"...\"\n * // }\n * ```\n */\n toJSON(): {\n name: string\n reason: string\n action: string\n code?: string\n context?: Record<string, unknown>\n message: string\n stack?: string\n } {\n return {\n name: this.name,\n reason: this.reason,\n action: this.action,\n code: this.code,\n context: this.context,\n message: this.message,\n stack: this.stack,\n }\n }\n\n /**\n * Get a summary of the rejection for logging\n *\n * @returns Formatted error summary string\n *\n * @example\n * ```ts\n * const error = new MiddlewareRejectionError('Not allowed', 'subscribe')\n * console.log(error.toString())\n * // \"[MiddlewareRejectionError:subscribe] Not allowed\"\n * ```\n */\n override toString(): string {\n return `[${this.name}:${this.action}] ${this.reason}`\n }\n}\n\n// ============================================================\n// MIDDLEWARE EXECUTION ERROR\n// ============================================================\n\n/**\n * Middleware execution error\n *\n * @remarks\n * Thrown when a middleware function throws an unexpected error\n * (not using `context.reject()`). This indicates a bug or failure\n * in the middleware rather than an intentional rejection.\n *\n * @property action - The action being processed when the error occurred\n * @property middleware - The name/index of the middleware that failed\n * @property cause - The original error thrown by the middleware\n *\n * @example\n * ### Error scenario\n * ```ts\n * const buggyMiddleware: Middleware = async (context, next) => {\n * // This throws an unexpected error\n * JSON.parse(context.req.message as string)\n * await next()\n * }\n * // Results in MiddlewareExecutionError\n * ```\n *\n * @example\n * ### Catching execution errors\n * ```ts\n * try {\n * await manager.execute(context)\n * } catch (error) {\n * if (error instanceof MiddlewareExecutionError) {\n * console.error(`${error.middleware} failed during ${error.action}:`)\n * console.error(error.cause)\n * }\n * }\n * ```\n */\nexport class MiddlewareExecutionError extends Error {\n /**\n * The action being processed when the error occurred\n */\n public readonly action: string\n\n /**\n * The name/index of the middleware that failed\n */\n public readonly middleware: string\n\n /**\n * The original error thrown by the middleware\n */\n public override readonly cause: Error\n\n /**\n * Creates a new MiddlewareExecutionError\n *\n * @param action - The action being processed\n * @param middleware - The name/index of the middleware\n * @param cause - The original error\n */\n constructor(action: string, middleware: string, cause: Error) {\n super(\n `Middleware execution error in ${middleware} during ${action}: ${cause.message}`,\n )\n this.name = 'MiddlewareExecutionError'\n this.action = action\n this.middleware = middleware\n this.cause = cause\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, MiddlewareExecutionError)\n }\n }\n\n /**\n * Get the original error cause\n *\n * @returns The original error thrown by the middleware\n *\n * @example\n * ```ts\n * if (error instanceof MiddlewareExecutionError) {\n * const originalError = error.getCause()\n * console.error('Original error:', originalError.message)\n * }\n * ```\n */\n getCause(): Error {\n return this.cause\n }\n\n /**\n * Get a summary of the error for logging\n *\n * @returns Formatted error summary string\n *\n * @example\n * ```ts\n * const error = new MiddlewareExecutionError('message', 'auth', originalError)\n * console.log(error.toString())\n * // \"[MiddlewareExecutionError] auth failed during message: Invalid token\"\n * ```\n */\n override toString(): string {\n return `[${this.name}] ${this.middleware} failed during ${this.action}: ${this.cause.message}`\n }\n}\n","import type { IClientConnection, SignalMessage } from '../types'\n\nimport { createSignalMessage, isReservedChannelName } from '../utils'\nimport { SignalType } from '../types'\nimport { ChannelError, MessageError } from '../errors'\nimport { BROADCAST_CHANNEL } from '../config'\nimport { ContextManager } from '../context'\nimport { ClientRegistry } from '../registry'\n\nexport interface SignalHandlerOptions {\n requireChannel?: boolean\n allowReservedChannels?: boolean\n sendAcknowledgments?: boolean\n autoRespondToPing?: boolean\n}\n\nexport class SignalHandler {\n private readonly registry: ClientRegistry\n private readonly context: ContextManager\n private readonly options: Required<SignalHandlerOptions>\n\n constructor(dependencies: {\n registry: ClientRegistry\n context: ContextManager\n options?: SignalHandlerOptions\n }) {\n this.registry = dependencies.registry\n this.context = dependencies.context\n\n // Apply defaults\n this.options = {\n requireChannel: dependencies.options?.requireChannel ?? false,\n allowReservedChannels:\n dependencies.options?.allowReservedChannels ?? false,\n sendAcknowledgments:\n dependencies.options?.sendAcknowledgments ?? true,\n autoRespondToPing: dependencies.options?.autoRespondToPing ?? true,\n }\n }\n\n async handleSignal(\n client: IClientConnection,\n message: SignalMessage,\n ): Promise<void> {\n // 1. Create Context based on signal\n let ctx\n\n if (message.signal === SignalType.SUBSCRIBE) {\n ctx = this.context.createSubscribeContext(client, message.channel!)\n } else if (message.signal === SignalType.UNSUBSCRIBE) {\n ctx = this.context.createUnsubscribeContext(\n client,\n message.channel!,\n )\n } else {\n ctx = this.context.createMessageContext(client, message)\n }\n\n // 3. Define Kernel\n const kernel = async () => {\n switch (message.signal) {\n case SignalType.SUBSCRIBE:\n await this.handleSubscribe(client, message)\n break\n\n case SignalType.UNSUBSCRIBE:\n await this.handleUnsubscribe(client, message)\n break\n\n case SignalType.PING:\n await this.handlePing(client, message)\n break\n\n case SignalType.PONG:\n await this.handlePong(client, message)\n break\n\n default:\n throw new MessageError(\n `Unknown signal type: ${message.signal}`,\n )\n }\n }\n\n // 4. Get the unified pipeline (Global + Channel specific if applicable)\n let channelInstance = undefined\n if (message.channel && message.channel !== BROADCAST_CHANNEL) {\n channelInstance = this.registry.getChannel(message.channel)\n }\n\n const pipeline = this.context.getPipeline(channelInstance)\n\n // 5. Execute Onion (Global + Channel Middleware)\n await this.context.execute(ctx, pipeline, kernel)\n }\n\n async handleSubscribe(\n client: IClientConnection,\n message: SignalMessage,\n ): Promise<void> {\n const { channel } = message\n\n // Check for reserved channel\n if (\n !this.options.allowReservedChannels &&\n isReservedChannelName(channel)\n ) {\n throw new ChannelError(\n `Cannot subscribe to reserved channel: ${channel}`,\n )\n }\n\n // Cannot subscribe to broadcast channel (it's for all clients)\n if (channel === BROADCAST_CHANNEL) {\n throw new ChannelError('Cannot subscribe to broadcast channel')\n }\n\n // Subscribe to channel via registry\n // Features like reserved channels, max subscribers, etc. are now\n // handled via onSubscribe callbacks that can reject the subscription\n const success = this.registry.subscribe(client.id, channel)\n if (!success) {\n throw new ChannelError(\n `Failed to subscribe client ${client.id} to channel ${channel}`,\n )\n }\n\n // Send acknowledgment\n if (this.options.sendAcknowledgments) {\n const ackMessage = createSignalMessage(\n channel,\n SignalType.SUBSCRIBED,\n undefined,\n message.id,\n )\n client.socket.send(JSON.stringify(ackMessage), () => {})\n }\n }\n\n async handleUnsubscribe(\n client: IClientConnection,\n message: SignalMessage,\n ): Promise<void> {\n const { channel } = message\n\n // Check if client is subscribed\n if (!this.registry.isSubscribed(client.id, channel)) {\n throw new ChannelError(\n `Client not subscribed to channel: ${channel}`,\n )\n }\n\n // Unsubscribe from channel via registry\n this.registry.unsubscribe(client.id, channel)\n\n // Send acknowledgment\n if (this.options.sendAcknowledgments) {\n const ackMessage = createSignalMessage(\n channel,\n SignalType.UNSUBSCRIBED,\n undefined,\n message.id,\n )\n client.socket.send(JSON.stringify(ackMessage), () => {})\n }\n }\n\n async handlePing(\n client: IClientConnection,\n message: SignalMessage,\n ): Promise<void> {\n // Update client's last ping time\n client.lastPingAt = Date.now()\n\n // Auto-respond with PONG if enabled\n if (this.options.autoRespondToPing) {\n const pongMessage = createSignalMessage(\n message.channel,\n SignalType.PONG,\n undefined,\n message.id,\n )\n client.socket.send(JSON.stringify(pongMessage), () => {})\n }\n }\n\n async handlePong(\n client: IClientConnection,\n _message: SignalMessage,\n ): Promise<void> {\n // PONG is received, connection is alive\n // The timestamp is already updated by the transport layer\n client.lastPingAt = Date.now()\n }\n\n getOptions(): Readonly<Required<SignalHandlerOptions>> {\n return this.options\n }\n}\n","import type { IClientConnection, DataMessage } from '../types'\nimport { BaseChannel } from '../channel'\n\nimport { MessageError, ChannelError } from '../errors'\nimport { isDataMessage } from '../utils'\nimport { ContextManager } from '../context'\nimport { ClientRegistry } from '../registry'\n\nexport interface MessageHandlerOptions {\n requireChannel?: boolean\n}\n\nexport class MessageHandler {\n private readonly registry: ClientRegistry\n private readonly context: ContextManager\n private readonly options: Required<MessageHandlerOptions>\n\n constructor(dependencies: {\n registry: ClientRegistry\n context: ContextManager\n options?: MessageHandlerOptions\n }) {\n this.registry = dependencies.registry\n this.context = dependencies.context\n\n // Apply defaults\n this.options = {\n requireChannel: dependencies.options?.requireChannel ?? true,\n }\n }\n\n async handleMessage<T = unknown>(\n client: IClientConnection,\n message: DataMessage<T>,\n ): Promise<void> {\n // Validate message is a DataMessage\n if (!isDataMessage<T>(message)) {\n throw new MessageError(\n 'Invalid message type: expected DATA message',\n )\n }\n\n // Get channel using the registry\n const channel = this.registry.getChannel<T>(message.channel)\n\n // Validate channel exists\n if (this.options.requireChannel && !channel) {\n throw new ChannelError(`Channel not found: ${message.channel}`)\n }\n\n // Build the middleware pipeline\n const pipeline = this.context.getPipeline(channel)\n\n // Create Context\n const ctx = this.context.createMessageContext(client, message)\n\n // Define Kernel\n const kernel = async () => {\n if (channel) {\n await channel.dispatch(message.data, client, message)\n }\n }\n\n // Execute Onion\n await this.context.execute(ctx, pipeline, kernel)\n }\n\n canProcessMessage<T = unknown>(message: DataMessage<T>): boolean {\n if (!isDataMessage<T>(message)) {\n return false\n }\n\n if (this.options.requireChannel) {\n return !!this.registry.getChannel<T>(message.channel)\n }\n\n return true\n }\n\n getChannelForMessage<T = unknown>(\n message: DataMessage<T>,\n ): BaseChannel<T> | undefined {\n return this.registry.getChannel<T>(message.channel)\n }\n\n getOptions(): Readonly<Required<MessageHandlerOptions>> {\n return this.options\n }\n}\n","import type { IClientConnection } from '../types'\nimport { ClientRegistry } from '../registry'\nimport { CLOSE_CODES } from '../config'\n\nexport interface ConnectionHandlerOptions {\n rejectionCloseCode?: number\n}\n\nexport class ConnectionHandler {\n private readonly registry: ClientRegistry\n private readonly options: Required<ConnectionHandlerOptions>\n\n constructor(dependencies: {\n registry: ClientRegistry\n options?: ConnectionHandlerOptions\n }) {\n this.registry = dependencies.registry\n\n // Apply defaults\n this.options = {\n rejectionCloseCode:\n dependencies.options?.rejectionCloseCode ??\n CLOSE_CODES.REJECTED,\n }\n }\n\n async handleConnection(\n connection: IClientConnection,\n ): Promise<IClientConnection> {\n // Register client in registry\n const client = this.registry.register(connection)\n return client\n }\n\n async handleDisconnection(\n clientId: string,\n _reason?: string,\n ): Promise<void> {\n const client = this.registry.get(clientId)\n\n if (!client) {\n return // Client already unregistered\n }\n\n // Unregister client\n this.registry.unregister(clientId)\n }\n\n getOptions(): Readonly<Required<ConnectionHandlerOptions>> {\n return this.options\n }\n}\n","import type { Context, Middleware, Next } from './types'\n\n/**\n * Compose middleware functions into a single function.\n * Follows onion-style execution pattern.\n */\nexport const compose = <S = Record<string, unknown>>(\n middleware: Middleware<S>[],\n) => {\n return (context: Context<S>, next?: Next) => {\n let index = -1\n\n const dispatch = async (i: number): Promise<Context<S>> => {\n if (i <= index) throw new Error('next() called multiple times')\n index = i\n\n let res: unknown\n const handler = middleware[i]\n\n if (handler) {\n res = await handler(context, async () => {\n await dispatch(i + 1)\n })\n } else if (i === middleware.length && next) {\n res = await next()\n } else {\n return context\n }\n\n if (res !== undefined && !context.finalized) {\n context.res = res\n context.finalized = true\n }\n\n return context\n }\n\n return dispatch(0)\n }\n}\n","import { MiddlewareRejectionError, MiddlewareExecutionError } from './errors'\nimport { compose } from './compose'\nimport type {\n Context,\n IMiddlewareAction,\n IClientConnection,\n Message,\n ChannelName,\n IMiddleware,\n} from './types'\n\n/**\n * Context Data Options\n *\n * @remarks\n * Options for creating a new middleware context with pre-populated state.\n *\n * @template S - Type of the state object (default: Record<string, unknown>)\n *\n * @property action - The middleware action being performed\n * @property client - Optional client connection\n * @property message - Optional message being processed\n * @property channel - Optional channel name\n * @property initialState - Optional initial state values\n *\n * @example\n * ```ts\n * const options: ContextOptions<{ user: UserInfo }> = {\n * action: 'message',\n * client: connection,\n * message: dataMessage,\n * channel: 'chat',\n * initialState: { user: { id: '123', name: 'Alice' } }\n * }\n * ```\n */\nexport interface ContextOptions<S = Record<string, unknown>> {\n /** The middleware action being performed */\n action: IMiddlewareAction\n /** Optional client connection */\n client?: IClientConnection\n /** Optional message being processed */\n message?: Message\n /** Optional channel name */\n channel?: ChannelName\n /** Optional initial state values */\n initialState?: S\n}\n\n/**\n * Create onion pattern middleware context\n *\n * @remarks\n * Creates a lightweight middleware context using closures to ensure properties\n * can be destructured safely. The context provides access to request information,\n * state management, and control flow methods.\n *\n * @template S - Type of the state object (default: Record<string, unknown>)\n *\n * @param options - Context initialization options\n * @returns A new Context object\n *\n * @example\n * ### Basic usage\n * ```ts\n * const context = createContext({\n * action: 'message',\n * client: connection,\n * message: dataMessage,\n * channel: 'chat'\n * })\n * ```\n *\n * @example\n * ### With initial state\n * ```ts\n * interface AppState {\n * user: { id: string; name: string }\n * requestId: string\n * }\n *\n * const context = createContext<AppState>({\n * action: 'message',\n * initialState: {\n * user: { id: '123', name: 'Alice' },\n * requestId: generateId()\n * }\n * })\n *\n * // Access state\n * const user = context.get('user')\n * const requestId = context.get('requestId')\n * ```\n *\n * @see {@link Context} for the context interface\n */\nexport function createContext<S = Record<string, unknown>>(\n options: ContextOptions<S>,\n): Context<S> {\n const { action, client, message, channel, initialState = {} as S } = options\n const state = initialState as S\n\n return {\n req: {\n action,\n client,\n message,\n channel,\n },\n\n var: state,\n finalized: false,\n\n get: <K extends keyof S>(key: K): S[K] => {\n return state[key]\n },\n\n set: <K extends keyof S>(key: K, value: S[K]): void => {\n state[key] = value\n },\n\n reject: (reason: string): never => {\n throw new MiddlewareRejectionError(reason, action)\n },\n }\n}\n\n/**\n * Context Manager - manages and executes middleware functions\n *\n * @remarks\n * The ContextManager is responsible for registering middleware functions\n * and executing them in the correct order for various actions (connect,\n * disconnect, message, subscribe, unsubscribe).\n *\n * Key features:\n * - Global middleware registration\n * - Per-action middleware execution\n * - Channel-specific middleware support\n * - Automatic error wrapping and reporting\n *\n * @example\n * ```ts\n * import { ContextManager } from '@syncar/server'\n *\n * const manager = new ContextManager()\n *\n * // Register middleware\n * manager.use(async (context, next) => {\n * console.log(`Action: ${context.req.action}`)\n * await next()\n * })\n *\n * // Execute middleware for an action\n * const context = await manager.executeConnection(client, 'connect')\n * ```\n *\n * @see {@link createSyncarServer} for server-level context management\n */\nexport class ContextManager {\n /** Registered middleware functions */\n protected readonly middlewares: IMiddleware[] = []\n\n /**\n * Register a middleware function\n *\n * @remarks\n * Adds a middleware function to the global middleware chain.\n * Middleware is executed in the order it is registered.\n *\n * @param middleware - The middleware function to register\n *\n * @example\n * ```ts\n * manager.use(async (context, next) => {\n * console.log('Before')\n * await next()\n * console.log('After')\n * })\n * ```\n */\n use(middleware: IMiddleware): void {\n this.middlewares.push(middleware)\n }\n\n /**\n * Remove a middleware function\n *\n * @remarks\n * Removes a previously registered middleware function from the chain.\n *\n * @param middleware - The middleware function to remove\n * @returns `true` if the middleware was found and removed, `false` otherwise\n *\n * @example\n * ```ts\n * const middleware = async (context, next) => { /* ... *\\/ }\n * manager.use(middleware)\n *\n * // Later, remove it\n * if (manager.remove(middleware)) {\n * console.log('Middleware removed')\n * }\n * ```\n */\n remove(middleware: IMiddleware): boolean {\n const index = this.middlewares.indexOf(middleware)\n if (index !== -1) {\n this.middlewares.splice(index, 1)\n return true\n }\n return false\n }\n\n /**\n * Clear all middleware\n *\n * @remarks\n * Removes all registered middleware functions from the chain.\n *\n * @example\n * ```ts\n * manager.clear()\n * console.log('All middleware cleared')\n * ```\n */\n clear(): void {\n this.middlewares.length = 0\n }\n\n /**\n * Get all registered middleware\n *\n * @remarks\n * Returns a shallow copy of the middleware array to prevent\n * external modification.\n *\n * @returns Array of middleware functions\n *\n * @example\n * ```ts\n * const allMiddleware = manager.getMiddlewares()\n * console.log(`Registered middleware: ${allMiddleware.length}`)\n * ```\n */\n getMiddlewares(): IMiddleware[] {\n return [...this.middlewares]\n }\n\n /**\n * Get the complete middleware pipeline\n *\n * @remarks\n * Returns the combined middleware pipeline including global middleware\n * and any channel-specific middleware from the provided channel instance.\n *\n * @param channelInstance - Optional channel instance with middleware\n * @returns Combined array of middleware functions\n *\n * @example\n * ```ts\n * const chat = server.createMulticast('chat')\n * const pipeline = manager.getPipeline(chat)\n * // Returns global middleware + chat channel middleware\n * ```\n *\n * @internal\n */\n getPipeline(channelInstance?: {\n getMiddlewares?: () => IMiddleware[]\n }): IMiddleware[] {\n let pipeline = this.getMiddlewares()\n const channelMiddlewares = channelInstance?.getMiddlewares?.()\n\n if (channelMiddlewares && channelMiddlewares.length > 0) {\n pipeline = [...pipeline, ...channelMiddlewares]\n }\n\n return pipeline\n }\n\n /**\n * Execute middleware for connection actions\n *\n * @remarks\n * Creates a connection context and executes the middleware pipeline\n * for connect or disconnect actions.\n *\n * @param client - The client connection\n * @param action - The action ('connect' or 'disconnect')\n * @returns The executed context\n *\n * @example\n * ```ts\n * await manager.executeConnection(client, 'connect')\n * await manager.executeConnection(client, 'disconnect')\n * ```\n *\n * @internal\n */\n async executeConnection(\n client: IClientConnection,\n action: 'connect' | 'disconnect',\n ): Promise<Context> {\n const c = this.createConnectionContext(client, action)\n return await this.execute(c)\n }\n\n /**\n * Execute middleware for message actions\n *\n * @remarks\n * Creates a message context and executes the middleware pipeline\n * for incoming client messages.\n *\n * @param client - The client connection\n * @param message - The message being processed\n * @returns The executed context\n *\n * @example\n * ```ts\n * await manager.executeMessage(client, dataMessage)\n * ```\n *\n * @internal\n */\n async executeMessage(\n client: IClientConnection,\n message: Message,\n ): Promise<Context> {\n const c = this.createMessageContext(client, message)\n return await this.execute(c)\n }\n\n /**\n * Execute middleware for subscribe actions\n *\n * @remarks\n * Creates a subscribe context and executes the middleware pipeline\n * for channel subscription requests.\n *\n * @param client - The client connection\n * @param channel - The channel name\n * @param finalHandler - Optional final handler to execute after middleware\n * @returns The executed context\n *\n * @example\n * ```ts\n * await manager.executeSubscribe(client, 'chat', async () => {\n * // Final handler - perform the actual subscription\n * channel.subscribe(client.id)\n * })\n * ```\n *\n * @internal\n */\n async executeSubscribe(\n client: IClientConnection,\n channel: ChannelName,\n finalHandler?: () => Promise<void>,\n ): Promise<Context> {\n const c = this.createSubscribeContext(client, channel)\n return await this.execute(c, this.middlewares, finalHandler)\n }\n\n /**\n * Execute middleware for unsubscribe actions\n *\n * @remarks\n * Creates an unsubscribe context and executes the middleware pipeline\n * for channel unsubscription requests.\n *\n * @param client - The client connection\n * @param channel - The channel name\n * @param finalHandler - Optional final handler to execute after middleware\n * @returns The executed context\n *\n * @example\n * ```ts\n * await manager.executeUnsubscribe(client, 'chat', async () => {\n * // Final handler - perform the actual unsubscription\n * channel.unsubscribe(client.id)\n * })\n * ```\n *\n * @internal\n */\n async executeUnsubscribe(\n client: IClientConnection,\n channel: ChannelName,\n finalHandler?: () => Promise<void>,\n ): Promise<Context> {\n const c = this.createUnsubscribeContext(client, channel)\n return await this.execute(c, this.middlewares, finalHandler)\n }\n\n /**\n * Execute middleware pipeline\n *\n * @remarks\n * Executes the provided middleware functions in order, wrapping\n * each to capture and report errors appropriately.\n *\n * @param context - The middleware context\n * @param middlewares - The middleware functions to execute (defaults to registered middleware)\n * @param finalHandler - Optional final handler to execute after all middleware\n * @returns The executed context\n *\n * @throws {MiddlewareExecutionError} If a middleware function throws an unexpected error\n *\n * @example\n * ```ts\n * const context = createContext({ action: 'message' })\n * await manager.execute(context)\n * ```\n *\n * @internal\n */\n async execute(\n context: Context,\n middlewares: IMiddleware[] = this.middlewares,\n finalHandler?: () => Promise<void>,\n ): Promise<Context> {\n const action = context.req.action || 'unknown'\n\n // Wrap middlewares to capture specific execution errors\n const wrappedMiddlewares = middlewares.map((mw, i) => {\n return async (ctx: Context, next: () => Promise<void>) => {\n try {\n await mw(ctx, next)\n } catch (error) {\n // Re-throw if already handled or explicit rejection\n if (\n error instanceof MiddlewareRejectionError ||\n error instanceof MiddlewareExecutionError\n ) {\n throw error\n }\n\n const middlewareName = mw.name || `middleware[${i}]`\n throw new MiddlewareExecutionError(\n action,\n middlewareName,\n error instanceof Error\n ? error\n : new Error(String(error)),\n )\n }\n }\n })\n\n return await compose(wrappedMiddlewares)(\n context,\n finalHandler as () => Promise<void>,\n )\n }\n\n /**\n * Create a connection context\n *\n * @remarks\n * Creates a middleware context for connection or disconnect actions.\n *\n * @param client - The client connection\n * @param action - The action ('connect' or 'disconnect')\n * @returns A new connection context\n *\n * @example\n * ```ts\n * const context = manager.createConnectionContext(client, 'connect')\n * ```\n *\n * @internal\n */\n createConnectionContext(\n client: IClientConnection,\n action: 'connect' | 'disconnect',\n ): Context {\n return createContext({\n client,\n action,\n })\n }\n\n /**\n * Create a message context\n *\n * @remarks\n * Creates a middleware context for message processing.\n *\n * @param client - The client connection\n * @param message - The message being processed\n * @returns A new message context\n *\n * @example\n * ```ts\n * const context = manager.createMessageContext(client, dataMessage)\n * ```\n *\n * @internal\n */\n createMessageContext(client: IClientConnection, message: Message): Context {\n return createContext({\n client,\n message,\n action: 'message',\n })\n }\n\n /**\n * Create a subscribe context\n *\n * @remarks\n * Creates a middleware context for channel subscription.\n *\n * @param client - The client connection\n * @param channel - The channel name\n * @returns A new subscribe context\n *\n * @example\n * ```ts\n * const context = manager.createSubscribeContext(client, 'chat')\n * ```\n *\n * @internal\n */\n createSubscribeContext(\n client: IClientConnection,\n channel: ChannelName,\n ): Context {\n return createContext({\n client,\n channel,\n action: 'subscribe',\n })\n }\n\n /**\n * Create an unsubscribe context\n *\n * @remarks\n * Creates a middleware context for channel unsubscription.\n *\n * @param client - The client connection\n * @param channel - The channel name\n * @returns A new unsubscribe context\n *\n * @example\n * ```ts\n * const context = manager.createUnsubscribeContext(client, 'chat')\n * ```\n *\n * @internal\n */\n createUnsubscribeContext(\n client: IClientConnection,\n channel: ChannelName,\n ): Context {\n return createContext({\n client,\n channel,\n action: 'unsubscribe',\n })\n }\n\n /**\n * Get the number of registered middleware\n *\n * @returns The count of middleware functions\n *\n * @example\n * ```ts\n * console.log(`Middleware count: ${manager.getCount()}`)\n * ```\n */\n getCount(): number {\n return this.middlewares.length\n }\n\n /**\n * Check if any middleware is registered\n *\n * @returns `true` if at least one middleware is registered, `false` otherwise\n *\n * @example\n * ```ts\n * if (manager.hasMiddleware()) {\n * console.log('Middleware is configured')\n * }\n * ```\n */\n hasMiddleware(): boolean {\n return this.middlewares.length > 0\n }\n}\n","import type { IClientConnection, ClientId, ChannelName, ILogger } from './types'\nimport { BaseChannel } from './channel'\nimport { assertValidChannelName } from './utils'\n\n/**\n * Client Registry\n * Manages connected clients, their subscriptions, and channel instances.\n */\nexport class ClientRegistry {\n public readonly connections: Map<ClientId, IClientConnection> = new Map()\n private readonly subscriptions: Map<ClientId, Set<ChannelName>> = new Map()\n private readonly channels: Map<ChannelName, Set<ClientId>> = new Map()\n private readonly channelInstances: Map<ChannelName, BaseChannel<unknown>> =\n new Map()\n public readonly logger?: ILogger\n\n constructor(logger?: ILogger) {\n this.logger = logger\n }\n\n register(connection: IClientConnection): IClientConnection {\n this.connections.set(connection.id, connection)\n return connection\n }\n\n unregister(clientId: ClientId): boolean {\n const connection = this.get(clientId)\n if (!connection) return false\n\n this.clearSubscriptions(connection)\n return this.connections.delete(clientId)\n }\n\n private clearSubscriptions(connection: IClientConnection): void {\n const clientChannels = this.subscriptions.get(connection.id)\n if (!clientChannels) return\n\n for (const channelName of clientChannels) {\n this.channels.get(channelName)?.delete(connection.id)\n }\n\n this.subscriptions.delete(connection.id)\n }\n\n get(clientId: ClientId): IClientConnection | undefined {\n return this.connections.get(clientId)\n }\n\n getAll(): IClientConnection[] {\n return Array.from(this.connections.values())\n }\n\n getCount(): number {\n return this.connections.size\n }\n\n registerChannel(channel: BaseChannel<unknown>): void {\n // Create channel in internal map if not exists\n if (!this.channels.has(channel.name)) {\n this.channels.set(channel.name, new Set())\n }\n // Store the channel instance\n this.channelInstances.set(channel.name, channel)\n }\n\n getChannel<T = unknown>(name: ChannelName): BaseChannel<T> | undefined {\n return this.channelInstances.get(name) as BaseChannel<T> | undefined\n }\n\n removeChannel(name: ChannelName): boolean {\n const subscribers = this.channels.get(name)\n if (!subscribers) return false\n\n // Remove channel from all subscribers' subscription sets\n for (const clientId of subscribers) {\n const clientChannels = this.subscriptions.get(clientId)\n if (clientChannels) {\n clientChannels.delete(name)\n }\n }\n\n // Remove the channel instance\n this.channelInstances.delete(name)\n\n // Remove the channel\n return this.channels.delete(name)\n }\n\n subscribe(clientId: ClientId, channel: ChannelName): boolean {\n assertValidChannelName(channel)\n // Verify client exists\n if (!this.connections.has(clientId)) return false\n\n // Create channel if not exists\n if (!this.channels.has(channel)) {\n this.channels.set(channel, new Set())\n }\n\n const subscribers = this.channels.get(channel)!\n\n // Check if already subscribed\n if (subscribers.has(clientId)) return true\n\n // Add to channels map (reverse index)\n subscribers.add(clientId)\n\n // Add to subscriptions map (forward index)\n if (!this.subscriptions.has(clientId)) {\n this.subscriptions.set(clientId, new Set())\n }\n this.subscriptions.get(clientId)!.add(channel)\n\n return true\n }\n\n unsubscribe(clientId: ClientId, channel: ChannelName): boolean {\n const subscribers = this.channels.get(channel)\n if (!subscribers || !subscribers.has(clientId)) return false\n\n // Remove from channels map (reverse index)\n subscribers.delete(clientId)\n\n // Remove from subscriptions map (forward index)\n const clientChannels = this.subscriptions.get(clientId)\n if (clientChannels) {\n clientChannels.delete(channel)\n // Clean up empty subscription sets\n if (clientChannels.size === 0) {\n this.subscriptions.delete(clientId)\n }\n }\n\n return true\n }\n\n getSubscribers(channel: ChannelName): IClientConnection[] {\n const subscriberIds = this.channels.get(channel)\n if (!subscriberIds) return []\n\n const subscribers: IClientConnection[] = []\n for (const id of subscriberIds) {\n const client = this.connections.get(id)\n if (client) {\n subscribers.push(client)\n }\n }\n\n return subscribers\n }\n\n getSubscriberCount(channel: ChannelName): number {\n return this.channels.get(channel)?.size ?? 0\n }\n\n getChannels(): ChannelName[] {\n return Array.from(this.channels.keys())\n }\n\n getTotalSubscriptionCount(): number {\n let total = 0\n for (const subscribers of this.channels.values()) {\n total += subscribers.size\n }\n return total\n }\n\n isSubscribed(clientId: ClientId, channel: ChannelName): boolean {\n return this.channels.get(channel)?.has(clientId) ?? false\n }\n\n getClientChannels(clientId: ClientId): Set<ChannelName> {\n return this.subscriptions.get(clientId) ?? new Set()\n }\n\n getChannelSubscribers(channel: ChannelName): Set<ClientId> {\n return this.channels.get(channel) ?? new Set()\n }\n\n clear(): void {\n this.connections.clear()\n this.subscriptions.clear()\n this.channels.clear()\n this.channelInstances.clear()\n }\n}\n","import { EventEmitter } from 'node:events'\nimport {\n WebSocketServer as WsServer,\n type ServerOptions as WsServerOptions,\n type WebSocket,\n} from 'ws'\nimport {\n MessageType,\n SignalType,\n type ClientId,\n type IClientConnection,\n type ILogger,\n type IdGenerator,\n} from './types'\nimport {\n DEFAULT_MAX_PAYLOAD,\n DEFAULT_PING_INTERVAL,\n DEFAULT_PING_TIMEOUT,\n DEFAULT_WS_PATH,\n} from './config'\nimport { generateClientId } from './utils'\n\n// Instance types\ntype ServerInstance = WsServer\n\n/**\n * WebSocket Server Transport Configuration\n *\n * @remarks\n * Configuration options for the WebSocket transport layer. Extends the\n * standard `ws` library options with Syncar-specific settings.\n *\n * @example\n * ```ts\n * const config: WebSocketServerTransportConfig = {\n * server: httpServer,\n * path: '/ws',\n * enablePing: true,\n * pingInterval: 30000,\n * pingTimeout: 5000,\n * maxPayload: 1048576,\n * connections: new Map(),\n * generateId: (request) => extractUserId(request),\n * logger: console\n * }\n * ```\n */\nexport interface WebSocketServerTransportConfig extends WsServerOptions {\n /**\n * Enable client ping/pong\n *\n * @remarks\n * When enabled, the server sends periodic ping frames to detect\n * dead connections and maintain keep-alive.\n *\n * @default true\n */\n enablePing?: boolean\n\n /**\n * Ping interval in milliseconds\n *\n * @remarks\n * Time between ping frames when `enablePing` is true.\n *\n * @default 30000 (30 seconds)\n */\n pingInterval?: number\n\n /**\n * Ping timeout in milliseconds\n *\n * @remarks\n * Time to wait for pong response before closing connection.\n *\n * @default 5000 (5 seconds)\n */\n pingTimeout?: number\n\n /**\n * Shared connection map\n *\n * @remarks\n * Optional map for sharing connections across multiple server instances.\n * If not provided, a new map will be created.\n */\n connections?: Map<ClientId, IClientConnection>\n\n /**\n * Custom ID generator for new connections\n *\n * @remarks\n * Function to generate unique client IDs from incoming HTTP requests.\n * Useful for implementing custom authentication or ID generation strategies.\n *\n * @example\n * ```ts\n * generateId: async (request) => {\n * const token = request.headers.authorization?.split(' ')[1]\n * return verifyToken(token).then(user => user.id)\n * }\n * ```\n */\n generateId?: IdGenerator\n\n /**\n * Custom WebSocket Server constructor\n *\n * @remarks\n * Allows using a custom WebSocket server implementation.\n * Defaults to the standard `ws` WebSocketServer.\n */\n ServerConstructor?: new (config: WsServerOptions) => ServerInstance\n\n /**\n * Logger instance\n *\n * @remarks\n * Optional logger for transport-level logging.\n */\n logger?: ILogger\n}\n\n/**\n * WebSocket Server Transport\n *\n * @remarks\n * Handles low-level WebSocket communication using the `ws` library.\n * Manages connections, message parsing, ping/pong keep-alive, and\n * emits high-level events for the server to consume.\n *\n * This transport:\n * - Wraps the `ws` WebSocketServer\n * - Generates unique client IDs\n * - Handles connection lifecycle (connect, disconnect, error)\n * - Parses incoming messages as JSON\n * - Manages ping/pong for connection health\n * - Emits typed events for server consumption\n *\n * @example\n * ### Basic usage\n * ```ts\n * import { WebSocketServerTransport } from '@syncar/server'\n *\n * const transport = new WebSocketServerTransport({\n * server: httpServer,\n * path: '/ws',\n * enablePing: true,\n * pingInterval: 30000,\n * pingTimeout: 5000\n * })\n *\n * transport.on('connection', (client) => {\n * console.log(`Client connected: ${client.id}`)\n * })\n *\n * transport.on('message', (clientId, message) => {\n * console.log(`Message from ${clientId}:`, message)\n * })\n *\n * transport.on('disconnection', (clientId) => {\n * console.log(`Client disconnected: ${clientId}`)\n * })\n * ```\n *\n * @example\n * ### With custom ID generator\n * ```ts\n * const transport = new WebSocketServerTransport({\n * server: httpServer,\n * generateId: async (request) => {\n * const token = request.headers.authorization?.split(' ')[1]\n * const user = await verifyJwt(token)\n * return user.id\n * }\n * })\n * ```\n *\n * @example\n * ### With shared connections\n * ```ts\n * const sharedConnections = new Map()\n *\n * const transport1 = new WebSocketServerTransport({\n * connections: sharedConnections\n * })\n *\n * const transport2 = new WebSocketServerTransport({\n * connections: sharedConnections\n * })\n * ```\n *\n * @see {@link EventEmitter} for event methods (on, off, emit, etc.)\n */\nexport class WebSocketServerTransport extends EventEmitter {\n /**\n * Map of connected clients by ID\n *\n * @remarks\n * Public map of all active connections. Can be used to look up clients\n * by ID or iterate over all connections.\n */\n public readonly connections: Map<ClientId, IClientConnection>\n\n /** @internal */\n private readonly wsServer: ServerInstance\n /** @internal */\n private readonly config: WebSocketServerTransportConfig & {\n pingInterval: number\n pingTimeout: number\n enablePing: boolean\n }\n /** @internal */\n private pingTimer?: ReturnType<typeof setInterval>\n /** @internal */\n private authenticator?: (\n request: import('node:http').IncomingMessage,\n ) => string | Promise<string>\n\n /**\n * Creates a new WebSocket Server Transport instance\n *\n * @remarks\n * Initializes the WebSocket transport layer with the provided configuration.\n * Sets up the underlying WebSocketServer, configures ping/pong, and\n * establishes event handlers.\n *\n * @param config - Transport configuration options\n *\n * @example\n * ```ts\n * const transport = new WebSocketServerTransport({\n * server: httpServer,\n * path: '/ws',\n * enablePing: true,\n * pingInterval: 30000,\n * pingTimeout: 5000\n * })\n * ```\n *\n * @emits connection When a new client connects\n * @emits disconnection When a client disconnects\n * @emits message When a message is received from a client\n * @emits error When an error occurs\n */\n constructor(config: WebSocketServerTransportConfig) {\n super()\n this.setMaxListeners(100)\n\n this.connections = config.connections ?? new Map()\n\n this.config = {\n ...config,\n path: config.path ?? DEFAULT_WS_PATH,\n maxPayload: config.maxPayload ?? DEFAULT_MAX_PAYLOAD,\n enablePing: config.enablePing ?? true,\n pingInterval: config.pingInterval ?? DEFAULT_PING_INTERVAL,\n pingTimeout: config.pingTimeout ?? DEFAULT_PING_TIMEOUT,\n connections: this.connections,\n }\n\n const ServerConstructor = config.ServerConstructor ?? WsServer\n this.wsServer = new ServerConstructor({\n server: this.config.server,\n path: this.config.path,\n maxPayload: this.config.maxPayload,\n })\n\n this.setupEventHandlers()\n\n if (this.config.enablePing) {\n this.startPingTimer()\n }\n }\n\n /**\n * Set a custom authentication handler\n *\n * @remarks\n * Sets an authenticator function that receives the HTTP upgrade request\n * and returns a client ID. The authenticator can throw to reject the connection.\n *\n * @param authenticator - Function that receives the HTTP upgrade request\n * and returns a client ID (or throws to reject)\n *\n * @example\n * ```ts\n * transport.setAuthenticator(async (request) => {\n * const token = request.headers.authorization?.split(' ')[1]\n * if (!token) {\n * throw new Error('No token provided')\n * }\n * const user = await verifyJwt(token)\n * return user.id\n * })\n * ```\n */\n setAuthenticator(\n authenticator: (\n request: import('node:http').IncomingMessage,\n ) => string | Promise<string>,\n ): void {\n this.authenticator = authenticator\n }\n\n private setupEventHandlers(): void {\n this.wsServer.on(\n 'connection',\n (\n socket: WebSocket,\n request: import('node:http').IncomingMessage,\n ) => {\n this.handleConnection(socket, request)\n },\n )\n\n this.wsServer.on('error', (error: Error) => {\n this.config.logger?.error('WebSocket Server Error:', error)\n this.emit('error', error)\n })\n }\n\n private async handleConnection(\n socket: WebSocket,\n request: import('node:http').IncomingMessage,\n ): Promise<void> {\n let clientId: ClientId\n\n try {\n if (this.authenticator) {\n clientId = (await this.authenticator(request)) as ClientId\n } else if (this.config.generateId) {\n clientId = await this.config.generateId(request)\n } else {\n clientId = generateClientId()\n }\n } catch (error) {\n try {\n socket.close(\n 4001,\n error instanceof Error ? error.message : 'Unauthorized',\n )\n } catch (e) {\n // Ignore close error\n }\n return\n }\n\n const connectedAt = Date.now()\n\n const connection: IClientConnection = {\n socket,\n id: clientId,\n connectedAt,\n lastPingAt: connectedAt,\n }\n\n this.connections.set(clientId, connection)\n\n socket.on('message', (data: Buffer) => {\n this.handleMessage(clientId, data)\n })\n\n socket.on('close', (_code: number, _reason: Buffer) => {\n this.handleDisconnection(clientId)\n })\n\n socket.on('error', (error: Error) => {\n this.emit('error', error)\n })\n\n if (this.config.enablePing) {\n this.setupPingPong(clientId, socket)\n }\n\n // Emit connection event\n this.emit('connection', connection)\n }\n\n private handleMessage(clientId: ClientId, data: Buffer): void {\n try {\n const message = JSON.parse(data.toString())\n const connection = this.connections.get(clientId)\n\n if (\n connection &&\n message.type === MessageType.SIGNAL &&\n message.signal === SignalType.PONG\n ) {\n connection.lastPingAt = Date.now()\n }\n\n // Emit message event\n this.emit('message', clientId, message)\n } catch (error) {\n this.config.logger?.error(\n `Failed to parse message from ${clientId}:`,\n error as Error,\n )\n this.emit('error', error as Error)\n }\n }\n\n private handleDisconnection(clientId: ClientId): void {\n this.emit('disconnection', clientId)\n this.connections.delete(clientId)\n }\n\n private setupPingPong(clientId: ClientId, socket: WebSocket): void {\n socket.on('pong', () => {\n const connection = this.connections.get(clientId)\n if (connection) {\n connection.lastPingAt = Date.now()\n }\n })\n }\n\n private startPingTimer(): void {\n if (this.pingTimer) {\n clearInterval(this.pingTimer)\n }\n\n this.pingTimer = setInterval(() => {\n this.checkConnections()\n }, this.config.pingInterval)\n }\n\n private checkConnections(): void {\n const now = Date.now()\n const connections = Array.from(this.connections.values())\n\n for (const connection of connections) {\n const socket = connection.socket\n const lastPing = connection.lastPingAt ?? connection.connectedAt\n\n // Check for timeout\n if (\n now - lastPing >\n this.config.pingInterval + this.config.pingTimeout\n ) {\n socket.close(1000, 'Ping timeout')\n continue\n }\n\n // Send ping if socket is open\n if (socket.readyState === 1) {\n socket.ping()\n }\n }\n }\n}\n","import {\n type ChannelName,\n type Message,\n type IMiddleware,\n MessageType,\n} from './types'\nimport type { ILogger, IdGenerator } from './types'\nimport { createDefaultLogger, assertValidChannelName } from './utils'\n\n/**\n * Server statistics interface\n *\n * @remarks\n * Provides real-time statistics about the server state including\n * connected clients, active channels, and total subscriptions.\n *\n * @property clientCount - Number of currently connected clients\n * @property channelCount - Number of active channels\n * @property subscriptionCount - Total number of channel subscriptions across all channels\n * @property startedAt - Unix timestamp (ms) when the server was started\n *\n * @example\n * ```ts\n * const server = createSyncarServer({ port: 3000 })\n * await server.start()\n *\n * const stats = server.getStats()\n * console.log(`Clients: ${stats.clientCount}`)\n * console.log(`Channels: ${stats.channelCount}`)\n * console.log(`Started at: ${new Date(stats.startedAt!).toLocaleString()}`)\n * ```\n */\nexport interface IServerStats {\n /** Number of currently connected clients */\n clientCount: number\n /** Number of active channels */\n channelCount: number\n /** Total number of channel subscriptions across all channels */\n subscriptionCount: number\n /** Unix timestamp (ms) when the server was started */\n startedAt?: number\n}\n\nimport { MulticastChannel, BroadcastChannel } from './channel'\nimport { ConnectionHandler, MessageHandler, SignalHandler } from './handlers'\nimport { ContextManager } from './context'\nimport { StateError } from './errors'\nimport { ClientRegistry } from './registry'\nimport { WebSocketServerTransport } from './websocket'\nimport { DEFAULT_SERVER_CONFIG, DEFAULT_MAX_PAYLOAD } from './config'\n\ninterface ServerState {\n started: boolean\n startedAt: number | undefined\n}\n\n/**\n * Server configuration options\n *\n * @remarks\n * Complete configuration interface for the Syncar server. These options\n * control the WebSocket transport layer, connection handling, middleware,\n * and performance tuning parameters.\n *\n * @example\n * ```ts\n * import { createSyncarServer } from '@syncar/server'\n *\n * const server = createSyncarServer({\n * port: 3000,\n * host: '0.0.0.0',\n * path: '/ws',\n * enablePing: true,\n * pingInterval: 30000,\n * pingTimeout: 5000,\n * broadcastChunkSize: 500,\n * })\n * ```\n *\n * @see {@link DEFAULT_SERVER_CONFIG} for default values\n */\nexport interface IServerOptions {\n /**\n * HTTP or HTTPS server instance\n *\n * @remarks\n * If provided, the WebSocket server will attach to this existing server.\n * If not provided, a new HTTP server will be created automatically.\n */\n server?: import('node:http').Server | import('node:https').Server\n\n /**\n * Custom connection ID generator\n *\n * @remarks\n * Function to generate unique client IDs from incoming HTTP requests.\n * Useful for implementing custom authentication or ID generation strategies.\n *\n * @example\n * ```ts\n * generateId: (request) => {\n * const token = request.headers.authorization?.split(' ')[1]\n * return extractUserIdFromToken(token)\n * }\n * ```\n */\n generateId?: IdGenerator\n\n /**\n * Custom logger instance\n *\n * @remarks\n * Logger conforming to the {@link ILogger} interface. Used for\n * debugging, error reporting, and operational monitoring.\n */\n logger: ILogger\n\n /**\n * Port to listen on (default: 3000)\n *\n * @remarks\n * Only used when creating a new HTTP server. Ignored if `server`\n * option is provided.\n */\n port: number\n\n /**\n * Host to bind to (default: '0.0.0.0')\n *\n * @remarks\n * Determines which network interface the server listens on.\n * Use 'localhost' for local-only access or '0.0.0.0' for all interfaces.\n */\n host: string\n\n /**\n * WebSocket path (default: '/syncar')\n *\n * @remarks\n * The URL path for WebSocket connections. Clients must connect to\n * `ws://host:port/path` to establish a connection.\n */\n path: string\n\n /**\n * Transport implementation\n *\n * @remarks\n * Custom WebSocket transport layer. Defaults to {@link WebSocketServerTransport}\n * if not provided. Allows for custom transport implementations.\n */\n transport: WebSocketServerTransport\n\n /**\n * Enable automatic ping/pong (default: true)\n *\n * @remarks\n * When enabled, the server sends periodic ping frames to detect\n * dead connections and maintain keep-alive.\n */\n enablePing: boolean\n\n /**\n * Ping interval in ms (default: 30000)\n *\n * @remarks\n * Time between ping frames when `enablePing` is true.\n * Lower values detect dead connections faster but increase bandwidth.\n */\n pingInterval: number\n\n /**\n * Ping timeout in ms (default: 5000)\n *\n * @remarks\n * Time to wait for pong response before closing connection.\n * Should be significantly less than `pingInterval`.\n */\n pingTimeout: number\n\n /**\n * Client registry instance\n *\n * @remarks\n * Shared registry for tracking clients and subscriptions.\n * Allows multiple server instances to share state.\n */\n registry: ClientRegistry\n\n /**\n * Global middleware chain\n *\n * @remarks\n * Middleware functions applied to all actions before channel-specific middleware.\n * Executed in the order they are defined.\n *\n * @example\n * ```ts\n * middleware: [\n * createAuthMiddleware({ verifyToken }),\n * createLoggingMiddleware(),\n * createRateLimitMiddleware({ maxRequests: 100 })\n * ]\n * ```\n */\n middleware: IMiddleware[]\n\n /**\n * Chunk size for large broadcasts (default: 500)\n *\n * @remarks\n * When broadcasting to more than this many clients, messages are sent\n * in chunks to avoid blocking the event loop. Lower values reduce latency\n * per chunk but increase total broadcast time.\n */\n broadcastChunkSize: number\n}\n\n/**\n * Syncar Server - Real-time WebSocket server with pub/sub channels\n *\n * @remarks\n * The main server class providing WebSocket communication with broadcast\n * and multicast channels, middleware support, and connection management.\n *\n * @example\n * ```ts\n * import { createSyncarServer } from '@syncar/server'\n *\n * const server = createSyncarServer({ port: 3000 })\n * await server.start()\n *\n * // Create channels\n * const broadcast = server.createBroadcast<string>()\n * const chat = server.createMulticast<{ text: string }>('chat')\n *\n * // Listen for events\n * server.on('connection', (client) => {\n * console.log(`Client connected: ${client.id}`)\n * })\n *\n * // Publish messages\n * broadcast.publish('Hello everyone!')\n * chat.publish({ text: 'Welcome!' })\n * ```\n *\n * @see {@link createSyncarServer} for factory function\n */\nexport class SyncarServer {\n private readonly config: IServerOptions\n private transport: WebSocketServerTransport | undefined\n public readonly registry: ClientRegistry\n private readonly context: ContextManager\n private readonly status: ServerState = {\n started: false,\n startedAt: undefined,\n }\n private connectionHandler: ConnectionHandler | undefined\n private messageHandler: MessageHandler | undefined\n private signalHandler: SignalHandler | undefined\n private broadcastChannel: BroadcastChannel<unknown> | undefined\n\n constructor(config: IServerOptions) {\n this.config = config\n\n // Use the injected client registry from options\n this.registry = this.config.registry\n\n // Create context manager\n this.context = new ContextManager()\n\n // Register any middleware from config\n if (this.config.middleware) {\n for (const mw of this.config.middleware) {\n this.context.use(mw)\n }\n }\n }\n\n /**\n * Start the server and begin accepting connections\n *\n * @remarks\n * Initializes the WebSocket transport layer, sets up event handlers,\n * creates the broadcast channel, and prepares the server for connections.\n *\n * @throws {StateError} If the server is already started\n *\n * @example\n * ```ts\n * const server = createSyncarServer({ port: 3000 })\n * await server.start()\n * console.log('Server is running')\n * ```\n */\n async start(): Promise<void> {\n if (this.status.started) {\n throw new StateError('Server is already started')\n }\n\n // Use provided transport from config\n this.transport = this.config.transport\n\n // Create handlers\n this.connectionHandler = new ConnectionHandler({\n registry: this.registry,\n })\n this.messageHandler = new MessageHandler({\n registry: this.registry,\n context: this.context,\n })\n this.signalHandler = new SignalHandler({\n registry: this.registry,\n context: this.context,\n })\n\n // Set up transport event handlers\n this.setupTransportHandlers()\n\n // Create broadcast channel and register it\n this.broadcastChannel = new BroadcastChannel(\n this.registry,\n this.config.broadcastChunkSize,\n )\n this.registry.registerChannel(this.broadcastChannel)\n\n // Update state\n this.status.started = true\n this.status.startedAt = Date.now()\n }\n\n /**\n * Stop the server and close all connections\n *\n * @remarks\n * Gracefully shuts down the server by clearing all handlers,\n * removing all channels, and allowing the transport layer to close.\n * Existing connections will be terminated.\n *\n * @example\n * ```ts\n * await server.stop()\n * console.log('Server stopped')\n * ```\n */\n async stop(): Promise<void> {\n if (!this.status.started || !this.transport) {\n return // Already stopped\n }\n\n // Clear handlers\n this.connectionHandler = undefined\n this.messageHandler = undefined\n this.signalHandler = undefined\n\n // Clear channels from registry\n this.registry.clear()\n this.broadcastChannel = undefined\n\n // Update state\n this.status.started = false\n this.status.startedAt = undefined\n }\n\n /**\n * Get or create the broadcast channel\n *\n * @remarks\n * Returns the singleton broadcast channel that sends messages to ALL\n * connected clients. No subscription is required - all clients receive\n * broadcast messages automatically.\n *\n * @template T - Type of data to be broadcast (default: unknown)\n * @returns The broadcast channel instance\n *\n * @throws {StateError} If the server hasn't been started yet\n *\n * @example\n * ```ts\n * // Broadcast a string to all clients\n * const broadcast = server.createBroadcast<string>()\n * broadcast.publish('Server maintenance in 5 minutes')\n *\n * // Broadcast an object\n * const alerts = server.createBroadcast<{ type: string; message: string }>()\n * alerts.publish({ type: 'warning', message: 'High load detected' })\n *\n * // Exclude specific clients\n * broadcast.publish('Admin message', { exclude: ['client-123'] })\n *\n * // Send to specific clients only\n * broadcast.publish('Private message', { to: ['client-1', 'client-2'] })\n * ```\n *\n * @see {@link BroadcastChannel} for channel API\n */\n createBroadcast<T = unknown>(): BroadcastChannel<T> {\n if (!this.status.started || !this.broadcastChannel) {\n throw new StateError(\n 'Server must be started before creating channels',\n )\n }\n return this.broadcastChannel as BroadcastChannel<T>\n }\n\n /**\n * Create or retrieve a multicast channel\n *\n * @remarks\n * Creates a named channel that delivers messages only to subscribed clients.\n * Clients must explicitly subscribe to receive messages. If a channel with\n * the given name already exists, it will be returned instead of creating a new one.\n *\n * @template T - Type of data to be published on this channel (default: unknown)\n * @param name - Unique channel name (must not start with `__` which is reserved)\n * @returns The multicast channel instance\n *\n * @throws {StateError} If the server hasn't been started yet\n * @throws {ValidationError} If the channel name is invalid (starts with `__`)\n *\n * @example\n * ```ts\n * // Create a chat channel\n * const chat = server.createMulticast<{ text: string; user: string }>('chat')\n *\n * // Handle incoming messages\n * chat.onMessage((data, client) => {\n * console.log(`${client.id}: ${data.text}`)\n * // Relay to all subscribers except sender\n * chat.publish(data, { exclude: [client.id] })\n * })\n *\n * // Publish to all subscribers\n * chat.publish({ text: 'Hello!', user: 'System' })\n *\n * // Check channel existence\n * if (server.hasChannel('chat')) {\n * const existingChat = server.createMulticast('chat')\n * }\n *\n * // Get all channel names\n * const channels = server.getChannels()\n * // ['chat', 'notifications', 'presence']\n * ```\n *\n * @see {@link MulticastChannel} for channel API\n * @see {@link BROADCAST_CHANNEL} for reserved channel name\n */\n createMulticast<T = unknown>(name: ChannelName): MulticastChannel<T> {\n assertValidChannelName(name)\n if (!this.status.started || !this.transport) {\n throw new StateError(\n 'Server must be started before creating channels',\n )\n }\n\n const existing = this.registry.getChannel<T>(name) as\n | MulticastChannel<T>\n | undefined\n if (existing) return existing\n\n const channel = new MulticastChannel<T>({\n name,\n registry: this.registry,\n options: {\n chunkSize: this.config.broadcastChunkSize,\n },\n })\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.registry.registerChannel(channel as any)\n return channel\n }\n\n /**\n * Check if a channel exists\n *\n * @param name - The channel name to check\n * @returns `true` if a channel with this name exists, `false` otherwise\n *\n * @example\n * ```ts\n * if (!server.hasChannel('chat')) {\n * const chat = server.createMulticast('chat')\n * }\n * ```\n */\n hasChannel(name: ChannelName): boolean {\n return !!this.registry.getChannel(name)\n }\n\n /**\n * Get all active channel names\n *\n * @returns Array of channel names currently registered on the server\n *\n * @example\n * ```ts\n * const channels = server.getChannels()\n * console.log('Active channels:', channels)\n * // ['chat', 'notifications', 'presence']\n * ```\n */\n getChannels(): ChannelName[] {\n return this.registry.getChannels()\n }\n\n /**\n * Register a global middleware\n *\n * @remarks\n * Adds a middleware function to the global middleware chain.\n * Global middleware runs before channel-specific middleware for all actions.\n *\n * @param middleware - The middleware function to register\n *\n * @example\n * ```ts\n * import { createAuthMiddleware } from '@syncar/server'\n *\n * server.use(createAuthMiddleware({\n * verifyToken: async (token) => jwt.verify(token, SECRET)\n * }))\n * ```\n *\n * @see {@link IMiddleware} for middleware interface\n */\n use(middleware: IMiddleware): void {\n this.context.use(middleware)\n }\n\n /**\n * Set a custom authentication handler for connection validation\n *\n * @remarks\n * Sets an authenticator function that receives the HTTP upgrade request\n * and returns a client ID. The authenticator can throw to reject the connection.\n * This is useful for implementing token-based authentication during the\n * WebSocket handshake.\n *\n * @param authenticator - A function that receives the HTTP upgrade request\n * and returns a ClientId (or throws to reject the connection)\n *\n * @example\n * ```ts\n * server.authenticate(async (request) => {\n * const token = request.headers.authorization?.split(' ')[1]\n * if (!token) {\n * throw new Error('No token provided')\n * }\n * const user = await verifyJwt(token)\n * return user.id\n * })\n * ```\n */\n authenticate(\n authenticator: (\n request: import('node:http').IncomingMessage,\n ) => string | Promise<string>,\n ): void {\n const transport = this.transport || this.config.transport\n if (transport && 'setAuthenticator' in transport) {\n ;(transport as any).setAuthenticator(authenticator)\n } else {\n this.config.logger.warn(\n 'Current transport does not support setting an authenticator.',\n )\n }\n }\n\n /**\n * Get server statistics\n *\n * @returns Server statistics including client count, channel count,\n * subscription count, and start time\n *\n * @example\n * ```ts\n * const stats = server.getStats()\n * console.log(`Clients: ${stats.clientCount}`)\n * console.log(`Channels: ${stats.channelCount}`)\n * console.log(`Subscriptions: ${stats.subscriptionCount}`)\n * console.log(`Started: ${new Date(stats.startedAt!).toLocaleString()}`)\n * ```\n *\n * @see {@link IServerStats} for statistics structure\n */\n getStats(): IServerStats {\n return {\n startedAt: this.status.startedAt,\n clientCount: this.registry.getCount(),\n channelCount: this.registry.getChannels().length,\n subscriptionCount: this.registry.getTotalSubscriptionCount(),\n }\n }\n\n /**\n * Get the server configuration (read-only)\n *\n * @returns Readonly copy of the server configuration options\n *\n * @example\n * ```ts\n * const config = server.getConfig()\n * console.log(`Port: ${config.port}`)\n * console.log(`Host: ${config.host}`)\n * console.log(`Path: ${config.path}`)\n * ```\n */\n getConfig(): Readonly<IServerOptions> {\n return this.config\n }\n\n /**\n * Get the client registry\n *\n * @returns The client registry instance used by this server\n *\n * @remarks\n * The registry manages client connections and channel subscriptions.\n * Direct access allows for advanced operations like manual client\n * lookup or subscription management.\n *\n * @example\n * ```ts\n * const registry = server.getRegistry()\n * const client = registry.get('client-123')\n * if (client) {\n * console.log(`Client connected at: ${new Date(client.connectedAt).toLocaleString()}`)\n * }\n * ```\n *\n * @see {@link ClientRegistry} for registry API\n */\n getRegistry(): ClientRegistry {\n return this.registry\n }\n\n private setupTransportHandlers(): void {\n const transport = this.transport!\n\n transport.on('connection', async (connection) => {\n try {\n await this.context.executeConnection(connection, 'connect')\n await this.connectionHandler!.handleConnection(connection)\n } catch (error) {\n this.config.logger.error(\n 'Error handling connection:',\n error as Error,\n )\n }\n })\n\n transport.on('disconnection', async (clientId) => {\n try {\n const client = this.registry.get(clientId)\n if (client) {\n await this.context.executeConnection(client, 'disconnect')\n await this.connectionHandler!.handleDisconnection(clientId)\n }\n } catch (error) {\n this.config.logger.error(\n 'Error handling disconnection:',\n error as Error,\n )\n }\n })\n\n transport.on('message', async (clientId: string, message: Message) => {\n try {\n const client = this.registry.get(clientId)\n if (!client) return\n\n if (message.type === MessageType.DATA) {\n await this.messageHandler!.handleMessage(client, message)\n } else if (message.type === MessageType.SIGNAL) {\n await this.signalHandler!.handleSignal(client, message)\n }\n } catch (error) {\n this.config.logger.error(\n 'Error handling message:',\n error as Error,\n )\n }\n })\n\n transport.on('error', (error: Error) => {\n this.config.logger.error('Transport error:', error)\n })\n }\n}\n\n/**\n * Create a Syncar server with automatic WebSocket transport setup\n *\n * @remarks\n * Factory function that creates a configured SyncarServer instance.\n * Automatically sets up the WebSocket transport layer if not provided,\n * merges user configuration with defaults, and creates the client registry.\n *\n * @param config - Optional partial server configuration. All properties are optional\n * and will be merged with {@link DEFAULT_SERVER_CONFIG}.\n *\n * @returns Configured Syncar server instance ready to be started\n *\n * @example\n * ### Basic usage\n * ```ts\n * import { createSyncarServer } from '@syncar/server'\n *\n * const server = createSyncarServer({ port: 3000 })\n * await server.start()\n * ```\n *\n * @example\n * ### With custom configuration\n * ```ts\n * const server = createSyncarServer({\n * port: 8080,\n * host: 'localhost',\n * path: '/ws',\n * enablePing: true,\n * pingInterval: 30000,\n * pingTimeout: 5000,\n * broadcastChunkSize: 1000,\n * })\n * await server.start()\n * ```\n *\n * @example\n * ### With existing HTTP server\n * ```ts\n * import { createServer } from 'node:http'\n * import { createSyncarServer } from '@syncar/server'\n *\n * const httpServer = createServer((req, res) => {\n * res.writeHead(200)\n * res.end('OK')\n * })\n *\n * const server = createSyncarServer({\n * server: httpServer,\n * path: '/ws',\n * })\n *\n * await server.start()\n * ```\n *\n * @example\n * ### With Express\n * ```ts\n * import express from 'express'\n * import { createSyncarServer } from '@syncar/server'\n *\n * const app = express()\n * const httpServer = app.listen(3000)\n *\n * const server = createSyncarServer({\n * server: httpServer,\n * path: '/ws',\n * })\n *\n * await server.start()\n * ```\n *\n * @example\n * ### With custom logger\n * ```ts\n * import { createSyncarServer } from '@syncar/server'\n *\n * const server = createSyncarServer({\n * port: 3000,\n * logger: {\n * debug: (msg, ...args) => console.debug('[DEBUG]', msg, ...args),\n * info: (msg, ...args) => console.info('[INFO]', msg, ...args),\n * warn: (msg, ...args) => console.warn('[WARN]', msg, ...args),\n * error: (msg, ...args) => console.error('[ERROR]', msg, ...args),\n * },\n * })\n * ```\n *\n * @example\n * ### With middleware\n * ```ts\n * import { createSyncarServer, createLoggingMiddleware } from '@syncar/server'\n *\n * const server = createSyncarServer({\n * port: 3000,\n * middleware: [\n * createLoggingMiddleware(),\n * ],\n * })\n *\n * await server.start()\n * ```\n *\n * @see {@link SyncarServer} for server class API\n * @see {@link DEFAULT_SERVER_CONFIG} for default configuration values\n */\nexport function createSyncarServer(\n config: Partial<IServerOptions> = {},\n): SyncarServer {\n // Ensure registry exists\n const registry = config.registry ?? new ClientRegistry()\n const logger = config.logger ?? createDefaultLogger()\n\n // Merge defaults\n const serverOptions: IServerOptions = {\n ...DEFAULT_SERVER_CONFIG,\n middleware: [],\n ...config,\n registry,\n logger,\n } as IServerOptions\n\n if (!serverOptions.transport) {\n if (!serverOptions.server) {\n import('node:http').then((http) => {\n const httpServer = http.createServer()\n httpServer.listen(serverOptions.port, serverOptions.host)\n serverOptions.server = httpServer\n })\n }\n\n serverOptions.transport = new WebSocketServerTransport({\n server: serverOptions.server,\n path: serverOptions.path,\n maxPayload:\n (config as { maxPayload?: number }).maxPayload ??\n DEFAULT_MAX_PAYLOAD,\n enablePing: serverOptions.enablePing,\n pingInterval: serverOptions.pingInterval,\n pingTimeout: serverOptions.pingTimeout,\n connections: registry.connections,\n generateId: serverOptions.generateId,\n logger: serverOptions.logger,\n })\n }\n\n return new SyncarServer(serverOptions)\n}\n","/**\n * Middleware Factories\n * Factory functions for creating common middleware implementations.\n *\n * @module middleware/factories\n */\n\nimport type {\n IMiddleware,\n Context,\n IMiddlewareAction,\n IClientConnection,\n ChannelName,\n} from '../types'\n\n// ============================================================\n// AUTH MIDDLEWARE FACTORY\n// ============================================================\n\n/**\n * Auth middleware options\n *\n * @example\n * ```ts\n * const options: AuthMiddlewareOptions = {\n * verifyToken: async (token) => {\n * const user = await verifyJwt(token)\n * return { id: user.id, email: user.email }\n * },\n * getToken: (context) => {\n * // Extract token from message or connection\n * return context.message?.data?.token\n * },\n * attachProperty: 'user'\n * }\n * ```\n */\nexport interface AuthMiddlewareOptions {\n /**\n * Verify and decode a token\n * Returns the user data to attach to the client\n *\n * @param token - The token to verify\n * @returns User data to attach\n * @throws Error if token is invalid\n */\n verifyToken: (token: string) => Promise<unknown> | unknown\n\n /**\n * Extract token from the middleware context\n *\n * @param c - The middleware context\n * @returns The token string or undefined if not found\n */\n getToken?: (c: Context) => string | undefined\n\n /**\n * Property name to attach verified user data\n * @default 'user'\n */\n attachProperty?: string\n\n /**\n * Actions to require authentication\n * @default All actions require auth\n */\n actions?: IMiddlewareAction[]\n}\n\n/**\n * Create an authentication middleware\n *\n * This middleware verifies tokens and attaches user data to clients.\n * Rejects connections that fail authentication.\n *\n * @param options - Authentication options\n * @returns Middleware function\n *\n * @example\n * ```ts\n * import { createAuthMiddleware } from '@syncar/server/middleware'\n *\n * const authMiddleware = createAuthMiddleware({\n * verifyToken: async (token) => {\n * const user = await jwt.verify(token, SECRET)\n * return { id: user.sub, email: user.email }\n * },\n * getToken: (context) => context.message?.data?.token,\n * attachProperty: 'user'\n * })\n *\n * server.use(authMiddleware)\n * ```\n */\nexport function createAuthMiddleware(\n options: AuthMiddlewareOptions,\n): IMiddleware {\n const {\n verifyToken,\n getToken = (c) => {\n // Default: extract token from message.data.token\n const msg = c.req.message as\n | { data?: { token?: string } }\n | undefined\n return msg?.data?.token\n },\n attachProperty = 'user',\n actions,\n } = options\n\n return async (c, next) => {\n // Check if this action requires auth\n if (actions && !actions.includes(c.req.action)) {\n return next()\n }\n\n // Extract token\n const token = getToken(c)\n if (!token) {\n c.reject('Authentication token required')\n }\n\n // Verify token\n try {\n const userData = await verifyToken(token!)\n\n // Attach user data to client (LEGACY - for compatibility)\n if (c.req.client) {\n ;(c.req.client as unknown as Record<string, unknown>)[\n attachProperty\n ] = userData\n }\n\n // Attach to STATE (Hono-style)\n c.set(attachProperty, userData)\n\n // PASS TO NEXT LAYER\n await next()\n } catch (error) {\n c.reject('Authentication failed: Invalid token')\n }\n }\n}\n\n// ============================================================\n// LOGGING MIDDLEWARE FACTORY\n// ============================================================\n\n/**\n * Logging middleware options\n *\n * @example\n * ```ts\n * const options: LoggingMiddlewareOptions = {\n * logger: console,\n * logLevel: 'info',\n * includeMessageData: false\n * }\n * ```\n */\nexport interface LoggingMiddlewareOptions {\n /**\n * Logger instance to use\n * @default console\n */\n logger?: Pick<Console, 'log' | 'info' | 'warn' | 'error'>\n\n /**\n * Log level\n * @default 'info'\n */\n logLevel?: 'log' | 'info' | 'warn' | 'error'\n\n /**\n * Whether to include message data in logs\n * @default false\n */\n includeMessageData?: boolean\n\n /**\n * Custom format function for log output\n *\n * @param context - The middleware context\n * @returns Formatted log string\n */\n format?: (context: {\n action: string\n clientId?: string\n channel?: string\n message?: unknown\n duration?: number\n }) => string\n\n /**\n * Actions to log\n * @default All actions are logged\n */\n actions?: IMiddlewareAction[]\n}\n\n/**\n * Create a logging middleware\n *\n * Logs all middleware actions with client and action information.\n *\n * @param options - Logging options\n * @returns Middleware function\n *\n * @example\n * ```ts\n * import { createLoggingMiddleware } from '@syncar/server/middleware'\n *\n * const loggingMiddleware = createLoggingMiddleware({\n * logger: console,\n * logLevel: 'info',\n * includeMessageData: false\n * })\n *\n * server.use(loggingMiddleware)\n * ```\n */\nexport function createLoggingMiddleware(\n options: LoggingMiddlewareOptions = {},\n): IMiddleware {\n const {\n logger = console,\n logLevel = 'info',\n includeMessageData = false,\n format,\n actions,\n } = options\n\n return async (c, next) => {\n // Check if this action should be logged\n if (actions && !actions.includes(c.req.action)) {\n return next()\n }\n\n const start = Date.now()\n\n await next() // Wait for downstream layers\n\n const duration = Date.now() - start\n\n const logData = {\n action: c.req.action,\n clientId: c.req.client?.id,\n channel: c.req.channel,\n message: includeMessageData ? c.req.message : undefined,\n duration,\n }\n\n const logMessage = format\n ? format(logData)\n : `[${logData.action}] Client: ${logData.clientId ?? 'unknown'}${logData.channel ? ` Channel: ${logData.channel}` : ''} (${duration}ms)`\n\n logger[logLevel](logMessage)\n }\n}\n\n// ============================================================\n// RATE LIMIT MIDDLEWARE FACTORY\n// ============================================================\n\n/**\n * Rate limit middleware options\n *\n * @example\n * ```ts\n * const options: RateLimitMiddlewareOptions = {\n * maxRequests: 100,\n * windowMs: 60000,\n * getMessageId: (context) => context.client?.id ?? ''\n * }\n * ```\n */\nexport interface RateLimitMiddlewareOptions {\n /**\n * Maximum number of requests allowed per window\n * @default 100\n */\n maxRequests?: number\n\n /**\n * Time window in milliseconds\n * @default 60000 (1 minute)\n */\n windowMs?: number\n\n /**\n * Extract a unique identifier for rate limiting\n * Defaults to client ID\n *\n * @param c - The middleware context\n * @returns Unique identifier for rate limiting\n */\n getMessageId?: (c: Context) => string\n\n /**\n * Actions to rate limit\n * @default 'message' only\n */\n actions?: IMiddlewareAction[]\n}\n\n/**\n * Rate limit state for each client\n */\ninterface RateLimitState {\n count: number\n resetTime: number\n}\n\n/**\n * Rate limit storage\n * Maps client ID to rate limit state\n */\nconst rateLimitStore = new Map<string, RateLimitState>()\n\n/**\n * Create a rate limiting middleware\n *\n * Limits the rate of requests per client within a time window.\n *\n * @param options - Rate limit options\n * @returns Middleware function\n *\n * @example\n * ```ts\n * import { createRateLimitMiddleware } from '@syncar/server/middleware'\n *\n * const rateLimitMiddleware = createRateLimitMiddleware({\n * maxRequests: 100,\n * windowMs: 60000\n * })\n *\n * server.use(rateLimitMiddleware)\n * ```\n */\nexport function createRateLimitMiddleware(\n options: RateLimitMiddlewareOptions = {},\n): IMiddleware {\n const {\n maxRequests = 100,\n windowMs = 60000,\n getMessageId = (c) => c.req.client?.id ?? '',\n actions = ['message'],\n } = options\n\n // Clean up expired entries periodically (every 10 windows)\n const cleanupInterval = setInterval(() => {\n const now = Date.now()\n for (const [id, state] of rateLimitStore) {\n if (state.resetTime < now) {\n rateLimitStore.delete(id)\n }\n }\n }, windowMs * 10)\n\n // Return middleware with cleanup\n const middleware: IMiddleware = async (c, next) => {\n // Check if this action should be rate limited\n if (!actions.includes(c.req.action)) {\n return next()\n }\n\n const id = getMessageId(c)\n if (!id) {\n return next() // No ID to rate limit\n }\n\n const now = Date.now()\n const state = rateLimitStore.get(id)\n\n // Check if window has expired\n if (state && state.resetTime < now) {\n rateLimitStore.delete(id)\n }\n\n // Get or create state\n let currentState = rateLimitStore.get(id)\n if (!currentState) {\n currentState = {\n count: 0,\n resetTime: now + windowMs,\n }\n rateLimitStore.set(id, currentState)\n }\n\n // Check limit\n if (currentState.count >= maxRequests) {\n c.reject(\n `Rate limit exceeded. Max ${maxRequests} requests per ${windowMs}ms`,\n )\n }\n\n // Increment counter\n currentState.count++\n\n // CONTINUE\n await next()\n }\n\n // Attach cleanup method\n ;(middleware as { cleanup?: () => void }).cleanup = () => {\n clearInterval(cleanupInterval)\n rateLimitStore.clear()\n }\n\n return middleware\n}\n\n/**\n * Clear the rate limit store\n * Useful for testing or manual reset\n *\n * @example\n * ```ts\n * import { clearRateLimitStore } from '@syncar/server/middleware'\n *\n * clearRateLimitStore()\n * ```\n */\nexport function clearRateLimitStore(): void {\n rateLimitStore.clear()\n}\n\n/**\n * Get rate limit state for a specific client\n *\n * @param id - Client or message ID\n * @returns Rate limit state or undefined\n *\n * @example\n * ```ts\n * import { getRateLimitState } from '@syncar/server/middleware'\n *\n * const state = getRateLimitState('client-123')\n * console.log(`Requests: ${state?.count ?? 0}/${maxRequests}`)\n * ```\n */\nexport function getRateLimitState(id: string): RateLimitState | undefined {\n return rateLimitStore.get(id)\n}\n\n// ============================================================\n// CHANNEL WHITELIST MIDDLEWARE FACTORY\n// ============================================================\n\n/**\n * Channel whitelist middleware options\n *\n * @example\n * ```ts\n * const options: ChannelWhitelistMiddlewareOptions = {\n * allowedChannels: ['chat', 'notifications'],\n * isDynamic: false\n * }\n * ```\n */\nexport interface ChannelWhitelistMiddlewareOptions {\n /**\n * List of allowed channels\n * If isDynamic is true, this is used as a fallback\n */\n allowedChannels?: ChannelName[]\n\n /**\n * Dynamic check function for channel access\n * If provided, this takes precedence over allowedChannels\n *\n * @param channel - The channel name to check\n * @param client - The client attempting to access the channel\n * @returns true if channel is allowed\n *\n * @example\n * ```ts\n * const isDynamic: (channel, client) => {\n * // Check if user has permission for this channel\n * return user.permissions.includes(channel)\n * }\n * ```\n */\n isDynamic?: (channel: ChannelName, client?: IClientConnection) => boolean\n\n /**\n * Whether to also check unsubscribe actions\n * @default false (only restrict subscribe)\n */\n restrictUnsubscribe?: boolean\n}\n\n/**\n * Create a channel whitelist middleware\n *\n * Restricts which channels clients can subscribe to.\n *\n * @param options - Channel whitelist options\n * @returns Middleware function\n *\n * @example\n * ```ts\n * import { createChannelWhitelistMiddleware } from '@syncar/server/middleware'\n *\n * const whitelistMiddleware = createChannelWhitelistMiddleware({\n * allowedChannels: ['chat', 'notifications']\n * })\n *\n * server.use(whitelistMiddleware)\n * ```\n */\nexport function createChannelWhitelistMiddleware(\n options: ChannelWhitelistMiddlewareOptions = {},\n): IMiddleware {\n const {\n allowedChannels = [],\n isDynamic,\n restrictUnsubscribe = false,\n } = options\n\n return async (c, next) => {\n // Only check subscribe/unsubscribe actions\n if (c.req.action !== 'subscribe' && c.req.action !== 'unsubscribe') {\n return next()\n }\n\n // Skip unsubscribe if not restricted\n if (c.req.action === 'unsubscribe' && !restrictUnsubscribe) {\n return next()\n }\n\n if (!c.req.channel) {\n return next() // No channel to check\n }\n\n // Check dynamic function first\n if (isDynamic) {\n if (!isDynamic(c.req.channel, c.req.client)) {\n c.reject(`Channel '${c.req.channel}' is not allowed`)\n }\n return next()\n }\n\n // Check static whitelist\n if (!allowedChannels.includes(c.req.channel)) {\n c.reject(`Channel '${c.req.channel}' is not allowed`)\n }\n\n await next()\n }\n}\n"],"mappings":"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","names":["generateMessageId","generateClientId","CHANNEL_NAME_MIN_LENGTH","CHANNEL_NAME_MAX_LENGTH","RESERVED_PREFIX","isValidChannelName","name","isReservedChannelName","assertValidChannelName","isDataMessage","message","createDataMessage","channel","data","id","createSignalMessage","signal","LOG_LEVEL_NAMES","createLogTimestamp","createDefaultLogger","prefix","enabled","logEnabled","formatMessage","level","timestamp","args","BROADCAST_CHANNEL","CLOSE_CODES","ERROR_CODES","DEFAULT_WS_PATH","DEFAULT_MAX_PAYLOAD","DEFAULT_PING_INTERVAL","DEFAULT_PING_TIMEOUT","DEFAULT_PORT","DEFAULT_HOST","DEFAULT_PATH","DEFAULT_ENABLE_PING","DEFAULT_SERVER_CONFIG","BaseChannel","name","registry","chunkSize","data","options","clients","_data","_client","_message","clientIds","message","createDataMessage","clientId","client","error","index","nextChunk","chunk","BroadcastChannel","BROADCAST_CHANNEL","_options","MulticastChannel","config","assertValidChannelName","middleware","handler","subscriber","SyncarError","_SyncarError","message","code","context","ConfigError","TransportError","ChannelError","ClientError","MessageError","ValidationError","StateError","MiddlewareRejectionError","_MiddlewareRejectionError","reason","action","MiddlewareExecutionError","_MiddlewareExecutionError","middleware","cause","SignalHandler","dependencies","client","message","ctx","kernel","MessageError","channelInstance","BROADCAST_CHANNEL","pipeline","channel","isReservedChannelName","ChannelError","ackMessage","createSignalMessage","pongMessage","_message","MessageHandler","dependencies","client","message","isDataMessage","MessageError","channel","ChannelError","pipeline","ctx","kernel","ConnectionHandler","dependencies","CLOSE_CODES","connection","clientId","_reason","compose","middleware","context","next","index","dispatch","i","res","handler","createContext","options","action","client","message","channel","initialState","state","key","value","reason","MiddlewareRejectionError","ContextManager","middleware","index","channelInstance","pipeline","channelMiddlewares","c","finalHandler","context","middlewares","wrappedMiddlewares","mw","i","ctx","next","error","MiddlewareExecutionError","middlewareName","compose","ClientRegistry","logger","connection","clientId","clientChannels","channelName","channel","name","subscribers","assertValidChannelName","subscriberIds","id","client","total","EventEmitter","WsServer","WebSocketServerTransport","EventEmitter","config","DEFAULT_WS_PATH","DEFAULT_MAX_PAYLOAD","DEFAULT_PING_INTERVAL","DEFAULT_PING_TIMEOUT","ServerConstructor","WsServer","authenticator","socket","request","error","clientId","generateClientId","connectedAt","connection","data","_code","_reason","message","now","connections","lastPing","SyncarServer","config","ContextManager","mw","StateError","ConnectionHandler","MessageHandler","SignalHandler","BroadcastChannel","name","assertValidChannelName","existing","channel","MulticastChannel","middleware","authenticator","transport","connection","error","clientId","client","message","createSyncarServer","registry","ClientRegistry","logger","createDefaultLogger","serverOptions","DEFAULT_SERVER_CONFIG","http","httpServer","WebSocketServerTransport","DEFAULT_MAX_PAYLOAD","createAuthMiddleware","options","verifyToken","getToken","c","attachProperty","actions","next","token","userData","createLoggingMiddleware","logger","logLevel","includeMessageData","format","start","duration","logData","logMessage","rateLimitStore","createRateLimitMiddleware","maxRequests","windowMs","getMessageId","cleanupInterval","now","id","state","middleware","currentState","createChannelWhitelistMiddleware","options","allowedChannels","isDynamic","restrictUnsubscribe","c","next"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/compose.ts","../src/context.ts","../src/utils.ts","../src/registry.ts","../src/channel.ts","../src/websocket.ts","../src/config.ts","../src/handlers/signal.ts","../src/handlers/messages.ts","../src/handlers/connections.ts","../src/server.ts"],"sourcesContent":["/**\n * Errors Module\n * @description Custom error classes for the Syncar server.\n */\n\nimport type { IMiddlewareRejectionError, IMiddlewareAction } from './types'\n\n// ============================================================\n// BASE SYNNEL ERROR\n// ============================================================\n\n/**\n * Base Syncar error class\n * @example\n * ```ts\n * throw new SyncarError('Something went wrong', 'INTERNAL_ERROR')\n * ```\n */\nexport class SyncarError extends Error {\n /**\n * Error code for programmatic error handling\n *\n * @remarks\n * Machine-readable error code that can be used for conditional\n * error handling and error response generation.\n */\n public readonly code: string\n\n /**\n * Additional error context (optional)\n *\n * @remarks\n * Arbitrary data attached to the error for debugging or logging.\n * Common uses include user IDs, request IDs, or validation details.\n */\n public readonly context?: Record<string, unknown>\n\n /**\n * Creates a new SyncarError\n *\n * @param message - Human-readable error message\n * @param code - Error code for programmatic handling (default: 'SYNNEL_ERROR')\n * @param context - Optional additional error context\n */\n constructor(\n message: string,\n code: string = 'SYNNEL_ERROR',\n context?: Record<string, unknown>,\n ) {\n super(message)\n this.name = 'SyncarError'\n this.code = code\n this.context = context\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, SyncarError)\n }\n }\n\n /**\n * Convert error to JSON for logging/serialization\n *\n * @returns JSON representation of the error\n *\n * @example\n * ```ts\n * const error = new SyncarError('Failed', 'FAIL', { id: 123 })\n * console.log(JSON.stringify(error.toJSON(), null, 2))\n * // {\n * // \"name\": \"SyncarError\",\n * // \"message\": \"Failed\",\n * // \"code\": \"FAIL\",\n * // \"context\": { \"id\": 123 },\n * // \"stack\": \"...\"\n * // }\n * ```\n */\n toJSON(): {\n name: string\n message: string\n code: string\n context?: Record<string, unknown>\n stack?: string\n } {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n context: this.context,\n stack: this.stack,\n }\n }\n\n /**\n * Get a summary of the error for logging\n *\n * @returns Formatted error summary string\n *\n * @example\n * ```ts\n * const error = new SyncarError('Failed', 'FAIL')\n * console.log(error.toString())\n * // \"[SyncarError:FAIL] Failed\"\n * ```\n */\n override toString(): string {\n return `[${this.name}:${this.code}] ${this.message}`\n }\n}\n\n// ============================================================\n// COMMON ERROR CLASSES\n// ============================================================\n\n/**\n * Configuration error\n *\n * @remarks\n * Thrown when server configuration is invalid or missing required values.\n *\n * @example\n * ```ts\n * if (!config.port) {\n * throw new ConfigError('Port is required', { config })\n * }\n * ```\n */\nexport class ConfigError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'CONFIG_ERROR', context)\n this.name = 'ConfigError'\n }\n}\n\n/**\n * Transport error\n *\n * @remarks\n * Thrown when the transport layer fails (WebSocket connection issues, etc.).\n *\n * @example\n * ```ts\n * if (!wsServer) {\n * throw new TransportError('WebSocket server not initialized')\n * }\n * ```\n */\nexport class TransportError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'TRANSPORT_ERROR', context)\n this.name = 'TransportError'\n }\n}\n\n/**\n * Channel error\n *\n * @remarks\n * Thrown when channel operations fail (invalid channel name, etc.).\n *\n * @example\n * ```ts\n * if (channelName.startsWith('__')) {\n * throw new ChannelError('Reserved channel name', { channelName })\n * }\n * ```\n */\nexport class ChannelError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'CHANNEL_ERROR', context)\n this.name = 'ChannelError'\n }\n}\n\n/**\n * Client error\n *\n * @remarks\n * Thrown when client operations fail (client not found, etc.).\n *\n * @example\n * ```ts\n * if (!registry.has(clientId)) {\n * throw new ClientError('Client not found', { clientId })\n * }\n * ```\n */\nexport class ClientError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'CLIENT_ERROR', context)\n this.name = 'ClientError'\n }\n}\n\n/**\n * Message error\n *\n * @remarks\n * Thrown when message processing fails (invalid format, etc.).\n *\n * @example\n * ```ts\n * if (!message.type) {\n * throw new MessageError('Invalid message format', { message })\n * }\n * ```\n */\nexport class MessageError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'MESSAGE_ERROR', context)\n this.name = 'MessageError'\n }\n}\n\n/**\n * Validation error\n *\n * @remarks\n * Thrown when input validation fails.\n *\n * @example\n * ```ts\n * if (!isValidChannelName(name)) {\n * throw new ValidationError('Invalid channel name', { name })\n * }\n * ```\n */\nexport class ValidationError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'VALIDATION_ERROR', context)\n this.name = 'ValidationError'\n }\n}\n\n/**\n * State error\n *\n * @remarks\n * Thrown when an operation is invalid for the current state.\n *\n * @example\n * ```ts\n * if (server.started) {\n * throw new StateError('Server is already started')\n * }\n *\n * if (!server.started) {\n * throw new StateError('Server must be started first')\n * }\n * ```\n */\nexport class StateError extends SyncarError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, 'STATE_ERROR', context)\n this.name = 'StateError'\n }\n}\n\n// ============================================================\n// MIDDLEWARE REJECTION ERROR\n// ============================================================\n\n/**\n * Middleware rejection error\n * @example\n * ```ts\n * if (!context.req.client) context.reject('Client is required')\n * ```\n */\nexport class MiddlewareRejectionError\n extends Error\n implements IMiddlewareRejectionError\n{\n /**\n * The reason for rejection\n *\n * @remarks\n * Human-readable explanation of why the action was rejected.\n */\n public readonly reason: string\n\n /**\n * The action that was rejected\n *\n * @remarks\n * One of: 'connect', 'disconnect', 'message', 'subscribe', 'unsubscribe'\n */\n public readonly action: string\n\n /**\n * Error name (fixed value for interface compliance)\n */\n public override readonly name = 'MiddlewareRejectionError'\n\n /**\n * Optional error code for programmatic handling\n *\n * @remarks\n * Can be used for mapping to client-facing error codes.\n */\n public readonly code?: string\n\n /**\n * Additional context about the rejection\n *\n * @remarks\n * Arbitrary data for debugging or logging.\n */\n public readonly context?: Record<string, unknown>\n\n /**\n * Creates a new MiddlewareRejectionError\n *\n * @param reason - Human-readable reason for the rejection\n * @param action - The action that was rejected\n * @param code - Optional error code for programmatic handling\n * @param context - Additional context about the rejection\n */\n constructor(\n reason: string,\n action: IMiddlewareAction | string,\n code?: string,\n context?: Record<string, unknown>,\n ) {\n super(`Action '${action}' rejected: ${reason}`)\n this.reason = reason\n this.action = typeof action === 'string' ? action : action\n\n // Set error name for instanceof checks\n this.name = 'MiddlewareRejectionError'\n\n // Optional code and context\n this.code = code\n this.context = context\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, MiddlewareRejectionError)\n }\n }\n\n /**\n * Convert error to JSON for logging/serialization\n *\n * @returns JSON representation of the rejection error\n *\n * @example\n * ```ts\n * const error = new MiddlewareRejectionError('Not allowed', 'subscribe', 'FORBIDDEN')\n * console.log(JSON.stringify(error.toJSON(), null, 2))\n * // {\n * // \"name\": \"MiddlewareRejectionError\",\n * // \"reason\": \"Not allowed\",\n * // \"action\": \"subscribe\",\n * // \"code\": \"FORBIDDEN\",\n * // \"message\": \"Action 'subscribe' rejected: Not allowed\",\n * // \"stack\": \"...\"\n * // }\n * ```\n */\n toJSON(): {\n name: string\n reason: string\n action: string\n code?: string\n context?: Record<string, unknown>\n message: string\n stack?: string\n } {\n return {\n name: this.name,\n reason: this.reason,\n action: this.action,\n code: this.code,\n context: this.context,\n message: this.message,\n stack: this.stack,\n }\n }\n\n /**\n * Get a summary of the rejection for logging\n *\n * @returns Formatted error summary string\n *\n * @example\n * ```ts\n * const error = new MiddlewareRejectionError('Not allowed', 'subscribe')\n * console.log(error.toString())\n * // \"[MiddlewareRejectionError:subscribe] Not allowed\"\n * ```\n */\n override toString(): string {\n return `[${this.name}:${this.action}] ${this.reason}`\n }\n}\n\n// ============================================================\n// MIDDLEWARE EXECUTION ERROR\n// ============================================================\n\n/**\n * Middleware execution error\n *\n * @remarks\n * Thrown when a middleware function throws an unexpected error\n * (not using `context.reject()`). This indicates a bug or failure\n * in the middleware rather than an intentional rejection.\n *\n * @property action - The action being processed when the error occurred\n * @property middleware - The name/index of the middleware that failed\n * @property cause - The original error thrown by the middleware\n *\n * @example\n * ### Error scenario\n * ```ts\n * const buggyMiddleware: Middleware = async (context, next) => {\n * // This throws an unexpected error\n * JSON.parse(context.req.message as string)\n * await next()\n * }\n * // Results in MiddlewareExecutionError\n * ```\n *\n * @example\n * ### Catching execution errors\n * ```ts\n * try {\n * await manager.execute(context)\n * } catch (error) {\n * if (error instanceof MiddlewareExecutionError) {\n * console.error(`${error.middleware} failed during ${error.action}:`)\n * console.error(error.cause)\n * }\n * }\n * ```\n */\nexport class MiddlewareExecutionError extends Error {\n /**\n * The action being processed when the error occurred\n */\n public readonly action: string\n\n /**\n * The name/index of the middleware that failed\n */\n public readonly middleware: string\n\n /**\n * The original error thrown by the middleware\n */\n public override readonly cause: Error\n\n /**\n * Creates a new MiddlewareExecutionError\n *\n * @param action - The action being processed\n * @param middleware - The name/index of the middleware\n * @param cause - The original error\n */\n constructor(action: string, middleware: string, cause: Error) {\n super(\n `Middleware execution error in ${middleware} during ${action}: ${cause.message}`,\n )\n this.name = 'MiddlewareExecutionError'\n this.action = action\n this.middleware = middleware\n this.cause = cause\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, MiddlewareExecutionError)\n }\n }\n\n /**\n * Get the original error cause\n *\n * @returns The original error thrown by the middleware\n *\n * @example\n * ```ts\n * if (error instanceof MiddlewareExecutionError) {\n * const originalError = error.getCause()\n * console.error('Original error:', originalError.message)\n * }\n * ```\n */\n getCause(): Error {\n return this.cause\n }\n\n /**\n * Get a summary of the error for logging\n *\n * @returns Formatted error summary string\n *\n * @example\n * ```ts\n * const error = new MiddlewareExecutionError('message', 'auth', originalError)\n * console.log(error.toString())\n * // \"[MiddlewareExecutionError] auth failed during message: Invalid token\"\n * ```\n */\n override toString(): string {\n return `[${this.name}] ${this.middleware} failed during ${this.action}: ${this.cause.message}`\n }\n}\n","import type { Context, Middleware, Next } from './types'\n\n/**\n * Compose middleware functions into a single function.\n * Follows the onion-style execution pattern.\n */\nexport const compose = <S = Record<string, unknown>>(\n middleware: Middleware<S>[],\n) => {\n return (context: Context<S>, next?: Next) => {\n let index = -1\n\n const dispatch = async (i: number): Promise<Context<S>> => {\n if (i <= index) throw new Error('next() called multiple times')\n index = i\n\n let res: unknown\n const handler = middleware[i]\n\n if (handler) {\n res = await handler(context, async () => {\n await dispatch(i + 1)\n })\n } else if (i === middleware.length && next) {\n res = await next()\n } else {\n return context\n }\n\n if (res !== undefined && !context.finalized) {\n context.res = res\n context.finalized = true\n }\n\n return context\n }\n\n return dispatch(0)\n }\n}\n","import { MiddlewareRejectionError, MiddlewareExecutionError } from './errors'\nimport { compose } from './compose'\nimport type {\n IContext,\n IMiddlewareAction,\n IClientConnection,\n Message,\n ChannelName,\n IMiddleware,\n} from './types'\n\n/**\n * Context Data Options\n *\n * @example\n * ```ts\n * const options: ContextOptions = { action: 'message', channel: 'chat' }\n * ```\n */\nexport interface ContextOptions<S = Record<string, unknown>> {\n /** The middleware action being performed */\n action: IMiddlewareAction\n /** Optional client connection */\n client?: IClientConnection\n /** Optional message being processed */\n message?: Message\n /** Optional channel name */\n channel?: ChannelName\n /** Optional initial state values */\n initialState?: S\n}\n\n/**\n * Create a new Onion-style middleware context\n *\n * @example\n * ```ts\n * const context = createContext({ action: 'message', channel: 'chat' })\n * ```\n */\nexport function createContext<S = Record<string, unknown>>(\n options: ContextOptions<S>,\n): IContext<S> {\n const { action, client, message, channel, initialState = {} as S } = options\n const state = initialState as S\n\n return {\n req: {\n action,\n client,\n message,\n channel,\n },\n\n finalized: false,\n\n get: <K extends keyof S>(key: K): S[K] => {\n return state[key]\n },\n\n set: <K extends keyof S>(key: K, value: S[K]): void => {\n state[key] = value\n },\n\n reject: (reason: string): never => {\n throw new MiddlewareRejectionError(reason, action)\n },\n }\n}\n\n/**\n * Context Manager - manages and executes middleware functions\n *\n * @example\n * ```ts\n * const manager = new ContextManager()\n * manager.use(async (ctx, next) => {\n * console.log(ctx.req.action)\n * await next()\n * })\n * ```\n */\nexport class ContextManager {\n /** Registered middleware functions */\n protected readonly middlewares: IMiddleware[] = []\n\n /**\n * Register a global middleware function\n * @param middleware - The middleware function\n */\n use(middleware: IMiddleware): void {\n this.middlewares.push(middleware)\n }\n\n /**\n * Remove a middleware function\n *\n * @remarks\n * Removes a previously registered middleware function from the chain.\n *\n * @param middleware - The middleware function to remove\n * @returns `true` if the middleware was found and removed, `false` otherwise\n *\n * @example\n * ```ts\n * const middleware = async (context, next) => { /* ... *\\/ }\n * manager.use(middleware)\n *\n * // Later, remove it\n * if (manager.remove(middleware)) {\n * console.log('Middleware removed')\n * }\n * ```\n */\n remove(middleware: IMiddleware): boolean {\n const index = this.middlewares.indexOf(middleware)\n if (index !== -1) {\n this.middlewares.splice(index, 1)\n return true\n }\n return false\n }\n\n /**\n * Clear all middleware\n *\n * @remarks\n * Removes all registered middleware functions from the chain.\n *\n * @example\n * ```ts\n * manager.clear()\n * console.log('All middleware cleared')\n * ```\n */\n clear(): void {\n this.middlewares.length = 0\n }\n\n /**\n * Get all registered middleware\n *\n * @remarks\n * Returns a shallow copy of the middleware array to prevent\n * external modification.\n *\n * @returns Array of middleware functions\n *\n * @example\n * ```ts\n * const allMiddleware = manager.getMiddlewares()\n * console.log(`Registered middleware: ${allMiddleware.length}`)\n * ```\n */\n getMiddlewares(): IMiddleware[] {\n return [...this.middlewares]\n }\n\n /**\n * Get the complete middleware pipeline\n *\n * @remarks\n * Returns the combined middleware pipeline including global middleware\n * and any channel-specific middleware from the provided channel instance.\n *\n * @param channelInstance - Optional channel instance with middleware\n * @returns Combined array of middleware functions\n *\n * @example\n * ```ts\n * const chat = server.createMulticast('chat')\n * const pipeline = manager.getPipeline(chat)\n * // Returns global middleware + chat channel middleware\n * ```\n *\n * @internal\n */\n getPipeline(channelInstance?: {\n getMiddlewares?: () => IMiddleware[]\n }): IMiddleware[] {\n let pipeline = this.getMiddlewares()\n const channelMiddlewares = channelInstance?.getMiddlewares?.()\n\n if (channelMiddlewares && channelMiddlewares.length > 0) {\n pipeline = [...pipeline, ...channelMiddlewares]\n }\n\n return pipeline\n }\n\n /**\n * Execute middleware for connection actions\n *\n * @remarks\n * Creates a connection context and executes the middleware pipeline\n * for connect or disconnect actions.\n *\n * @param client - The client connection\n * @param action - The action ('connect' or 'disconnect')\n * @returns The executed context\n *\n * @example\n * ```ts\n * await manager.executeConnection(client, 'connect')\n * await manager.executeConnection(client, 'disconnect')\n * ```\n *\n * @internal\n */\n async executeConnection(\n client: IClientConnection,\n action: 'connect' | 'disconnect',\n ): Promise<IContext> {\n const c = this.createConnectionContext(client, action)\n return await this.execute(c)\n }\n\n /**\n * Execute middleware for message actions\n *\n * @remarks\n * Creates a message context and executes the middleware pipeline\n * for incoming client messages.\n *\n * @param client - The client connection\n * @param message - The message being processed\n * @returns The executed context\n *\n * @example\n * ```ts\n * await manager.executeMessage(client, dataMessage)\n * ```\n *\n * @internal\n */\n async executeMessage(\n client: IClientConnection,\n message: Message,\n ): Promise<IContext> {\n const c = this.createMessageContext(client, message)\n return await this.execute(c)\n }\n\n /**\n * Execute middleware for subscribe actions\n *\n * @remarks\n * Creates a subscribe context and executes the middleware pipeline\n * for channel subscription requests.\n *\n * @param client - The client connection\n * @param channel - The channel name\n * @param finalHandler - Optional final handler to execute after middleware\n * @returns The executed context\n *\n * @example\n * ```ts\n * await manager.executeSubscribe(client, 'chat', async () => {\n * // Final handler - perform the actual subscription\n * channel.subscribe(client.id)\n * })\n * ```\n *\n * @internal\n */\n async executeSubscribe(\n client: IClientConnection,\n channel: ChannelName,\n finalHandler?: () => Promise<void>,\n ): Promise<IContext> {\n const c = this.createSubscribeContext(client, channel)\n return await this.execute(c, this.middlewares, finalHandler)\n }\n\n /**\n * Execute middleware for unsubscribe actions\n *\n * @remarks\n * Creates an unsubscribe context and executes the middleware pipeline\n * for channel unsubscription requests.\n *\n * @param client - The client connection\n * @param channel - The channel name\n * @param finalHandler - Optional final handler to execute after middleware\n * @returns The executed context\n *\n * @example\n * ```ts\n * await manager.executeUnsubscribe(client, 'chat', async () => {\n * // Final handler - perform the actual unsubscription\n * channel.unsubscribe(client.id)\n * })\n * ```\n *\n * @internal\n */\n async executeUnsubscribe(\n client: IClientConnection,\n channel: ChannelName,\n finalHandler?: () => Promise<void>,\n ): Promise<IContext> {\n const c = this.createUnsubscribeContext(client, channel)\n return await this.execute(c, this.middlewares, finalHandler)\n }\n\n /**\n * Execute middleware pipeline\n *\n * @remarks\n * Executes the provided middleware functions in order, wrapping\n * each to capture and report errors appropriately.\n *\n * @param context - The middleware context\n * @param middlewares - The middleware functions to execute (defaults to registered middleware)\n * @param finalHandler - Optional final handler to execute after all middleware\n * @returns The executed context\n *\n * @throws {MiddlewareExecutionError} If a middleware function throws an unexpected error\n *\n * @example\n * ```ts\n * const context = createContext({ action: 'message' })\n * await manager.execute(context)\n * ```\n *\n * @internal\n */\n async execute(\n context: IContext,\n middlewares: IMiddleware[] = this.middlewares,\n finalHandler?: () => Promise<void>,\n ): Promise<IContext> {\n const action = context.req.action || 'unknown'\n\n // Wrap middlewares to capture specific execution errors\n const wrappedMiddlewares = middlewares.map((mw, i) => {\n return async (ctx: IContext, next: () => Promise<void>) => {\n try {\n await mw(ctx, next)\n } catch (error) {\n // Re-throw if already handled or explicit rejection\n if (\n error instanceof MiddlewareRejectionError ||\n error instanceof MiddlewareExecutionError\n ) {\n throw error\n }\n\n const middlewareName = mw.name || `middleware[${i}]`\n throw new MiddlewareExecutionError(\n action,\n middlewareName,\n error instanceof Error\n ? error\n : new Error(String(error)),\n )\n }\n }\n })\n\n return await compose(wrappedMiddlewares)(\n context,\n finalHandler as () => Promise<void>,\n )\n }\n\n /**\n * Create a connection context\n *\n * @remarks\n * Creates a middleware context for connection or disconnect actions.\n *\n * @param client - The client connection\n * @param action - The action ('connect' or 'disconnect')\n * @returns A new connection context\n *\n * @example\n * ```ts\n * const context = manager.createConnectionContext(client, 'connect')\n * ```\n *\n * @internal\n */\n createConnectionContext(\n client: IClientConnection,\n action: 'connect' | 'disconnect',\n ): IContext {\n return createContext({\n client,\n action,\n })\n }\n\n /**\n * Create a message context\n *\n * @remarks\n * Creates a middleware context for message processing.\n *\n * @param client - The client connection\n * @param message - The message being processed\n * @returns A new message context\n *\n * @example\n * ```ts\n * const context = manager.createMessageContext(client, dataMessage)\n * ```\n *\n * @internal\n */\n createMessageContext(\n client: IClientConnection,\n message: Message,\n ): IContext {\n return createContext({\n client,\n message,\n action: 'message',\n })\n }\n\n /**\n * Create a subscribe context\n *\n * @remarks\n * Creates a middleware context for channel subscription.\n *\n * @param client - The client connection\n * @param channel - The channel name\n * @returns A new subscribe context\n *\n * @example\n * ```ts\n * const context = manager.createSubscribeContext(client, 'chat')\n * ```\n *\n * @internal\n */\n createSubscribeContext(\n client: IClientConnection,\n channel: ChannelName,\n ): IContext {\n return createContext({\n client,\n channel,\n action: 'subscribe',\n })\n }\n\n /**\n * Create an unsubscribe context\n *\n * @remarks\n * Creates a middleware context for channel unsubscription.\n *\n * @param client - The client connection\n * @param channel - The channel name\n * @returns A new unsubscribe context\n *\n * @example\n * ```ts\n * const context = manager.createUnsubscribeContext(client, 'chat')\n * ```\n *\n * @internal\n */\n createUnsubscribeContext(\n client: IClientConnection,\n channel: ChannelName,\n ): IContext {\n return createContext({\n client,\n channel,\n action: 'unsubscribe',\n })\n }\n\n /**\n * Get the number of registered middleware\n *\n * @returns The count of middleware functions\n *\n * @example\n * ```ts\n * console.log(`Middleware count: ${manager.getCount()}`)\n * ```\n */\n getCount(): number {\n return this.middlewares.length\n }\n\n /**\n * Check if any middleware is registered\n *\n * @returns `true` if at least one middleware is registered, `false` otherwise\n *\n * @example\n * ```ts\n * if (manager.hasMiddleware()) {\n * console.log('Middleware is configured')\n * }\n * ```\n */\n hasMiddleware(): boolean {\n return this.middlewares.length > 0\n }\n}\n","import type {\n MessageId,\n ClientId,\n ChannelName,\n Message,\n DataMessage,\n SignalMessage,\n SignalType,\n ILogger,\n IClientConnection,\n} from './types'\nimport { MessageType } from './types'\n\n// ============================================================\n// ID GENERATION UTILITIES\n// ============================================================\n\n/**\n * Generate a unique message ID\n * Format: timestamp-randomString\n */\nexport function generateMessageId(): MessageId {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`\n}\n\n/**\n * Generate a unique client ID\n * Format: client-timestamp-randomString\n */\nexport function generateClientId(): ClientId {\n return `client-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n}\n\n// ============================================================\n// VALIDATION UTILITIES\n// ============================================================\n\nconst CHANNEL_NAME_MIN_LENGTH = 1\nconst CHANNEL_NAME_MAX_LENGTH = 128\nconst RESERVED_PREFIX = '__'\n\n/**\n * Validate channel name\n * Channel names must be non-empty strings with reasonable length\n */\nexport function isValidChannelName(name: ChannelName): boolean {\n return (\n typeof name === 'string' &&\n name.length >= CHANNEL_NAME_MIN_LENGTH &&\n name.length <= CHANNEL_NAME_MAX_LENGTH\n )\n}\n\n/**\n * Check if channel name is reserved for system use\n * Reserved channel names start with '__'\n */\nexport function isReservedChannelName(name: ChannelName): boolean {\n return name.startsWith(RESERVED_PREFIX)\n}\n\n/**\n * Assert that channel name is valid\n * @throws Error if channel name is invalid\n */\nexport function assertValidChannelName(name: ChannelName): void {\n if (!isValidChannelName(name)) {\n throw new Error(\n `Invalid channel name: must be between ${CHANNEL_NAME_MIN_LENGTH} and ${CHANNEL_NAME_MAX_LENGTH} characters`,\n )\n }\n}\n\n// ============================================================\n// MESSAGE UTILITIES\n// ============================================================\n\n/**\n * Guard to check if message is a DataMessage\n */\nexport function isDataMessage<T = unknown>(\n message: Message<T>,\n): message is DataMessage<T> {\n return message.type === MessageType.DATA\n}\n\n/**\n * Create a data message\n */\nexport function createDataMessage<T>(\n channel: ChannelName,\n data: T,\n id?: MessageId,\n): DataMessage<T> {\n return {\n id: id || generateMessageId(),\n type: MessageType.DATA,\n channel,\n data,\n timestamp: Date.now(),\n }\n}\n\n/**\n * Create a signal message\n */\nexport function createSignalMessage(\n channel: ChannelName,\n signal: SignalType,\n data?: any,\n id?: MessageId,\n): SignalMessage {\n return {\n id: id || generateMessageId(),\n type: MessageType.SIGNAL,\n channel,\n signal,\n data,\n timestamp: Date.now(),\n }\n}\n\n/**\n * Send message in chunks for large lists of clients\n *\n * @param config.data - The raw data to send\n * @param config.connections - Array of target client connections\n * @param config.chunkSize - Number of messages to send per chunk\n * @param config.channel - The channel name (defaults to 'broadcast')\n */\nexport function publishMessage<T>(config: {\n data: T\n connections: IClientConnection[]\n chunkSize: number\n channel: ChannelName\n}): void {\n const { data, connections, chunkSize, channel } = config\n const dataMessage = createDataMessage<T>(channel, data)\n const message = JSON.stringify(dataMessage)\n\n let index = 0\n\n const nextChunk = () => {\n const chunk = connections.slice(index, index + chunkSize)\n if (chunk.length === 0) return\n\n for (const client of chunk) {\n try {\n client.socket.send(message)\n } catch (error) {\n // Silently fail or use basic console error\n console.error(`Failed to send message to ${client.id}`)\n }\n }\n\n index += chunkSize\n\n if (index < connections.length) {\n setImmediate(nextChunk)\n }\n }\n\n nextChunk()\n}\n\n// ============================================================\n// LOGGING UTILITIES\n// ============================================================\n\n/**\n * Log levels\n */\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug'\n\n/**\n * Logger function signature\n */\nexport type LoggerFn = (\n level: LogLevel,\n message: string,\n ...args: unknown[]\n) => void\n\n/**\n * Default log level names for display\n */\nconst LOG_LEVEL_NAMES: Record<LogLevel, string> = {\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n debug: 'DEBUG',\n}\n\n/**\n * Create a timestamp for log messages\n */\nexport function createLogTimestamp(): string {\n return new Date().toISOString()\n}\n\n/**\n * Default logger implementation\n * Logs to console with timestamp and level\n */\nexport function createDefaultLogger(\n prefix = 'Syncar',\n enabled: { [K in LogLevel]?: boolean } = {},\n): ILogger {\n const logEnabled = {\n debug: false,\n info: true,\n warn: true,\n error: true,\n ...enabled,\n }\n\n const formatMessage = (level: LogLevel, message: string): string => {\n const timestamp = createLogTimestamp()\n return `[${prefix} ${timestamp}] [${LOG_LEVEL_NAMES[level]}] ${message}`\n }\n\n return {\n debug: (message: string, ...args: unknown[]) => {\n if (logEnabled.debug)\n console.log(formatMessage('debug', message), ...args)\n },\n info: (message: string, ...args: unknown[]) => {\n if (logEnabled.info)\n console.log(formatMessage('info', message), ...args)\n },\n warn: (message: string, ...args: unknown[]) => {\n if (logEnabled.warn)\n console.warn(formatMessage('warn', message), ...args)\n },\n error: (message: string, ...args: unknown[]) => {\n if (logEnabled.error)\n console.error(formatMessage('error', message), ...args)\n },\n }\n}\n","import type { IClientConnection, ClientId, ChannelName, ILogger } from './types'\nimport { Channel } from './channel'\nimport { assertValidChannelName } from './utils'\n\n/**\n * Client Registry\n * Manages connected clients, their subscriptions, and channel instances.\n */\nexport class ClientRegistry {\n public readonly connections: Map<ClientId, IClientConnection> = new Map()\n private readonly subscriptions: Map<ClientId, Set<ChannelName>> = new Map()\n private readonly channels: Map<ChannelName, Set<ClientId>> = new Map()\n private readonly channelInstances: Map<ChannelName, Channel<any>> =\n new Map()\n public readonly logger?: ILogger\n\n constructor(logger?: ILogger) {\n this.logger = logger\n }\n\n register(connection: IClientConnection): IClientConnection {\n this.connections.set(connection.id, connection)\n return connection\n }\n\n unregister(clientId: ClientId): boolean {\n const connection = this.get(clientId)\n if (!connection) return false\n\n this.clearSubscriptions(connection)\n return this.connections.delete(clientId)\n }\n\n private clearSubscriptions(connection: IClientConnection): void {\n const clientChannels = this.subscriptions.get(connection.id)\n if (!clientChannels) return\n\n for (const channelName of clientChannels) {\n this.channels.get(channelName)?.delete(connection.id)\n }\n\n this.subscriptions.delete(connection.id)\n }\n\n get(clientId: ClientId): IClientConnection | undefined {\n return this.connections.get(clientId)\n }\n\n getAll(): IClientConnection[] {\n return Array.from(this.connections.values())\n }\n\n getCount(): number {\n return this.connections.size\n }\n\n registerChannel(channel: Channel<any>): void {\n // Create channel in internal map if not exists\n if (!this.channels.has(channel.name)) {\n this.channels.set(channel.name, new Set())\n }\n // Store the channel instance\n this.channelInstances.set(channel.name, channel)\n }\n\n getChannel<T = unknown>(name: ChannelName): Channel<T> | undefined {\n return this.channelInstances.get(name) as Channel<T> | undefined\n }\n\n removeChannel(name: ChannelName): boolean {\n const subscribers = this.channels.get(name)\n if (!subscribers) return false\n\n // Remove channel from all subscribers' subscription sets\n for (const clientId of subscribers) {\n const clientChannels = this.subscriptions.get(clientId)\n if (clientChannels) {\n clientChannels.delete(name)\n }\n }\n\n // Remove the channel instance\n this.channelInstances.delete(name)\n\n // Remove the channel\n return this.channels.delete(name)\n }\n\n subscribe(clientId: ClientId, channel: ChannelName): boolean {\n assertValidChannelName(channel)\n // Verify client exists\n if (!this.connections.has(clientId)) return false\n\n // Create channel if not exists\n if (!this.channels.has(channel)) {\n this.channels.set(channel, new Set())\n }\n\n const subscribers = this.channels.get(channel)!\n\n // Check if already subscribed\n if (subscribers.has(clientId)) return true\n\n // Add to channels map (reverse index)\n subscribers.add(clientId)\n\n // Add to subscriptions map (forward index)\n if (!this.subscriptions.has(clientId)) {\n this.subscriptions.set(clientId, new Set())\n }\n this.subscriptions.get(clientId)!.add(channel)\n\n return true\n }\n\n unsubscribe(clientId: ClientId, channel: ChannelName): boolean {\n const subscribers = this.channels.get(channel)\n if (!subscribers || !subscribers.has(clientId)) return false\n\n // Remove from channels map (reverse index)\n subscribers.delete(clientId)\n\n // Remove from subscriptions map (forward index)\n const clientChannels = this.subscriptions.get(clientId)\n if (clientChannels) {\n clientChannels.delete(channel)\n // Clean up empty subscription sets\n if (clientChannels.size === 0) {\n this.subscriptions.delete(clientId)\n }\n }\n\n return true\n }\n\n getSubscribers(channel: ChannelName): IClientConnection[] {\n const subscriberIds = this.channels.get(channel)\n if (!subscriberIds) return []\n\n const subscribers: IClientConnection[] = []\n for (const id of subscriberIds) {\n const client = this.connections.get(id)\n if (client) {\n subscribers.push(client)\n }\n }\n\n return subscribers\n }\n\n getSubscriberCount(channel: ChannelName): number {\n return this.channels.get(channel)?.size ?? 0\n }\n\n getChannels(): ChannelName[] {\n return Array.from(this.channels.keys())\n }\n\n getTotalSubscriptionCount(): number {\n let total = 0\n for (const subscribers of this.channels.values()) {\n total += subscribers.size\n }\n return total\n }\n\n isSubscribed(clientId: ClientId, channel: ChannelName): boolean {\n return this.channels.get(channel)?.has(clientId) ?? false\n }\n\n getClientChannels(clientId: ClientId): Set<ChannelName> {\n return this.subscriptions.get(clientId) ?? new Set()\n }\n\n getChannelSubscribers(channel: ChannelName): Set<ClientId> {\n return this.channels.get(channel) ?? new Set()\n }\n\n clear(): void {\n this.connections.clear()\n this.subscriptions.clear()\n this.channels.clear()\n this.channelInstances.clear()\n }\n}\n","import {\n type ChannelName,\n type ClientId,\n type DataMessage,\n type IClientConnection,\n type IMiddleware,\n} from './types'\nimport { ClientRegistry } from './registry'\nimport { publishMessage } from './utils'\n\n/**\n * Channel state information\n *\n * @remarks\n * Provides runtime information about a channel including its name,\n * subscriber count, creation time, and last message timestamp.\n *\n * @property name - The channel name\n * @property subscriberCount - Current number of subscribers\n * @property createdAt - Unix timestamp (ms) when the channel was created\n * @property lastMessageAt - Unix timestamp (ms) of the last published message\n */\nexport interface IChannelState {\n /** The channel name */\n name: string\n /** Current number of subscribers */\n subscriberCount: number\n /** Unix timestamp (ms) when the channel was created */\n createdAt: number\n /** Unix timestamp (ms) of the last published message */\n lastMessageAt?: number\n}\n\nexport type ChannelFlow = 'bidirectional' | 'send-only' | 'receive-only'\n\n/**\n * Channel creation options\n *\n * @example\n * ```ts\n * const options: ChannelOptions = { flow: 'receive-only' }\n * ```\n */\nexport interface ChannelOptions {\n /** Message direction: 'bidirectional', 'send-only', or 'receive-only' */\n flow?: ChannelFlow\n}\n\n/**\n * Base message handler signature\n *\n * @remarks\n * Type-safe handler function for processing incoming messages on a channel.\n * Handlers receive the message data, sending client, and the original message object.\n *\n * @template T - Type of data expected in messages\n */\nexport type IMessageHandler<T> = (\n /** The message data payload */\n data: T,\n /** The client connection that sent the message */\n client: IClientConnection,\n /** The complete message object with metadata */\n message: DataMessage<T>,\n) => void | Promise<void>\n\n/**\n * Unified Channel implementation\n *\n * @example\n * ```ts\n * const chat = server.createChannel('chat')\n * chat.onMessage((data, client) => {\n * chat.publish(data, [client.id])\n * })\n * ```\n */\nexport class Channel<T = unknown> {\n /** The channel name */\n public readonly name: ChannelName\n\n /** The channel flow: 'bidirectional', 'send-only', or 'receive-only' */\n public readonly flow: ChannelFlow\n\n /** @internal */\n protected readonly registry: ClientRegistry\n /** @internal */\n protected readonly chunkSize: number\n\n private readonly middlewares: IMiddleware[] = []\n private readonly messageHandlers: Set<IMessageHandler<T>> = new Set()\n private _lastMessageAt?: number\n private _createdAt: number\n\n /** @internal */\n constructor(config: {\n name: ChannelName\n registry: ClientRegistry\n options?: ChannelOptions\n chunkSize?: number\n }) {\n const { name, registry, options, chunkSize = 500 } = config\n\n this.name = name\n this.registry = registry\n this.chunkSize = chunkSize\n this.flow = options?.flow ?? 'bidirectional'\n this._createdAt = Date.now()\n }\n\n /**\n * Subscribe a client to this channel\n * @param subscriber - Client ID to subscribe\n * @returns `true` if subscribed successfully\n */\n subscribe(subscriber: ClientId): boolean {\n return this.registry.subscribe(subscriber, this.name)\n }\n\n /**\n * Unsubscribe a client from this channel\n * @param subscriber - Client ID to unsubscribe\n * @returns `true` if unsubscribed successfully\n */\n unsubscribe(subscriber: ClientId): boolean {\n return this.registry.unsubscribe(subscriber, this.name)\n }\n\n /**\n * Check if a client is subscribed to this channel\n * @param subscriber - The client ID to check\n */\n hasSubscriber(subscriber: ClientId): boolean {\n return this.registry.getChannelSubscribers(this.name).has(subscriber)\n }\n\n /**\n * Get all subscriber IDs for this channel\n */\n getSubscribers(): Set<ClientId> {\n return new Set(this.registry.getChannelSubscribers(this.name))\n }\n\n /**\n * Get the current count of subscribers\n */\n get subscriberCount(): number {\n return this.registry.getChannelSubscribers(this.name).size\n }\n\n /**\n * Check if the channel has no subscribers\n */\n isEmpty(): boolean {\n return this.subscriberCount === 0\n }\n\n /**\n * Publish data to all subscribers\n * @param data - The data to publish\n * @param exclude - Optional array of client IDs to exclude\n */\n publish(data: T, exclude: ClientId[] = []): void {\n this._lastMessageAt = Date.now()\n let connections = this.registry.getSubscribers(this.name)\n\n if (exclude.length > 0) {\n const excludeSet = new Set(exclude)\n connections = connections.filter((conn) => !excludeSet.has(conn.id))\n }\n\n if (connections.length === 0) return\n\n publishMessage<T>({\n channel: this.name,\n data,\n connections,\n chunkSize: this.chunkSize,\n })\n }\n\n /**\n * Register a message handler for incoming client data\n *\n * @returns Function to remove the handler\n */\n onMessage(handler: IMessageHandler<T>): () => void {\n if (this.flow === 'send-only') {\n throw new Error(\n `Cannot register message handler on channel '${this.name}': ` +\n `onMessage is not available in send-only mode.`,\n )\n }\n this.messageHandlers.add(handler)\n return () => this.messageHandlers.delete(handler)\n }\n\n /**\n * Dispatch an incoming client message\n *\n * @internal\n */\n async dispatch(\n data: T,\n client: IClientConnection,\n message: DataMessage<T>,\n ): Promise<void> {\n if (this.flow === 'send-only') {\n return\n }\n\n if (this.messageHandlers.size > 0) {\n for (const handler of this.messageHandlers) {\n try {\n await handler(data, client, message)\n } catch (error) {\n this.registry.logger?.error(\n `[${this.name}] Error in message handler:`,\n error as Error,\n )\n }\n }\n } else if (this.flow === 'bidirectional') {\n // Auto-relay: forward to other subscribers\n this.publish(data, [client.id])\n }\n }\n\n /**\n * Register channel-specific middleware\n */\n use(middleware: IMiddleware): void {\n this.middlewares.push(middleware)\n }\n\n /**\n * Get registered middleware for this channel\n */\n getMiddlewares(): IMiddleware[] {\n return [...this.middlewares]\n }\n\n /**\n * Get runtime channel statistics\n */\n getState(): IChannelState {\n return {\n name: this.name,\n subscriberCount: this.subscriberCount,\n createdAt: this._createdAt,\n lastMessageAt: this._lastMessageAt,\n }\n }\n}\n","import { EventEmitter } from 'node:events'\nimport { IncomingMessage } from 'node:http'\nimport {\n WebSocketServer as WsServer,\n type ServerOptions as WsServerOptions,\n type WebSocket,\n} from 'ws'\nimport {\n MessageType,\n SignalType,\n type ClientId,\n type IClientConnection,\n} from './types'\nimport {\n DEFAULT_MAX_PAYLOAD,\n DEFAULT_PING_INTERVAL,\n DEFAULT_PING_TIMEOUT,\n DEFAULT_PATH,\n} from './config'\nimport { generateClientId } from './utils'\n\n// Instance types\ntype ServerInstance = WsServer\n\n/**\n * WebSocket Server Transport Configuration\n */\nexport interface WebSocketServerTransportConfig extends WsServerOptions {\n /**\n * Enable client ping/pong\n *\n * @remarks\n * When enabled, the server sends periodic ping frames to detect\n * dead connections and maintain keep-alive.\n *\n * @default true\n */\n enablePing?: boolean\n\n /**\n * Ping interval in milliseconds\n *\n * @remarks\n * Time between ping frames when `enablePing` is true.\n *\n * @default 30000 (30 seconds)\n */\n pingInterval?: number\n\n /**\n * Ping timeout in milliseconds\n *\n * @remarks\n * Time to wait for pong response before closing connection.\n *\n * @default 5000 (5 seconds)\n */\n pingTimeout?: number\n\n /**\n * Shared connection map\n *\n * @remarks\n * Optional map for sharing connections across multiple server instances.\n * If not provided, a new map will be created.\n */\n connections?: Map<ClientId, IClientConnection>\n\n /**\n * Custom WebSocket Server constructor\n *\n * @remarks\n * Allows using a custom WebSocket server implementation.\n * Defaults to the standard `ws` WebSocketServer.\n */\n ServerConstructor?: new (config: WsServerOptions) => ServerInstance\n}\n\n/**\n * WebSocket Server Transport\n *\n * @example\n * ```ts\n * const transport = new WebSocketServerTransport({ server: httpServer, path: '/ws' })\n * transport.on('connection', (client) => console.log(client.id))\n * ```\n */\nexport class WebSocketServerTransport extends EventEmitter {\n /**\n * Map of connected clients by ID\n *\n * @remarks\n * Public map of all active connections. Can be used to look up clients\n * by ID or iterate over all connections.\n */\n public readonly connections: Map<ClientId, IClientConnection>\n\n /** @internal */\n private readonly wsServer: ServerInstance\n /** @internal */\n private readonly config: WebSocketServerTransportConfig & {\n pingInterval: number\n pingTimeout: number\n enablePing: boolean\n }\n /** @internal */\n private pingTimer?: ReturnType<typeof setInterval>\n\n /**\n * Creates a new WebSocket Server Transport instance\n *\n * @remarks\n * Initializes the WebSocket transport layer with the provided configuration.\n * Sets up the underlying WebSocketServer, configures ping/pong, and\n * establishes event handlers.\n *\n * @param config - Transport configuration options\n *\n * @example\n * ```ts\n * const transport = new WebSocketServerTransport({\n * server: httpServer,\n * path: '/ws',\n * enablePing: true,\n * pingInterval: 30000,\n * pingTimeout: 5000\n * })\n * ```\n *\n * @emits connection When a new client connects\n * @emits disconnection When a client disconnects\n * @emits message When a message is received from a client\n * @emits error When an error occurs\n */\n constructor(config: WebSocketServerTransportConfig) {\n super()\n this.setMaxListeners(100)\n\n this.connections = config.connections ?? new Map()\n\n this.config = {\n ...config,\n path: config.path ?? DEFAULT_PATH,\n maxPayload: config.maxPayload ?? DEFAULT_MAX_PAYLOAD,\n enablePing: config.enablePing ?? true,\n pingInterval: config.pingInterval ?? DEFAULT_PING_INTERVAL,\n pingTimeout: config.pingTimeout ?? DEFAULT_PING_TIMEOUT,\n connections: this.connections,\n }\n\n const ServerConstructor = config.ServerConstructor ?? WsServer\n this.wsServer = new ServerConstructor({\n server: this.config.server,\n path: this.config.path,\n maxPayload: this.config.maxPayload,\n })\n\n this.setupEventHandlers()\n\n if (this.config.enablePing) {\n this.startPingTimer()\n }\n }\n\n private setupEventHandlers(): void {\n this.wsServer.on(\n 'connection',\n (\n socket: WebSocket,\n request: import('node:http').IncomingMessage,\n ) => {\n this.handleConnection(socket, request)\n },\n )\n\n this.wsServer.on('error', (error: Error) => {\n this.emit('error', error)\n })\n }\n\n private async handleConnection(\n socket: WebSocket,\n request: IncomingMessage,\n ): Promise<void> {\n let clientId = generateClientId()\n const connectedAt = Date.now()\n\n const connection: IClientConnection = {\n socket,\n id: clientId,\n connectedAt,\n lastPingAt: connectedAt,\n }\n\n this.connections.set(clientId, connection)\n\n socket.on('message', (data: Buffer) => {\n this.handleMessage(clientId, data)\n })\n\n socket.on('close', (_code: number, _reason: Buffer) => {\n this.handleDisconnection(clientId)\n })\n\n socket.on('error', (error: Error) => {\n this.emit('error', error)\n })\n\n if (this.config.enablePing) {\n this.setupPingPong(clientId, socket)\n }\n\n // Emit connection event\n this.emit('connection', connection, request)\n }\n\n private handleMessage(clientId: ClientId, data: Buffer): void {\n try {\n const message = JSON.parse(data.toString())\n const connection = this.connections.get(clientId)\n\n if (\n connection &&\n message.type === MessageType.SIGNAL &&\n message.signal === SignalType.PONG\n ) {\n connection.lastPingAt = Date.now()\n }\n\n // Emit message event\n this.emit('message', clientId, message)\n } catch (error) {\n this.emit('error', error as Error)\n }\n }\n\n private handleDisconnection(clientId: ClientId): void {\n this.emit('disconnection', clientId)\n this.connections.delete(clientId)\n }\n\n private setupPingPong(clientId: ClientId, socket: WebSocket): void {\n socket.on('pong', () => {\n const connection = this.connections.get(clientId)\n if (connection) {\n connection.lastPingAt = Date.now()\n }\n })\n }\n\n private startPingTimer(): void {\n if (this.pingTimer) {\n clearInterval(this.pingTimer)\n }\n\n this.pingTimer = setInterval(() => {\n this.checkConnections()\n }, this.config.pingInterval)\n }\n\n private checkConnections(): void {\n const now = Date.now()\n const connections = Array.from(this.connections.values())\n\n for (const connection of connections) {\n const socket = connection.socket\n const lastPing = connection.lastPingAt ?? connection.connectedAt\n\n // Check for timeout\n if (\n now - lastPing >\n this.config.pingInterval + this.config.pingTimeout\n ) {\n socket.close(1000, 'Ping timeout')\n continue\n }\n\n // Send ping if socket is open\n if (socket.readyState === 1) {\n socket.ping()\n }\n }\n }\n}\n","/**\n * Config Module\n * Single source of truth for all constant values and default configuration for the server.\n *\n * @module config\n */\n\n/**\n * WebSocket close codes\n * Based on RFC 6455 and custom codes for application-specific closures\n */\nexport const CLOSE_CODES = {\n /**\n * Normal closure\n */\n NORMAL: 1000,\n\n /**\n * Endpoint is going away\n */\n GOING_AWAY: 1001,\n\n /**\n * Protocol error\n */\n PROTOCOL_ERROR: 1002,\n\n /**\n * Unsupported data\n */\n UNSUPPORTED_DATA: 1003,\n\n /**\n * No status received\n */\n NO_STATUS: 1005,\n\n /**\n * Abnormal closure\n */\n ABNORMAL: 1006,\n\n /**\n * Invalid frame payload data\n */\n INVALID_PAYLOAD: 1007,\n\n /**\n * Policy violation\n */\n POLICY_VIOLATION: 1008,\n\n /**\n * Message too big\n */\n MESSAGE_TOO_BIG: 1009,\n\n /**\n * Missing extension\n */\n MISSING_EXTENSION: 1010,\n\n /**\n * Internal error\n */\n INTERNAL_ERROR: 1011,\n\n /**\n * Service restart\n */\n SERVICE_RESTART: 1012,\n\n /**\n * Try again later\n */\n TRY_AGAIN_LATER: 1013,\n\n /**\n * Connection rejected by middleware\n */\n REJECTED: 4001,\n\n /**\n * Rate limit exceeded\n */\n RATE_LIMITED: 4002,\n\n /**\n * Channel not found\n */\n CHANNEL_NOT_FOUND: 4003,\n\n /**\n * Unauthorized\n */\n UNAUTHORIZED: 4005,\n} as const\n\n/**\n * Type for WebSocket close code values\n */\nexport type CloseCode = (typeof CLOSE_CODES)[keyof typeof CLOSE_CODES]\n\n/**\n * Application error codes\n * Used in error messages sent to clients\n */\nexport const ERROR_CODES = {\n /**\n * Action rejected by middleware\n */\n REJECTED: 'REJECTED',\n\n /**\n * Channel name missing from message\n */\n MISSING_CHANNEL: 'MISSING_CHANNEL',\n\n /**\n * Subscribe action rejected\n */\n SUBSCRIBE_REJECTED: 'SUBSCRIBE_REJECTED',\n\n /**\n * Unsubscribe action rejected\n */\n UNSUBSCRIBE_REJECTED: 'UNSUBSCRIBE_REJECTED',\n\n /**\n * Rate limit exceeded\n */\n RATE_LIMITED: 'RATE_LIMITED',\n\n /**\n * Authentication failed\n */\n AUTH_FAILED: 'AUTH_FAILED',\n\n /**\n * Authorization failed\n */\n NOT_AUTHORIZED: 'NOT_AUTHORIZED',\n\n /**\n * Channel not allowed\n */\n CHANNEL_NOT_ALLOWED: 'CHANNEL_NOT_ALLOWED',\n\n /**\n * Invalid message format\n */\n INVALID_MESSAGE: 'INVALID_MESSAGE',\n\n /**\n * Server error\n */\n SERVER_ERROR: 'SERVER_ERROR',\n} as const\n\n/**\n * Type for error code values\n */\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES]\n\n// ============================================================\n// DEFAULTS\n// ============================================================\n\n/**\n * Default maximum message payload size in bytes (1MB)\n */\nexport const DEFAULT_MAX_PAYLOAD = 1048576\n\n/**\n * Default ping interval in milliseconds (30 seconds)\n */\nexport const DEFAULT_PING_INTERVAL = 30000\n\n/**\n * Default ping timeout in milliseconds (5 seconds)\n */\nexport const DEFAULT_PING_TIMEOUT = 5000\n\n/**\n * Default server port\n */\nexport const DEFAULT_PORT = 3000\n\n/**\n * Default server host\n */\nexport const DEFAULT_HOST = '0.0.0.0'\n\n/**\n * Default WebSocket path\n */\nexport const DEFAULT_PATH = '/syncar'\n\n/**\n * Default ping enabled\n */\nexport const DEFAULT_ENABLE_PING = true\n\n/**\n * Default server configuration\n */\nexport const DEFAULT_SERVER_CONFIG = {\n port: DEFAULT_PORT,\n host: DEFAULT_HOST,\n path: DEFAULT_PATH,\n enablePing: DEFAULT_ENABLE_PING,\n pingInterval: DEFAULT_PING_INTERVAL,\n pingTimeout: DEFAULT_PING_TIMEOUT,\n chunkSize: 500,\n} as const\n\n/**\n * Default rate limit settings\n */\nexport const DEFAULT_RATE_LIMIT = {\n /**\n * Default maximum messages per time window\n */\n maxMessages: 100,\n\n /**\n * Default time window in milliseconds (1 minute)\n */\n windowMs: 60000,\n} as const\n\n/**\n * All defaults as a single object\n */\nexport const DEFAULTS = {\n server: DEFAULT_SERVER_CONFIG,\n rateLimit: DEFAULT_RATE_LIMIT,\n} as const\n","import type { IClientConnection, SignalMessage } from '../types'\n\nimport { createSignalMessage, isReservedChannelName } from '../utils'\nimport { SignalType } from '../types'\nimport { ChannelError, MessageError } from '../errors'\nimport { ContextManager } from '../context'\nimport { ClientRegistry } from '../registry'\n\nexport interface SignalHandlerOptions {\n requireChannel?: boolean\n allowReservedChannels?: boolean\n sendAcknowledgments?: boolean\n autoRespondToPing?: boolean\n}\n\nexport class SignalHandler {\n private readonly registry: ClientRegistry\n private readonly context: ContextManager\n private readonly options: Required<SignalHandlerOptions>\n\n constructor(dependencies: {\n registry: ClientRegistry\n context: ContextManager\n options?: SignalHandlerOptions\n }) {\n this.registry = dependencies.registry\n this.context = dependencies.context\n\n // Apply defaults\n this.options = {\n requireChannel: dependencies.options?.requireChannel ?? false,\n allowReservedChannels:\n dependencies.options?.allowReservedChannels ?? false,\n sendAcknowledgments:\n dependencies.options?.sendAcknowledgments ?? true,\n autoRespondToPing: dependencies.options?.autoRespondToPing ?? true,\n }\n }\n\n async handleSignal(\n client: IClientConnection,\n message: SignalMessage,\n ): Promise<void> {\n // 1. Create Context based on signal\n let ctx\n\n if (message.signal === SignalType.SUBSCRIBE) {\n ctx = this.context.createSubscribeContext(client, message.channel!)\n } else if (message.signal === SignalType.UNSUBSCRIBE) {\n ctx = this.context.createUnsubscribeContext(\n client,\n message.channel!,\n )\n } else {\n ctx = this.context.createMessageContext(client, message)\n }\n\n // 3. Define Kernel\n const kernel = async () => {\n switch (message.signal) {\n case SignalType.SUBSCRIBE:\n await this.handleSubscribe(client, message)\n break\n\n case SignalType.UNSUBSCRIBE:\n await this.handleUnsubscribe(client, message)\n break\n\n case SignalType.PING:\n await this.handlePing(client, message)\n break\n\n case SignalType.PONG:\n await this.handlePong(client, message)\n break\n\n default:\n throw new MessageError(\n `Unknown signal type: ${message.signal}`,\n )\n }\n }\n\n // 4. Get the unified pipeline (Global + Channel specific if applicable)\n let channelInstance = undefined\n if (message.channel) {\n channelInstance = this.registry.getChannel(message.channel)\n }\n\n const pipeline = this.context.getPipeline(channelInstance)\n\n // 5. Execute Onion (Global + Channel Middleware)\n await this.context.execute(ctx, pipeline, kernel)\n }\n\n async handleSubscribe(\n client: IClientConnection,\n message: SignalMessage,\n ): Promise<void> {\n const { channel } = message\n\n // Check for reserved channel\n if (\n !this.options.allowReservedChannels &&\n isReservedChannelName(channel)\n ) {\n throw new ChannelError(\n `Cannot subscribe to reserved channel: ${channel}`,\n )\n }\n\n // Cannot subscribe to broadcast channel (it's for all clients)\n const channelInstance = this.registry.getChannel(channel)\n if (channelInstance?.scope === 'broadcast') {\n throw new ChannelError('Cannot subscribe to broadcast channel')\n }\n\n // Subscribe to channel via registry\n // Features like reserved channels, max subscribers, etc. are now\n // handled via onSubscribe callbacks that can reject the subscription\n const success = this.registry.subscribe(client.id, channel)\n if (!success) {\n throw new ChannelError(\n `Failed to subscribe client ${client.id} to channel ${channel}`,\n )\n }\n\n // Send acknowledgment\n if (this.options.sendAcknowledgments) {\n const ackMessage = createSignalMessage(\n channel,\n SignalType.SUBSCRIBED,\n undefined,\n message.id,\n )\n client.socket.send(JSON.stringify(ackMessage))\n }\n }\n\n async handleUnsubscribe(\n client: IClientConnection,\n message: SignalMessage,\n ): Promise<void> {\n const { channel } = message\n\n // Check if client is subscribed\n if (!this.registry.isSubscribed(client.id, channel)) {\n throw new ChannelError(\n `Client not subscribed to channel: ${channel}`,\n )\n }\n\n // Unsubscribe from channel via registry\n this.registry.unsubscribe(client.id, channel)\n\n // Send acknowledgment\n if (this.options.sendAcknowledgments) {\n const ackMessage = createSignalMessage(\n channel,\n SignalType.UNSUBSCRIBED,\n undefined,\n message.id,\n )\n client.socket.send(JSON.stringify(ackMessage), () => {})\n }\n }\n\n async handlePing(\n client: IClientConnection,\n message: SignalMessage,\n ): Promise<void> {\n // Update client's last ping time\n client.lastPingAt = Date.now()\n\n // Auto-respond with PONG if enabled\n if (this.options.autoRespondToPing) {\n const pongMessage = createSignalMessage(\n message.channel,\n SignalType.PONG,\n undefined,\n message.id,\n )\n client.socket.send(JSON.stringify(pongMessage), () => {})\n }\n }\n\n async handlePong(\n client: IClientConnection,\n _message: SignalMessage,\n ): Promise<void> {\n // PONG is received, connection is alive\n // The timestamp is already updated by the transport layer\n client.lastPingAt = Date.now()\n }\n\n getOptions(): Readonly<Required<SignalHandlerOptions>> {\n return this.options\n }\n}\n","import type { IClientConnection, DataMessage } from '../types'\n\nimport { MessageError, ChannelError } from '../errors'\nimport { isDataMessage } from '../utils'\nimport { ContextManager } from '../context'\nimport { ClientRegistry } from '../registry'\n\nexport interface MessageHandlerOptions {\n requireChannel?: boolean\n}\n\nexport class MessageHandler {\n private readonly registry: ClientRegistry\n private readonly context: ContextManager\n private readonly options: Required<MessageHandlerOptions>\n\n constructor(dependencies: {\n registry: ClientRegistry\n context: ContextManager\n options?: MessageHandlerOptions\n }) {\n this.registry = dependencies.registry\n this.context = dependencies.context\n\n // Apply defaults\n this.options = {\n requireChannel: dependencies.options?.requireChannel ?? true,\n }\n }\n\n async handleMessage<T = unknown>(\n client: IClientConnection,\n message: DataMessage<T>,\n ): Promise<void> {\n // Validate message is a DataMessage\n if (!isDataMessage<T>(message)) {\n throw new MessageError(\n 'Invalid message type: expected DATA message',\n )\n }\n\n // Get channel using the registry\n const channel = this.registry.getChannel<T>(message.channel)\n\n // Validate channel exists\n if (this.options.requireChannel && !channel) {\n throw new ChannelError(`Channel not found: ${message.channel}`)\n }\n\n // Build the middleware pipeline\n const pipeline = this.context.getPipeline(channel)\n\n // Create Context\n const ctx = this.context.createMessageContext(client, message)\n\n // Define Kernel\n const kernel = async () => {\n if (channel) {\n await channel.dispatch(message.data, client, message)\n }\n }\n\n // Execute Onion\n await this.context.execute(ctx, pipeline, kernel)\n }\n\n getOptions(): Readonly<Required<MessageHandlerOptions>> {\n return this.options\n }\n}\n","import type { IClientConnection } from '../types'\nimport { ClientRegistry } from '../registry'\nimport { CLOSE_CODES } from '../config'\n\nexport interface ConnectionHandlerOptions {\n rejectionCloseCode?: number\n}\n\nexport class ConnectionHandler {\n private readonly registry: ClientRegistry\n private readonly options: Required<ConnectionHandlerOptions>\n\n constructor(dependencies: {\n registry: ClientRegistry\n options?: ConnectionHandlerOptions\n }) {\n this.registry = dependencies.registry\n\n // Apply defaults\n this.options = {\n rejectionCloseCode:\n dependencies.options?.rejectionCloseCode ??\n CLOSE_CODES.REJECTED,\n }\n }\n\n async handleConnection(connection: IClientConnection): Promise<void> {\n // Register client in registry\n this.registry.register(connection)\n }\n\n async handleDisconnection(\n clientId: string,\n _reason?: string,\n ): Promise<void> {\n const client = this.registry.get(clientId)\n\n if (!client) {\n return // Client already unregistered\n }\n\n // Unregister client\n this.registry.unregister(clientId)\n }\n\n getOptions(): Readonly<Required<ConnectionHandlerOptions>> {\n return this.options\n }\n}\n","import {\n type ChannelName,\n type Message,\n type IMiddleware,\n MessageType,\n ILogger,\n} from './types'\nimport { StateError } from './errors'\nimport { ContextManager } from './context'\nimport { ClientRegistry } from './registry'\nimport { Channel, ChannelOptions } from './channel'\nimport { WebSocketServerTransport } from './websocket'\nimport { createDefaultLogger, publishMessage } from './utils'\nimport { DEFAULT_SERVER_CONFIG, DEFAULT_MAX_PAYLOAD } from './config'\nimport { ConnectionHandler, MessageHandler, SignalHandler } from './handlers'\n\ninterface ServerState {\n started: boolean\n startedAt: number | undefined\n}\n\nexport interface IServerStats {\n /** Number of currently connected clients */\n clientCount: number\n /** Number of active channels */\n channelCount: number\n /** Total number of channel subscriptions across all channels */\n subscriptionCount: number\n /** Unix timestamp (ms) when the server was started */\n startedAt?: number\n}\n\n/**\n * Server configuration options\n * @see {@link DEFAULT_SERVER_CONFIG} for default values\n */\nexport interface IServerOptions {\n /**\n * HTTP or HTTPS server instance\n *\n * @remarks\n * If provided, the WebSocket server will attach to this existing server.\n * If not provided, a new HTTP server will be created automatically.\n */\n server?: import('node:http').Server | import('node:https').Server\n\n /**\n * Custom logger instance\n *\n * @remarks\n * Logger conforming to the {@link ILogger} interface. Used for\n * debugging, error reporting, and operational monitoring.\n */\n logger: ILogger\n\n /**\n * Port to listen on (default: 3000)\n *\n * @remarks\n * Only used when creating a new HTTP server. Ignored if `server`\n * option is provided.\n */\n port: number\n\n /**\n * Host to bind to (default: '0.0.0.0')\n *\n * @remarks\n * Determines which network interface the server listens on.\n * Use 'localhost' for local-only access or '0.0.0.0' for all interfaces.\n */\n host: string\n\n /**\n * WebSocket path (default: '/syncar')\n *\n * @remarks\n * The URL path for WebSocket connections. Clients must connect to\n * `ws://host:port/path` to establish a connection.\n */\n path: string\n\n /**\n * Transport implementation\n *\n * @remarks\n * Custom WebSocket transport layer. Defaults to {@link WebSocketServerTransport}\n * if not provided. Allows for custom transport implementations.\n */\n transport: WebSocketServerTransport\n\n /**\n * Enable automatic ping/pong (default: true)\n *\n * @remarks\n * When enabled, the server sends periodic ping frames to detect\n * dead connections and maintain keep-alive.\n */\n enablePing: boolean\n\n /**\n * Ping interval in ms (default: 30000)\n *\n * @remarks\n * Time between ping frames when `enablePing` is true.\n * Lower values detect dead connections faster but increase bandwidth.\n */\n pingInterval: number\n\n /**\n * Ping timeout in ms (default: 5000)\n *\n * @remarks\n * Time to wait for pong response before closing connection.\n * Should be significantly less than `pingInterval`.\n */\n pingTimeout: number\n\n /**\n * Client registry instance\n *\n * @remarks\n * Shared registry for tracking clients and subscriptions.\n * Allows multiple server instances to share state.\n */\n registry: ClientRegistry\n\n /**\n * Global middleware chain\n *\n * @remarks\n * Middleware functions applied to all actions before channel-specific middleware.\n * Executed in the order they are defined.\n *\n * @example\n * ```ts\n * middleware: [\n * authenticate({ verifyToken }),\n * logger(),\n * rateLimit({ maxRequests: 100 })\n * ]\n * ```\n */\n middleware: IMiddleware[]\n\n /**\n * Chunk size for large broadcasts (default: 500)\n *\n * @remarks\n * When broadcasting to more than this many clients, messages are sent\n * in chunks to avoid blocking the event loop. Lower values reduce latency\n * per chunk but increase total broadcast time.\n */\n chunkSize: number\n}\n\n/**\n * Syncar Server - Real-time WebSocket server with pub/sub channels\n *\n * @example\n * ```ts\n * const server = createSyncarServer({ port: 3000 })\n * await server.start()\n *\n * const chat = server.createChannel('chat')\n * chat.publish('Hello!')\n * ```\n */\nexport class SyncarServer {\n private readonly config: IServerOptions\n private transport: WebSocketServerTransport | undefined\n public readonly registry: ClientRegistry\n private readonly context: ContextManager\n private readonly status: ServerState = {\n started: false,\n startedAt: undefined,\n }\n private connectionHandler: ConnectionHandler | undefined\n private messageHandler: MessageHandler | undefined\n private signalHandler: SignalHandler | undefined\n\n constructor(config: IServerOptions) {\n this.config = config\n\n // Use the injected client registry from options\n this.registry = this.config.registry\n\n // Create context manager\n this.context = new ContextManager()\n\n // Register any middleware from config\n if (this.config.middleware) {\n for (const mw of this.config.middleware) {\n this.context.use(mw)\n }\n }\n }\n\n /**\n * Start the server and begin accepting connections\n *\n * @remarks\n * Initializes the WebSocket transport layer, sets up event handlers,\n * and prepares the server for connections.\n *\n * @throws {StateError} If the server is already started\n *\n * @example\n * ```ts\n * const server = createSyncarServer({ port: 3000 })\n * server.start()\n * console.log('Server is running')\n * ```\n */\n start(): void {\n if (this.status.started) {\n throw new StateError('Server is already started')\n }\n\n // Use provided transport from config\n this.transport = this.config.transport\n\n // Create handlers\n this.connectionHandler = new ConnectionHandler({\n registry: this.registry,\n })\n this.messageHandler = new MessageHandler({\n registry: this.registry,\n context: this.context,\n })\n this.signalHandler = new SignalHandler({\n registry: this.registry,\n context: this.context,\n })\n\n // Set up transport event handlers\n this.setupTransportHandlers()\n\n // Update state\n this.status.started = true\n this.status.startedAt = Date.now()\n }\n\n /**\n * Stop the server and close all connections\n *\n * @remarks\n * Gracefully shuts down the server by clearing all handlers,\n * removing all channels, and allowing the transport layer to close.\n * Existing connections will be terminated.\n *\n * @example\n * ```ts\n * server.stop()\n * console.log('Server stopped')\n * ```\n */\n stop(): void {\n if (!this.status.started || !this.transport) {\n return // Already stopped\n }\n\n // Clear handlers\n this.connectionHandler = undefined\n this.messageHandler = undefined\n this.signalHandler = undefined\n\n // Clear channels from registry\n this.registry.clear()\n\n // Update state\n this.status.started = false\n this.status.startedAt = undefined\n }\n\n /**\n * Create or retrieve a channel\n *\n * @remarks\n * Unified channel creation.\n *\n * @template T - Type of data to be published on this channel (default: unknown)\n * @param name - Unique channel name\n * @param options - Channel configuration options\n * @returns The channel instance\n *\n * @throws {StateError} If the server hasn't been started yet\n *\n * @example\n * ### Default: bidirectional (chat room)\n * ```ts\n * const chat = server.createChannel('chat')\n * chat.onMessage((data, client) => {\n * chat.publish(data, [client.id]) // Relay to others, excluding sender\n * })\n * ```\n */\n createChannel<T = unknown>(\n name: ChannelName,\n options?: ChannelOptions,\n ): Channel<T> {\n if (!this.status.started || !this.transport) {\n throw new StateError(\n 'Server must be started before creating channels',\n )\n }\n\n // Check if channel already exists\n const existing = this.registry.getChannel<T>(name) as Channel<T>\n if (existing) return existing\n\n const channel = new Channel<T>({\n name,\n registry: this.registry,\n options,\n chunkSize: this.config.chunkSize,\n })\n\n this.registry.registerChannel(channel)\n return channel\n }\n\n /**\n * Check if a channel exists\n *\n * @param name - The channel name to check\n * @returns `true` if a channel with this name exists, `false` otherwise\n *\n * @example\n * ```ts\n * if (!server.hasChannel('chat')) {\n * const chat = server.createChannel('chat')\n * }\n * ```\n */\n hasChannel(name: ChannelName): boolean {\n return !!this.registry.getChannel(name)\n }\n\n /**\n * Broadcast data to all connected clients\n *\n * @remarks\n * A convenience method to send data to every client currently connected\n * to the server. This uses the `publishInChunks` utility to efficiently\n * broadcast messages without blocking the event loop.\n *\n * @param data - The data to broadcast to all clients\n *\n * @example\n * ```ts\n * server.broadcast({\n * type: 'announcement',\n * content: 'Server update in progress'\n * })\n * ```\n */\n broadcast<T = unknown>(data: T): void {\n const connections = this.registry.getAll()\n publishMessage<T>({\n data,\n connections,\n chunkSize: this.config.chunkSize,\n channel: '__broadcast__',\n })\n }\n\n /**\n * Get all active channel names\n *\n * @returns Array of channel names currently registered on the server\n *\n * @example\n * ```ts\n * const channels = server.getChannels()\n * console.log('Active channels:', channels)\n * // ['chat', 'notifications', 'presence']\n * ```\n */\n getChannels(): ChannelName[] {\n return this.registry.getChannels()\n }\n\n /**\n * Register a global middleware\n *\n * @remarks\n * Adds a middleware function to the global middleware chain.\n * Global middleware runs before channel-specific middleware for all actions.\n *\n * @param middleware - The middleware function to register\n *\n * @example\n * ```ts\n * import { authenticate } from '@syncar/server'\n *\n * server.use(authenticate({\n * verifyToken: async (token) => jwt.verify(token, SECRET)\n * }))\n * ```\n *\n * @see {@link IMiddleware} for middleware interface\n */\n use(middleware: IMiddleware): void {\n this.context.use(middleware)\n }\n\n /**\n * Get server statistics\n *\n * @returns Server statistics including client count, channel count,\n * subscription count, and start time\n * @see {@link IServerStats} for statistics structure\n */\n getStats(): IServerStats {\n return {\n startedAt: this.status.startedAt,\n clientCount: this.registry.getCount(),\n channelCount: this.registry.getChannels().length,\n subscriptionCount: this.registry.getTotalSubscriptionCount(),\n }\n }\n\n /**\n * Get the server configuration (read-only)\n *\n * @returns Readonly copy of the server configuration options\n */\n getConfig(): Readonly<IServerOptions> {\n return this.config\n }\n\n private setupTransportHandlers(): void {\n const transport = this.transport!\n\n transport.on('connection', async (connection) => {\n try {\n await this.context.executeConnection(connection, 'connect')\n await this.connectionHandler!.handleConnection(connection)\n } catch (error) {\n this.config.logger.error(\n 'Error handling connection:',\n error as Error,\n )\n }\n })\n\n transport.on('disconnection', async (clientId) => {\n try {\n const client = this.registry.get(clientId)\n if (client) {\n await this.context.executeConnection(client, 'disconnect')\n await this.connectionHandler!.handleDisconnection(clientId)\n }\n } catch (error) {\n this.config.logger.error(\n 'Error handling disconnection:',\n error as Error,\n )\n }\n })\n\n transport.on('message', async (clientId: string, message: Message) => {\n try {\n const client = this.registry.get(clientId)\n if (!client) return\n\n if (message.type === MessageType.DATA) {\n await this.messageHandler!.handleMessage(client, message)\n } else if (message.type === MessageType.SIGNAL) {\n await this.signalHandler!.handleSignal(client, message)\n }\n } catch (error) {\n this.config.logger.error((error as Error).message)\n }\n })\n\n transport.on('error', (error: Error) => {\n this.config.logger.error(error.message)\n })\n }\n}\n\n/**\n * Create a Syncar server with automatic WebSocket transport setup\n *\n * @param config - Optional partial server configuration\n * @returns Configured Syncar server instance\n *\n * @example\n * ```ts\n * const server = createSyncarServer({ port: 3000 })\n * server.start()\n * ```\n */\nexport function createSyncarServer(\n config: Partial<IServerOptions> = {},\n): SyncarServer {\n // Ensure registry exists\n const registry = config.registry ?? new ClientRegistry()\n const logger = config.logger ?? createDefaultLogger()\n\n // Merge defaults\n const serverOptions: IServerOptions = {\n ...DEFAULT_SERVER_CONFIG,\n middleware: [],\n ...config,\n registry,\n logger,\n } as IServerOptions\n\n if (!serverOptions.transport) {\n if (!serverOptions.server) {\n import('node:http').then((http) => {\n const httpServer = http.createServer()\n httpServer.listen(serverOptions.port, serverOptions.host)\n serverOptions.server = httpServer\n })\n }\n\n serverOptions.transport = new WebSocketServerTransport({\n server: serverOptions.server,\n path: serverOptions.path,\n maxPayload:\n (config as { maxPayload?: number }).maxPayload ??\n DEFAULT_MAX_PAYLOAD,\n enablePing: serverOptions.enablePing,\n pingInterval: serverOptions.pingInterval,\n pingTimeout: serverOptions.pingTimeout,\n connections: registry.connections,\n })\n }\n\n return new SyncarServer(serverOptions)\n}\n"],"mappings":"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","names":["SyncarError","_SyncarError","message","code","context","ConfigError","TransportError","ChannelError","ClientError","MessageError","ValidationError","StateError","MiddlewareRejectionError","_MiddlewareRejectionError","reason","action","MiddlewareExecutionError","_MiddlewareExecutionError","middleware","cause","compose","middleware","context","next","index","dispatch","res","handler","createContext","options","action","client","message","channel","initialState","state","key","value","reason","MiddlewareRejectionError","ContextManager","middleware","index","channelInstance","pipeline","channelMiddlewares","c","finalHandler","context","middlewares","wrappedMiddlewares","mw","i","ctx","next","error","MiddlewareExecutionError","middlewareName","compose","generateMessageId","generateClientId","CHANNEL_NAME_MIN_LENGTH","CHANNEL_NAME_MAX_LENGTH","RESERVED_PREFIX","isValidChannelName","name","isReservedChannelName","assertValidChannelName","isDataMessage","message","createDataMessage","channel","data","id","createSignalMessage","signal","publishMessage","config","connections","chunkSize","dataMessage","index","nextChunk","chunk","client","LOG_LEVEL_NAMES","createLogTimestamp","createDefaultLogger","prefix","enabled","logEnabled","formatMessage","level","timestamp","args","ClientRegistry","logger","connection","clientId","clientChannels","channelName","channel","name","subscribers","assertValidChannelName","subscriberIds","id","client","total","Channel","config","name","registry","options","chunkSize","subscriber","data","exclude","connections","excludeSet","conn","publishMessage","handler","client","message","error","middleware","EventEmitter","WsServer","CLOSE_CODES","ERROR_CODES","DEFAULT_MAX_PAYLOAD","DEFAULT_PING_INTERVAL","DEFAULT_PING_TIMEOUT","DEFAULT_PORT","DEFAULT_HOST","DEFAULT_PATH","DEFAULT_ENABLE_PING","DEFAULT_SERVER_CONFIG","WebSocketServerTransport","EventEmitter","config","DEFAULT_PATH","DEFAULT_MAX_PAYLOAD","DEFAULT_PING_INTERVAL","DEFAULT_PING_TIMEOUT","ServerConstructor","WsServer","socket","request","error","clientId","generateClientId","connectedAt","connection","data","_code","_reason","message","now","connections","lastPing","SignalHandler","dependencies","client","message","ctx","kernel","MessageError","channelInstance","pipeline","channel","isReservedChannelName","ChannelError","ackMessage","createSignalMessage","pongMessage","_message","MessageHandler","dependencies","client","message","isDataMessage","MessageError","channel","ChannelError","pipeline","ctx","kernel","ConnectionHandler","dependencies","CLOSE_CODES","connection","clientId","_reason","SyncarServer","config","ContextManager","mw","StateError","ConnectionHandler","MessageHandler","SignalHandler","name","options","existing","channel","Channel","data","connections","publishMessage","middleware","transport","connection","error","clientId","client","message","createSyncarServer","registry","ClientRegistry","logger","createDefaultLogger","serverOptions","DEFAULT_SERVER_CONFIG","http","httpServer","WebSocketServerTransport","DEFAULT_MAX_PAYLOAD"]}