alepha 0.13.1 → 0.13.2
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/README.md +1 -1
- package/dist/api-files/index.d.ts +28 -91
- package/dist/api-files/index.js +10 -755
- package/dist/api-files/index.js.map +1 -1
- package/dist/api-jobs/index.d.ts +46 -46
- package/dist/api-jobs/index.js +13 -13
- package/dist/api-jobs/index.js.map +1 -1
- package/dist/api-notifications/index.d.ts +129 -146
- package/dist/api-notifications/index.js +17 -39
- package/dist/api-notifications/index.js.map +1 -1
- package/dist/api-parameters/index.d.ts +21 -22
- package/dist/api-parameters/index.js +22 -22
- package/dist/api-parameters/index.js.map +1 -1
- package/dist/api-users/index.d.ts +223 -2000
- package/dist/api-users/index.js +914 -4787
- package/dist/api-users/index.js.map +1 -1
- package/dist/api-verifications/index.d.ts +96 -96
- package/dist/batch/index.d.ts +13 -13
- package/dist/batch/index.js +8 -8
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +14 -14
- package/dist/bucket/index.js +12 -12
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/index.d.ts +11 -11
- package/dist/cache/index.js +9 -9
- package/dist/cache/index.js.map +1 -1
- package/dist/cli/index.d.ts +28 -26
- package/dist/cli/index.js +50 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +19 -19
- package/dist/command/index.js +25 -25
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +218 -218
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +232 -232
- package/dist/core/index.js +218 -218
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +2113 -0
- package/dist/core/index.native.js.map +1 -0
- package/dist/datetime/index.d.ts +9 -9
- package/dist/datetime/index.js +7 -7
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +16 -16
- package/dist/email/index.js +9 -9
- package/dist/email/index.js.map +1 -1
- package/dist/file/index.js +1 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/index.d.ts +9 -9
- package/dist/lock/index.js +8 -8
- package/dist/lock/index.js.map +1 -1
- package/dist/lock-redis/index.js +3 -66
- package/dist/lock-redis/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -5
- package/dist/logger/index.js +8 -8
- package/dist/logger/index.js.map +1 -1
- package/dist/orm/index.browser.js +114 -114
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.d.ts +218 -218
- package/dist/orm/index.js +46 -46
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/index.d.ts +29 -29
- package/dist/queue/index.js +20 -20
- package/dist/queue/index.js.map +1 -1
- package/dist/queue-redis/index.d.ts +2 -2
- package/dist/redis/index.d.ts +10 -10
- package/dist/retry/index.d.ts +19 -19
- package/dist/retry/index.js +7 -7
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +16 -16
- package/dist/scheduler/index.js +9 -9
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +80 -80
- package/dist/security/index.js +32 -32
- package/dist/security/index.js.map +1 -1
- package/dist/server/index.browser.js +1 -1
- package/dist/server/index.browser.js.map +1 -1
- package/dist/server/index.d.ts +101 -101
- package/dist/server/index.js +16 -16
- package/dist/server/index.js.map +1 -1
- package/dist/server-auth/index.browser.js +4 -982
- package/dist/server-auth/index.browser.js.map +1 -1
- package/dist/server-auth/index.d.ts +204 -785
- package/dist/server-auth/index.js +47 -1239
- package/dist/server-auth/index.js.map +1 -1
- package/dist/server-cache/index.d.ts +10 -10
- package/dist/server-cache/index.js +2 -2
- package/dist/server-cache/index.js.map +1 -1
- package/dist/server-compress/index.d.ts +4 -4
- package/dist/server-compress/index.js +1 -1
- package/dist/server-compress/index.js.map +1 -1
- package/dist/server-cookies/index.browser.js +8 -8
- package/dist/server-cookies/index.browser.js.map +1 -1
- package/dist/server-cookies/index.d.ts +17 -17
- package/dist/server-cookies/index.js +10 -10
- package/dist/server-cookies/index.js.map +1 -1
- package/dist/server-cors/index.d.ts +17 -17
- package/dist/server-cors/index.js +9 -9
- package/dist/server-cors/index.js.map +1 -1
- package/dist/server-health/index.d.ts +19 -19
- package/dist/server-helmet/index.d.ts +1 -1
- package/dist/server-links/index.browser.js +12 -12
- package/dist/server-links/index.browser.js.map +1 -1
- package/dist/server-links/index.d.ts +59 -251
- package/dist/server-links/index.js +23 -502
- package/dist/server-links/index.js.map +1 -1
- package/dist/server-metrics/index.d.ts +4 -4
- package/dist/server-multipart/index.d.ts +2 -2
- package/dist/server-proxy/index.d.ts +12 -12
- package/dist/server-proxy/index.js +10 -10
- package/dist/server-proxy/index.js.map +1 -1
- package/dist/server-rate-limit/index.d.ts +22 -22
- package/dist/server-rate-limit/index.js +12 -12
- package/dist/server-rate-limit/index.js.map +1 -1
- package/dist/server-security/index.d.ts +22 -22
- package/dist/server-security/index.js +15 -15
- package/dist/server-security/index.js.map +1 -1
- package/dist/server-static/index.d.ts +14 -14
- package/dist/server-static/index.js +8 -8
- package/dist/server-static/index.js.map +1 -1
- package/dist/server-swagger/index.d.ts +25 -184
- package/dist/server-swagger/index.js +21 -724
- package/dist/server-swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +14 -14
- package/dist/sms/index.js +9 -9
- package/dist/sms/index.js.map +1 -1
- package/dist/thread/index.d.ts +11 -11
- package/dist/thread/index.js +17 -17
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/index.d.ts +26 -26
- package/dist/topic/index.js +16 -16
- package/dist/topic/index.js.map +1 -1
- package/dist/topic-redis/index.d.ts +1 -1
- package/dist/vite/index.d.ts +3 -3
- package/dist/vite/index.js +8 -8
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +11 -11
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +58 -58
- package/dist/websocket/index.js +13 -13
- package/dist/websocket/index.js.map +1 -1
- package/package.json +113 -52
- package/src/api-files/services/FileService.ts +5 -7
- package/src/api-jobs/index.ts +1 -1
- package/src/api-jobs/{descriptors → primitives}/$job.ts +8 -8
- package/src/api-jobs/providers/JobProvider.ts +9 -9
- package/src/api-jobs/services/JobService.ts +5 -5
- package/src/api-notifications/index.ts +5 -15
- package/src/api-notifications/{descriptors → primitives}/$notification.ts +10 -10
- package/src/api-notifications/services/NotificationSenderService.ts +3 -3
- package/src/api-parameters/index.ts +1 -1
- package/src/api-parameters/{descriptors → primitives}/$config.ts +7 -12
- package/src/api-users/index.ts +1 -1
- package/src/api-users/{descriptors → primitives}/$userRealm.ts +8 -8
- package/src/api-users/providers/UserRealmProvider.ts +1 -1
- package/src/batch/index.ts +3 -3
- package/src/batch/{descriptors → primitives}/$batch.ts +13 -16
- package/src/bucket/index.ts +8 -8
- package/src/bucket/{descriptors → primitives}/$bucket.ts +8 -8
- package/src/bucket/providers/LocalFileStorageProvider.ts +3 -3
- package/src/cache/index.ts +4 -4
- package/src/cache/{descriptors → primitives}/$cache.ts +15 -15
- package/src/cli/apps/AlephaPackageBuilderCli.ts +24 -2
- package/src/cli/commands/DrizzleCommands.ts +6 -6
- package/src/cli/commands/VerifyCommands.ts +1 -1
- package/src/cli/commands/ViteCommands.ts +6 -1
- package/src/cli/services/ProjectUtils.ts +34 -3
- package/src/command/index.ts +5 -5
- package/src/command/{descriptors → primitives}/$command.ts +9 -12
- package/src/command/providers/CliProvider.ts +10 -10
- package/src/core/Alepha.ts +30 -33
- package/src/core/constants/KIND.ts +1 -1
- package/src/core/constants/OPTIONS.ts +1 -1
- package/src/core/helpers/{descriptor.ts → primitive.ts} +18 -18
- package/src/core/helpers/ref.ts +1 -1
- package/src/core/index.shared.ts +8 -8
- package/src/core/{descriptors → primitives}/$context.ts +5 -5
- package/src/core/{descriptors → primitives}/$hook.ts +4 -4
- package/src/core/{descriptors → primitives}/$inject.ts +2 -2
- package/src/core/{descriptors → primitives}/$module.ts +9 -9
- package/src/core/{descriptors → primitives}/$use.ts +2 -2
- package/src/core/providers/CodecManager.ts +1 -1
- package/src/core/providers/JsonSchemaCodec.ts +1 -1
- package/src/core/providers/StateManager.ts +2 -2
- package/src/datetime/index.ts +3 -3
- package/src/datetime/{descriptors → primitives}/$interval.ts +6 -6
- package/src/email/index.ts +4 -4
- package/src/email/{descriptors → primitives}/$email.ts +8 -8
- package/src/file/index.ts +1 -1
- package/src/lock/index.ts +3 -3
- package/src/lock/{descriptors → primitives}/$lock.ts +10 -10
- package/src/logger/index.ts +8 -8
- package/src/logger/{descriptors → primitives}/$logger.ts +2 -2
- package/src/logger/services/Logger.ts +1 -1
- package/src/orm/constants/PG_SYMBOLS.ts +2 -2
- package/src/orm/index.browser.ts +2 -2
- package/src/orm/index.ts +8 -8
- package/src/orm/{descriptors → primitives}/$entity.ts +11 -11
- package/src/orm/{descriptors → primitives}/$repository.ts +2 -2
- package/src/orm/{descriptors → primitives}/$sequence.ts +8 -8
- package/src/orm/{descriptors → primitives}/$transaction.ts +4 -4
- package/src/orm/providers/PostgresTypeProvider.ts +3 -3
- package/src/orm/providers/RepositoryProvider.ts +4 -4
- package/src/orm/providers/drivers/DatabaseProvider.ts +7 -7
- package/src/orm/services/ModelBuilder.ts +9 -9
- package/src/orm/services/PgRelationManager.ts +2 -2
- package/src/orm/services/PostgresModelBuilder.ts +5 -5
- package/src/orm/services/Repository.ts +7 -7
- package/src/orm/services/SqliteModelBuilder.ts +5 -5
- package/src/queue/index.ts +7 -7
- package/src/queue/{descriptors → primitives}/$consumer.ts +15 -15
- package/src/queue/{descriptors → primitives}/$queue.ts +12 -12
- package/src/queue/providers/WorkerProvider.ts +7 -7
- package/src/retry/index.ts +3 -3
- package/src/retry/{descriptors → primitives}/$retry.ts +14 -14
- package/src/scheduler/index.ts +3 -3
- package/src/scheduler/{descriptors → primitives}/$scheduler.ts +9 -9
- package/src/scheduler/providers/CronProvider.ts +1 -1
- package/src/security/index.ts +9 -9
- package/src/security/{descriptors → primitives}/$permission.ts +7 -7
- package/src/security/{descriptors → primitives}/$realm.ts +6 -12
- package/src/security/{descriptors → primitives}/$role.ts +12 -12
- package/src/security/{descriptors → primitives}/$serviceAccount.ts +8 -8
- package/src/server/index.browser.ts +1 -1
- package/src/server/index.ts +14 -14
- package/src/server/{descriptors → primitives}/$action.ts +13 -13
- package/src/server/{descriptors → primitives}/$route.ts +9 -9
- package/src/server/providers/NodeHttpServerProvider.ts +1 -1
- package/src/server/services/HttpClient.ts +1 -1
- package/src/server-auth/index.browser.ts +1 -1
- package/src/server-auth/index.ts +6 -6
- package/src/server-auth/{descriptors → primitives}/$auth.ts +10 -10
- package/src/server-auth/{descriptors → primitives}/$authCredentials.ts +4 -4
- package/src/server-auth/{descriptors → primitives}/$authGithub.ts +4 -4
- package/src/server-auth/{descriptors → primitives}/$authGoogle.ts +4 -4
- package/src/server-auth/providers/ServerAuthProvider.ts +4 -4
- package/src/server-cache/providers/ServerCacheProvider.ts +7 -7
- package/src/server-compress/providers/ServerCompressProvider.ts +3 -3
- package/src/server-cookies/index.browser.ts +2 -2
- package/src/server-cookies/index.ts +5 -5
- package/src/server-cookies/{descriptors → primitives}/$cookie.browser.ts +12 -12
- package/src/server-cookies/{descriptors → primitives}/$cookie.ts +13 -13
- package/src/server-cookies/providers/ServerCookiesProvider.ts +4 -4
- package/src/server-cookies/services/CookieParser.ts +1 -1
- package/src/server-cors/index.ts +3 -3
- package/src/server-cors/{descriptors → primitives}/$cors.ts +11 -13
- package/src/server-cors/providers/ServerCorsProvider.ts +5 -5
- package/src/server-links/index.browser.ts +5 -5
- package/src/server-links/index.ts +9 -9
- package/src/server-links/{descriptors → primitives}/$remote.ts +11 -11
- package/src/server-links/providers/LinkProvider.ts +7 -7
- package/src/server-links/providers/{RemoteDescriptorProvider.ts → RemotePrimitiveProvider.ts} +6 -6
- package/src/server-links/providers/ServerLinksProvider.ts +3 -3
- package/src/server-proxy/index.ts +3 -3
- package/src/server-proxy/{descriptors → primitives}/$proxy.ts +8 -8
- package/src/server-proxy/providers/ServerProxyProvider.ts +4 -4
- package/src/server-rate-limit/index.ts +6 -6
- package/src/server-rate-limit/{descriptors → primitives}/$rateLimit.ts +13 -13
- package/src/server-rate-limit/providers/ServerRateLimitProvider.ts +5 -5
- package/src/server-security/index.ts +3 -3
- package/src/server-security/{descriptors → primitives}/$basicAuth.ts +13 -13
- package/src/server-security/providers/ServerBasicAuthProvider.ts +5 -5
- package/src/server-security/providers/ServerSecurityProvider.ts +4 -4
- package/src/server-static/index.ts +3 -3
- package/src/server-static/{descriptors → primitives}/$serve.ts +8 -10
- package/src/server-static/providers/ServerStaticProvider.ts +6 -6
- package/src/server-swagger/index.ts +5 -5
- package/src/server-swagger/{descriptors → primitives}/$swagger.ts +9 -9
- package/src/server-swagger/providers/ServerSwaggerProvider.ts +11 -10
- package/src/sms/index.ts +4 -4
- package/src/sms/{descriptors → primitives}/$sms.ts +8 -8
- package/src/thread/index.ts +3 -3
- package/src/thread/{descriptors → primitives}/$thread.ts +13 -13
- package/src/thread/providers/ThreadProvider.ts +7 -9
- package/src/topic/index.ts +5 -5
- package/src/topic/{descriptors → primitives}/$subscriber.ts +14 -14
- package/src/topic/{descriptors → primitives}/$topic.ts +10 -10
- package/src/topic/providers/TopicProvider.ts +4 -4
- package/src/vite/tasks/copyAssets.ts +1 -1
- package/src/vite/tasks/generateSitemap.ts +3 -3
- package/src/vite/tasks/prerenderPages.ts +2 -2
- package/src/vite/tasks/runAlepha.ts +2 -2
- package/src/websocket/index.browser.ts +3 -3
- package/src/websocket/index.shared.ts +2 -2
- package/src/websocket/index.ts +4 -4
- package/src/websocket/interfaces/WebSocketInterfaces.ts +3 -3
- package/src/websocket/{descriptors → primitives}/$channel.ts +10 -10
- package/src/websocket/{descriptors → primitives}/$websocket.ts +8 -8
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +7 -7
- package/src/websocket/providers/WebSocketServerProvider.ts +3 -3
- package/src/websocket/services/WebSocketClient.ts +5 -5
- package/src/api-notifications/providers/MemorySmsProvider.ts +0 -20
- package/src/api-notifications/providers/SmsProvider.ts +0 -8
- /package/src/core/{descriptors → primitives}/$atom.ts +0 -0
- /package/src/core/{descriptors → primitives}/$env.ts +0 -0
- /package/src/server-auth/{descriptors → primitives}/$authApple.ts +0 -0
- /package/src/server-links/{descriptors → primitives}/$client.ts +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["code?: number","envSchema","userId: string | undefined","roomIds: string[]","id: string","ws: WebSocket","provider: NodeWebSocketServerProvider","endpoint: WebSocketDescriptorOptions<any, any>","WebSocket","parsed: any","context: WebSocketHandlerContext<any, any>","channel: ChannelDescriptor<TClient, TServer>","options: {\n url?: string;\n autoReconnect?: boolean;\n reconnectInterval?: number;\n maxReconnectAttempts?: number;\n }","env: Static<typeof envSchema>","url"],"sources":["../../src/websocket/descriptors/$channel.ts","../../src/websocket/providers/WebSocketServerProvider.ts","../../src/websocket/descriptors/$websocket.ts","../../src/websocket/errors/WebSocketError.ts","../../src/websocket/services/RoomManager.ts","../../src/websocket/services/WebSocketTopicService.ts","../../src/websocket/providers/NodeWebSocketServerProvider.ts","../../src/websocket/interfaces/WebSocketInterfaces.ts","../../src/websocket/services/WebSocketClient.ts","../../src/websocket/index.ts"],"sourcesContent":["import {\n createDescriptor,\n Descriptor,\n KIND,\n type TObject,\n type TString,\n type TUnion,\n} from \"alepha\";\n\nexport type TWSObject = TObject | TUnion;\n\n/**\n * Channel descriptor options\n */\nexport interface ChannelDescriptorOptions<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * WebSocket endpoint path (e.g., \"/ws/chat\")\n */\n path: string;\n\n /**\n * Optional description for documentation\n */\n description?: string;\n\n /**\n * Message schemas for bidirectional communication\n */\n schema: {\n /**\n * Optional room ID schema validation\n * Default: t.text() (any string)\n * Can be enforced at application level: t.uuid(), t.regex(/^[a-f0-9\\-]{36}$/)\n */\n roomId?: TString;\n\n /**\n * Messages from server to client\n * This is what clients will receive\n */\n in: TClient;\n\n /**\n * Messages from client to server\n * This is what the server will receive\n */\n out: TServer;\n };\n}\n\n/**\n * Defines a WebSocket channel with specified client and server message schemas.\n *\n * Channels must be defined as class properties to be registered in the Alepha context.\n * They define the \"vocabulary\" for communication - the schema for messages flowing\n * in both directions (server→client and client→server).\n *\n * @example Server-side with $websocket\n * ```typescript\n * class ChatController {\n * // Channel must be defined inside a class\n * chatChannel = $channel({\n * path: \"/ws/chat\",\n * description: \"Real-time chat channel\",\n * schema: {\n * // Server → Client messages\n * in: t.union([\n * t.object({\n * type: t.const(\"append\"),\n * content: t.text(),\n * username: t.text()\n * }),\n * t.object({\n * type: t.const(\"system\"),\n * message: t.text()\n * })\n * ]),\n * // Client → Server messages\n * out: t.object({\n * content: t.text()\n * })\n * }\n * });\n *\n * chat = $websocket({\n * channel: this.chatChannel,\n * handler: async ({ message, reply }) => {\n * await reply({\n * message: { type: \"append\", content: message.content, username: \"user\" }\n * });\n * }\n * });\n * }\n * ```\n *\n * @example Browser-side with useRoom\n * ```typescript\n * // Define channel in a class for browser context\n * class ChatClient {\n * chatChannel = $channel({\n * path: \"/ws/chat\",\n * schema: { in: inSchema, out: outSchema }\n * });\n * }\n *\n * // Use in React component\n * function Chat() {\n * const client = useInject(ChatClient);\n * const chat = useRoom({ roomId: \"lobby\", channel: client.chatChannel, handler: ... }, []);\n * }\n * ```\n */\nexport const $channel = <TClient extends TWSObject, TServer extends TWSObject>(\n options: ChannelDescriptorOptions<TClient, TServer>,\n): ChannelDescriptor<TClient, TServer> => {\n return createDescriptor(ChannelDescriptor<TClient, TServer>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ChannelDescriptor<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> extends Descriptor<ChannelDescriptorOptions<TClient, TServer>> {\n // Channels are just schema definitions - no initialization logic needed\n}\n\n$channel[KIND] = ChannelDescriptor;\n","import type { TWSObject } from \"../descriptors/$channel.ts\";\nimport type {\n EmitOptions,\n WebSocketConnection,\n WebSocketDescriptorOptions,\n} from \"../interfaces/WebSocketInterfaces.ts\";\n\n/**\n * Abstract WebSocket server provider\n *\n * This class provides the base interface that must be implemented by\n * platform-specific providers (Node.js, Browser, etc.)\n */\nexport abstract class WebSocketServerProvider {\n /**\n * Register a WebSocket endpoint with its channel configuration\n */\n abstract registerEndpoint<\n TClient extends TWSObject,\n TServer extends TWSObject,\n >(config: WebSocketDescriptorOptions<TClient, TServer>): void;\n\n /**\n * Emit a message to clients based on targeting criteria\n *\n * This method distributes messages across all server instances via pub/sub.\n */\n abstract emit<TClient extends TWSObject>(\n channelPath: string,\n options: EmitOptions<TClient>,\n ): Promise<void>;\n\n /**\n * Get all active connections (local to this server instance)\n */\n abstract getConnections(): WebSocketConnection[];\n\n /**\n * Get connections in a specific room (local to this server instance)\n */\n abstract getRoomConnections(roomId: string): WebSocketConnection[];\n\n /**\n * Get connections for a specific user (local to this server instance)\n */\n abstract getUserConnections(userId: string): WebSocketConnection[];\n\n /**\n * Close a specific connection\n */\n abstract closeConnection(\n connectionId: string,\n code?: number,\n reason?: string,\n ): Promise<void>;\n}\n","import { $inject, createDescriptor, Descriptor, KIND } from \"alepha\";\nimport type {\n EmitOptions,\n WebSocketDescriptorOptions,\n} from \"../interfaces/WebSocketInterfaces.ts\";\nimport { WebSocketServerProvider } from \"../providers/WebSocketServerProvider.ts\";\nimport type { TWSObject } from \"./$channel.ts\";\n\n/**\n * Defines a WebSocket server endpoint for a specific channel.\n *\n * Server-side only. Creates a WebSocket endpoint that:\n * - Accepts connections from clients\n * - Validates incoming messages against the channel schema\n * - Provides room-based messaging\n * - Integrates with alepha/security for authentication (optional)\n * - Supports horizontal scaling via alepha/topic\n *\n * @example\n * ```typescript\n * class ChatController {\n * chat = $websocket({\n * channel: chatChannel,\n * handler: async ({ connectionId, userId, roomId, message, reply }) => {\n * // Broadcast to all in room except sender\n * await reply({\n * message: {\n * type: \"append\",\n * username: userId,\n * content: message.content\n * },\n * exceptSelf: true\n * });\n * }\n * });\n *\n * async broadcastAnnouncement(roomId: string, text: string) {\n * await this.chat.emit({\n * roomId,\n * message: {\n * type: \"append\",\n * username: \"System\",\n * content: text\n * }\n * });\n * }\n * }\n * ```\n */\nexport const $websocket = <\n TClient extends TWSObject,\n TServer extends TWSObject,\n>(\n options: WebSocketDescriptorOptions<TClient, TServer>,\n): WebSocketDescriptor<TClient, TServer> => {\n return createDescriptor(WebSocketDescriptor<TClient, TServer>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class WebSocketDescriptor<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> extends Descriptor<WebSocketDescriptorOptions<TClient, TServer>> {\n protected readonly webSocketServerProvider = $inject(WebSocketServerProvider);\n\n protected onInit() {\n this.webSocketServerProvider.registerEndpoint(this.options);\n }\n\n /**\n * Emit message to clients\n *\n * Send messages from the server to connected clients based on targeting criteria.\n * Messages are distributed across all server instances via pub/sub.\n *\n * @example\n * ```typescript\n * // Send to specific room\n * await websocket.emit({\n * roomId: \"room-123\",\n * message: { type: \"update\", data: {...} }\n * });\n *\n * // Send to specific user (all their connections)\n * await websocket.emit({\n * userId: \"user-456\",\n * message: { type: \"notification\", text: \"Hello!\" }\n * });\n *\n * // Send to multiple rooms, except certain users\n * await websocket.emit({\n * roomIds: [\"room-1\", \"room-2\"],\n * exceptUserIds: [\"user-123\"],\n * message: { type: \"broadcast\", content: \"System announcement\" }\n * });\n * ```\n */\n public async emit(options: EmitOptions<TClient>): Promise<void> {\n await this.webSocketServerProvider.emit(\n this.options.channel.options.path,\n options,\n );\n }\n}\n\n$websocket[KIND] = WebSocketDescriptor;\n","/**\n * Base WebSocket error class\n */\nexport class WebSocketError extends Error {\n constructor(\n message: string,\n public readonly code?: number,\n ) {\n super(message);\n this.name = \"WebSocketError\";\n }\n}\n\n/**\n * Error thrown when WebSocket connection fails\n */\nexport class WebSocketConnectionError extends WebSocketError {\n constructor(message: string, code?: number) {\n super(message, code);\n this.name = \"WebSocketConnectionError\";\n }\n}\n\n/**\n * Error thrown when WebSocket message validation fails\n */\nexport class WebSocketValidationError extends WebSocketError {\n constructor(message: string) {\n super(message);\n this.name = \"WebSocketValidationError\";\n }\n}\n","import { $logger } from \"alepha/logger\";\n\n/**\n * Manages WebSocket room memberships\n *\n * Rooms are logical groupings of connections. A connection can be in multiple rooms,\n * and messages can be targeted to specific rooms.\n */\nexport class RoomManager {\n protected readonly log = $logger();\n\n /**\n * Maps roomId → Set<connectionId>\n */\n protected readonly rooms = new Map<string, Set<string>>();\n\n /**\n * Maps connectionId → Set<roomId>\n * Inverse index for fast lookup of connection's rooms\n */\n protected readonly connectionRooms = new Map<string, Set<string>>();\n\n /**\n * Join a connection to one or more rooms\n */\n public joinRooms(connectionId: string, roomIds: string[]): void {\n for (const roomId of roomIds) {\n this.joinRoom(connectionId, roomId);\n }\n }\n\n /**\n * Join a connection to a room\n */\n public joinRoom(connectionId: string, roomId: string): void {\n // Add to room\n let room = this.rooms.get(roomId);\n if (!room) {\n room = new Set();\n this.rooms.set(roomId, room);\n }\n room.add(connectionId);\n\n // Update inverse index\n let connRooms = this.connectionRooms.get(connectionId);\n if (!connRooms) {\n connRooms = new Set();\n this.connectionRooms.set(connectionId, connRooms);\n }\n connRooms.add(roomId);\n\n this.log.debug(`Connection ${connectionId} joined room ${roomId}`);\n }\n\n /**\n * Leave a connection from a room\n */\n public leaveRoom(connectionId: string, roomId: string): void {\n // Remove from room\n const room = this.rooms.get(roomId);\n if (room) {\n room.delete(connectionId);\n if (room.size === 0) {\n this.rooms.delete(roomId);\n }\n }\n\n // Update inverse index\n const connRooms = this.connectionRooms.get(connectionId);\n if (connRooms) {\n connRooms.delete(roomId);\n if (connRooms.size === 0) {\n this.connectionRooms.delete(connectionId);\n }\n }\n\n this.log.debug(`Connection ${connectionId} left room ${roomId}`);\n }\n\n /**\n * Remove a connection from all rooms\n */\n public leaveAllRooms(connectionId: string): void {\n const connRooms = this.connectionRooms.get(connectionId);\n if (!connRooms) {\n return;\n }\n\n for (const roomId of connRooms) {\n const room = this.rooms.get(roomId);\n if (room) {\n room.delete(connectionId);\n if (room.size === 0) {\n this.rooms.delete(roomId);\n }\n }\n }\n\n this.connectionRooms.delete(connectionId);\n this.log.debug(`Connection ${connectionId} left all rooms`);\n }\n\n /**\n * Get all connection IDs in a room\n */\n public getRoomConnections(roomId: string): string[] {\n const room = this.rooms.get(roomId);\n return room ? Array.from(room) : [];\n }\n\n /**\n * Get all room IDs for a connection\n */\n public getConnectionRooms(connectionId: string): string[] {\n const connRooms = this.connectionRooms.get(connectionId);\n return connRooms ? Array.from(connRooms) : [];\n }\n\n /**\n * Check if a connection is in a room\n */\n public isInRoom(connectionId: string, roomId: string): boolean {\n const connRooms = this.connectionRooms.get(connectionId);\n return connRooms ? connRooms.has(roomId) : false;\n }\n\n /**\n * Get all active rooms\n */\n public getAllRooms(): string[] {\n return Array.from(this.rooms.keys());\n }\n\n /**\n * Get total number of connections across all rooms\n */\n public getTotalConnections(): number {\n return this.connectionRooms.size;\n }\n\n /**\n * Get room statistics\n */\n public getStats(): {\n totalRooms: number;\n totalConnections: number;\n roomSizes: Map<string, number>;\n } {\n const roomSizes = new Map<string, number>();\n for (const [roomId, connections] of this.rooms) {\n roomSizes.set(roomId, connections.size);\n }\n\n return {\n totalRooms: this.rooms.size,\n totalConnections: this.connectionRooms.size,\n roomSizes,\n };\n }\n}\n","import { type Static, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { $topic } from \"alepha/topic\";\n\n/**\n * WebSocket message distribution event\n */\nconst webSocketMessageSchema = {\n payload: t.object({\n /**\n * Channel path (e.g., \"/ws/chat\")\n */\n channelPath: t.text(),\n\n /**\n * Target room ID(s)\n */\n roomIds: t.optional(t.array(t.text())),\n\n /**\n * Target user ID(s)\n */\n userIds: t.optional(t.array(t.text())),\n\n /**\n * Target connection ID(s)\n */\n connectionIds: t.optional(t.array(t.text())),\n\n /**\n * Exclude connection ID(s) from receiving the message\n */\n exceptConnectionIds: t.optional(t.array(t.text())),\n\n /**\n * Exclude user ID(s) from receiving the message\n */\n exceptUserIds: t.optional(t.array(t.text())),\n\n /**\n * The message payload to send\n */\n message: t.any(),\n }),\n};\n\n/**\n * WebSocket Topic Service\n *\n * Manages pub/sub messaging for WebSocket connections across multiple server instances.\n * Uses alepha/topic for cross-instance message distribution, enabling horizontal scaling.\n *\n * When a WebSocket message needs to be sent:\n * 1. Server instance A publishes to the topic\n * 2. All server instances (A, B, C, etc.) receive the message\n * 3. Each instance sends to its local connections that match the criteria\n *\n * This enables:\n * - Multiple server instances handling WebSocket connections\n * - Redis-backed message distribution (with alepha/topic/redis)\n * - Horizontal scaling without losing messages\n */\nexport class WebSocketTopicService {\n protected readonly log = $logger();\n\n /**\n * Handler function to be called when a message is received from the topic\n * This is set by the WebSocket provider during initialization\n */\n public messageHandler?: (\n event: Static<(typeof webSocketMessageSchema)[\"payload\"]>,\n ) => Promise<void>;\n\n /**\n * Topic for distributing WebSocket messages across server instances\n */\n public readonly topic = $topic({\n name: \"websocket:broadcast\",\n description:\n \"Distributes WebSocket messages across server instances for horizontal scaling\",\n schema: webSocketMessageSchema,\n handler: async (message) => {\n if (this.messageHandler) {\n await this.messageHandler(message.payload);\n }\n },\n });\n\n /**\n * Publish a message to be distributed across all server instances\n */\n public async publish(\n event: Static<(typeof webSocketMessageSchema)[\"payload\"]>,\n ): Promise<void> {\n await this.topic.publish(event);\n }\n\n /**\n * Set the handler for incoming messages\n */\n public setMessageHandler(\n handler: (\n event: Static<(typeof webSocketMessageSchema)[\"payload\"]>,\n ) => Promise<void>,\n ): void {\n this.messageHandler = handler;\n }\n}\n","import type { IncomingMessage } from \"node:http\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n type Static,\n TypeBoxValue,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { WebSocket, WebSocketServer } from \"ws\";\nimport type { TWSObject } from \"../descriptors/$channel.ts\";\nimport { WebSocketValidationError } from \"../errors/WebSocketError.ts\";\nimport type {\n EmitOptions,\n WebSocketConnection,\n WebSocketDescriptorOptions,\n WebSocketHandlerContext,\n WebSocketState,\n} from \"../interfaces/WebSocketInterfaces.ts\";\nimport { RoomManager } from \"../services/RoomManager.ts\";\nimport { WebSocketTopicService } from \"../services/WebSocketTopicService.ts\";\nimport { WebSocketServerProvider } from \"./WebSocketServerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst envSchema = t.object({\n WEBSOCKET_PATH: t.text({\n default: \"/ws\",\n description: \"Base path for WebSocket endpoints\",\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class NodeWebSocketServerProvider extends WebSocketServerProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly roomManager = $inject(RoomManager);\n protected readonly topicService = $inject(WebSocketTopicService);\n protected readonly log = $logger();\n protected readonly env = $env(envSchema);\n\n protected wss?: WebSocketServer;\n protected endpoints = new Map<string, WebSocketDescriptorOptions<any, any>>();\n protected connections = new Map<string, WebSocketConnection>();\n protected userConnections = new Map<string, Set<string>>(); // userId → Set<connectionId>\n protected nextConnectionId = 1;\n\n // -------------------------------------------------------------------------------------------------------------------\n\n public registerEndpoint<TClient extends TWSObject, TServer extends TWSObject>(\n config: WebSocketDescriptorOptions<TClient, TServer>,\n ): void {\n const path = config.channel.options.path;\n this.endpoints.set(path, config);\n }\n\n public async emit<TClient extends TWSObject>(\n channelPath: string,\n options: EmitOptions<TClient>,\n ): Promise<void> {\n // Publish to topic so all server instances receive it\n await this.topicService.publish({\n channelPath,\n roomIds: options.roomIds\n ? options.roomIds\n : options.roomId\n ? [options.roomId]\n : undefined,\n userIds: options.userIds\n ? options.userIds\n : options.userId\n ? [options.userId]\n : undefined,\n connectionIds: options.connectionIds\n ? options.connectionIds\n : options.connectionId\n ? [options.connectionId]\n : undefined,\n exceptConnectionIds: options.exceptConnectionIds,\n exceptUserIds: options.exceptUserIds,\n message: options.message,\n });\n }\n\n public getConnections(): WebSocketConnection[] {\n return Array.from(this.connections.values());\n }\n\n public getRoomConnections(roomId: string): WebSocketConnection[] {\n const connectionIds = this.roomManager.getRoomConnections(roomId);\n return connectionIds\n .map((id) => this.connections.get(id))\n .filter((conn): conn is WebSocketConnection => conn !== undefined);\n }\n\n public getUserConnections(userId: string): WebSocketConnection[] {\n const connectionIds = this.userConnections.get(userId);\n if (!connectionIds) {\n return [];\n }\n return Array.from(connectionIds)\n .map((id) => this.connections.get(id))\n .filter((conn): conn is WebSocketConnection => conn !== undefined);\n }\n\n public async closeConnection(\n connectionId: string,\n code?: number,\n reason?: string,\n ): Promise<void> {\n const connection = this.connections.get(connectionId);\n if (!connection) {\n this.log.warn(`Connection not found: ${connectionId}`);\n return;\n }\n await connection.close(code, reason);\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n protected handleUpgrade(\n request: IncomingMessage,\n socket: any,\n head: Buffer,\n ): boolean {\n const url = new URL(request.url || \"/\", \"http://localhost\");\n const path = url.pathname;\n\n const endpoint = this.endpoints.get(path);\n if (!endpoint) {\n // Not our endpoint - in Vite dev mode, let Vite HMR handle it\n // In production, destroy the socket\n if (!this.alepha.isViteDev()) {\n this.log.warn(`No WebSocket endpoint found for path: ${path}`);\n socket.destroy();\n }\n return false;\n }\n\n this.log.debug(`WebSocket upgrade request: ${path}`);\n\n this.wss?.handleUpgrade(request, socket, head, (ws) => {\n this.handleConnection(ws, endpoint, request);\n });\n\n return true;\n }\n\n protected handleConnection<\n TClient extends TWSObject,\n TServer extends TWSObject,\n >(\n ws: WebSocket,\n endpoint: WebSocketDescriptorOptions<TClient, TServer>,\n request: IncomingMessage,\n ): void {\n const connectionId = `ws-${this.nextConnectionId++}`;\n\n // TODO: Extract userId from security context when alepha/security is available\n const userId: string | undefined = undefined;\n\n // Extract roomIds from query params (e.g., ?roomId=room1&roomId=room2 or ?roomIds=room1,room2)\n const url = new URL(request.url || \"/\", \"http://localhost\");\n const roomIds = this.extractRoomIds(url);\n\n const connection = this.alepha.inject(NodeWebSocketConnection, {\n lifetime: \"transient\",\n args: [connectionId, userId, roomIds, ws, this, endpoint],\n });\n\n this.connections.set(connectionId, connection);\n\n // Track user connections\n if (userId) {\n let userConns = this.userConnections.get(userId);\n if (!userConns) {\n userConns = new Set();\n this.userConnections.set(userId, userConns);\n }\n userConns.add(connectionId);\n\n // Check max connections per user\n if (\n endpoint.maxConnectionsPerUser &&\n userConns.size > endpoint.maxConnectionsPerUser\n ) {\n this.log.warn(\n `User ${userId} exceeded max connections (${endpoint.maxConnectionsPerUser})`,\n );\n ws.close(1008, \"Max connections per user exceeded\");\n return;\n }\n }\n\n // Join rooms\n if (roomIds.length > 0) {\n this.roomManager.joinRooms(connectionId, roomIds);\n }\n\n this.log.info(`WebSocket connection established: ${connectionId}`, {\n path: endpoint.channel.options.path,\n userId,\n roomIds,\n remoteAddress: request.socket.remoteAddress,\n });\n\n // Call onConnect handler if provided\n if (endpoint.onConnect) {\n Promise.resolve(\n endpoint.onConnect({ connectionId, userId, roomIds }),\n ).catch((error) => {\n this.log.error(\"Error in onConnect handler:\", error);\n });\n }\n\n ws.on(\"message\", async (data) => {\n await connection.handleMessage(data);\n });\n\n ws.on(\"close\", (code, reason) => {\n this.log.info(`WebSocket connection closed: ${connectionId}`, {\n code,\n reason: reason.toString(),\n });\n\n // Clean up\n this.connections.delete(connectionId);\n this.roomManager.leaveAllRooms(connectionId);\n\n if (userId) {\n const userConns = this.userConnections.get(userId);\n if (userConns) {\n userConns.delete(connectionId);\n if (userConns.size === 0) {\n this.userConnections.delete(userId);\n }\n }\n }\n\n // Call onDisconnect handler if provided\n if (endpoint.onDisconnect) {\n Promise.resolve(\n endpoint.onDisconnect({ connectionId, userId, roomIds }),\n ).catch((error) => {\n this.log.error(\"Error in onDisconnect handler:\", error);\n });\n }\n });\n\n ws.on(\"error\", (error) => {\n this.log.error(`WebSocket error on ${connectionId}:`, error);\n });\n }\n\n protected extractRoomIds(url: URL): string[] {\n const roomIds: string[] = [];\n\n // Check for roomId parameter (can be multiple)\n const roomIdParams = url.searchParams.getAll(\"roomId\");\n roomIds.push(...roomIdParams);\n\n // Check for roomIds parameter (comma-separated)\n const roomIdsParam = url.searchParams.get(\"roomIds\");\n if (roomIdsParam) {\n roomIds.push(...roomIdsParam.split(\",\").map((id) => id.trim()));\n }\n\n // Default room if none specified\n if (roomIds.length === 0) {\n roomIds.push(\"default\");\n }\n\n return roomIds;\n }\n\n /**\n * Send message to local connections based on targeting criteria\n * This is called by the topic service when a message is received\n */\n protected async sendToLocalConnections(\n channelPath: string,\n message: any,\n criteria: {\n roomIds?: string[];\n userIds?: string[];\n connectionIds?: string[];\n exceptConnectionIds?: string[];\n exceptUserIds?: string[];\n },\n ): Promise<void> {\n const targetConnections = new Set<string>();\n\n // Collect target connections based on criteria\n if (criteria.roomIds) {\n for (const roomId of criteria.roomIds) {\n const roomConns = this.roomManager.getRoomConnections(roomId);\n for (const connId of roomConns) {\n targetConnections.add(connId);\n }\n }\n }\n\n if (criteria.userIds) {\n for (const userId of criteria.userIds) {\n const userConns = this.userConnections.get(userId);\n if (userConns) {\n for (const connId of userConns) {\n targetConnections.add(connId);\n }\n }\n }\n }\n\n if (criteria.connectionIds) {\n for (const connId of criteria.connectionIds) {\n targetConnections.add(connId);\n }\n }\n\n // If no specific targeting, send to all connections on this channel\n if (!criteria.roomIds && !criteria.userIds && !criteria.connectionIds) {\n for (const conn of this.connections.values()) {\n targetConnections.add(conn.id);\n }\n }\n\n // Remove exceptions\n if (criteria.exceptConnectionIds) {\n for (const connId of criteria.exceptConnectionIds) {\n targetConnections.delete(connId);\n }\n }\n\n if (criteria.exceptUserIds) {\n for (const userId of criteria.exceptUserIds) {\n const userConns = this.userConnections.get(userId);\n if (userConns) {\n for (const connId of userConns) {\n targetConnections.delete(connId);\n }\n }\n }\n }\n\n // Send to all target connections\n const serialized = JSON.stringify(message);\n await Promise.all(\n Array.from(targetConnections).map(async (connId) => {\n const conn = this.connections.get(connId);\n if (conn) {\n try {\n await conn.send(serialized);\n } catch (error) {\n this.log.error(`Failed to send to connection ${connId}:`, error);\n }\n }\n }),\n );\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n protected readonly start = $hook({\n on: \"start\",\n handler: async () => {\n if (this.alepha.isServerless()) {\n this.log.debug(\"WebSocket server disabled in serverless mode\");\n return;\n }\n\n this.wss = new WebSocketServer({ noServer: true });\n\n for (const [path, endpoint] of this.endpoints.entries()) {\n this.log.debug(`WebSocket endpoint registered: ${path}`);\n }\n\n // Set up topic service message handler\n this.topicService.setMessageHandler(async (event) => {\n await this.sendToLocalConnections(event.channelPath, event.message, {\n roomIds: event.roomIds,\n userIds: event.userIds,\n connectionIds: event.connectionIds,\n exceptConnectionIds: event.exceptConnectionIds,\n exceptUserIds: event.exceptUserIds,\n });\n });\n\n this.log.info(\"WebSocket server OK\", {\n basePath: this.env.WEBSOCKET_PATH,\n });\n },\n });\n\n protected readonly ready = $hook({\n on: \"ready\",\n handler: async () => {\n if (this.alepha.isServerless() || !this.wss) {\n return;\n }\n\n // Attach upgrade handler to the HTTP server (must be done after HTTP server starts)\n const httpServer = this.alepha.state.get(\"alepha.node.server\");\n if (httpServer) {\n httpServer.on(\"upgrade\", (request, socket, head) => {\n this.handleUpgrade(request, socket, head);\n });\n this.log.debug(\"WebSocket upgrade handler attached to HTTP server\");\n } else {\n this.log.warn(\n \"No HTTP server found - WebSocket upgrade handler not attached\",\n );\n }\n },\n });\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: async () => {\n if (!this.wss) {\n return;\n }\n\n // Close all connections\n for (const connection of this.connections.values()) {\n await connection.close(1001, \"Server shutting down\");\n }\n\n await new Promise<void>((resolve, reject) => {\n this.wss?.close((err) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n });\n\n this.log.info(\"WebSocket server closed\");\n },\n });\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class NodeWebSocketConnection implements WebSocketConnection {\n protected readonly log = $logger();\n public metadata?: Record<string, any>;\n\n constructor(\n public readonly id: string,\n public readonly userId: string | undefined,\n public readonly roomIds: string[],\n protected readonly ws: WebSocket,\n protected readonly provider: NodeWebSocketServerProvider,\n protected readonly endpoint: WebSocketDescriptorOptions<any, any>,\n ) {}\n\n public get readyState(): WebSocketState {\n return this.ws.readyState;\n }\n\n public async send(message: any): Promise<void> {\n if (this.ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"WebSocket is not open\");\n }\n\n const data =\n typeof message === \"string\" ? message : JSON.stringify(message);\n await new Promise<void>((resolve, reject) => {\n this.ws.send(data, (err) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n });\n }\n\n public async close(code?: number, reason?: string): Promise<void> {\n this.ws.close(code, reason);\n }\n\n public async handleMessage(data: any): Promise<void> {\n try {\n const rawMessage = data.toString();\n let parsed: any;\n\n try {\n parsed = JSON.parse(rawMessage);\n } catch {\n this.log.warn(\"Received non-JSON message\");\n return;\n }\n\n // Extract roomId from message (or use first room in connection's rooms)\n const roomId = parsed.roomId || this.roomIds[0] || \"default\";\n\n // Extract message payload\n const message = parsed.message || parsed;\n\n // Validate message against schema (out = client→server)\n const outSchema = this.endpoint.channel.options.schema.out;\n if (!TypeBoxValue.Check(outSchema, message)) {\n const errors = Array.from(TypeBoxValue.Errors(outSchema, message));\n throw new WebSocketValidationError(\n `Message validation failed: ${errors.map((e: any) => e.message).join(\", \")}`,\n );\n }\n\n // Create reply function scoped to this context\n const reply = async (options: {\n message: any;\n roomId?: string;\n exceptSelf?: boolean;\n exceptConnectionIds?: string[];\n exceptUserIds?: string[];\n }) => {\n const targetRoomId = options.roomId || roomId;\n const exceptConnectionIds = options.exceptConnectionIds || [];\n\n if (options.exceptSelf) {\n exceptConnectionIds.push(this.id);\n }\n\n await this.provider.emit(this.endpoint.channel.options.path, {\n message: options.message,\n roomId: targetRoomId,\n exceptConnectionIds,\n exceptUserIds: options.exceptUserIds,\n });\n };\n\n const context: WebSocketHandlerContext<any, any> = {\n connectionId: this.id,\n userId: this.userId,\n roomId,\n message,\n reply,\n };\n\n await this.endpoint.handler(context);\n } catch (error) {\n this.log.error(`Error handling WebSocket message on ${this.id}:`, error);\n\n // Send error back to client\n await this.send({\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n }\n}\n","import type { Static } from \"alepha\";\nimport type { ChannelDescriptor, TWSObject } from \"../descriptors/$channel.ts\";\n\n/**\n * WebSocket connection interface\n */\nexport interface WebSocketConnection {\n /**\n * Unique connection ID\n */\n id: string;\n\n /**\n * User ID (if authenticated and security module is used)\n */\n userId?: string;\n\n /**\n * Room IDs that this connection is currently in\n */\n roomIds: string[];\n\n /**\n * Send a message to this connection\n */\n send(message: any): Promise<void>;\n\n /**\n * Close this connection\n */\n close(code?: number, reason?: string): Promise<void>;\n\n /**\n * Connection metadata (custom data)\n */\n metadata?: Record<string, any>;\n\n /**\n * Connection state\n */\n readyState: WebSocketState;\n}\n\n/**\n * WebSocket state enum\n */\nexport enum WebSocketState {\n CONNECTING = 0,\n OPEN = 1,\n CLOSING = 2,\n CLOSED = 3,\n}\n\n/**\n * WebSocket endpoint configuration (server-side)\n */\nexport interface WebSocketDescriptorOptions<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * Channel definition with schema and path\n */\n channel: ChannelDescriptor<TClient, TServer>;\n\n /**\n * Handler for incoming messages from clients\n */\n handler: WebSocketHandler<TClient, TServer>;\n\n /**\n * Optional connection handler (called when a client connects)\n */\n onConnect?: (params: {\n /**\n * Unique connection ID of the client\n */\n connectionId: string;\n\n /**\n * User ID of the connected client (if authenticated and security module is used)\n */\n userId?: string;\n\n /**\n * Room IDs that the client is connecting to\n */\n roomIds: string[];\n }) => Promise<void> | void;\n\n /**\n * Optional disconnection handler (called when a client disconnects)\n */\n onDisconnect?: (params: {\n /**\n * Unique connection ID of the client\n */\n connectionId: string;\n\n /**\n * User ID of the connected client (if authenticated and security module is used)\n */\n userId?: string;\n\n /**\n * Room IDs that the client was connected to\n */\n roomIds: string[];\n }) => Promise<void> | void;\n\n /**\n * Change WebSocket server provider (for custom implementations)\n */\n provider?: any;\n\n /**\n * Whether to enforce security (authentication, authorization) on this WebSocket endpoint\n * Requires alepha/security integration\n */\n secure?: boolean;\n\n /**\n * Limit number of connections per user (if authenticated)\n */\n maxConnectionsPerUser?: number;\n}\n\n/**\n * WebSocket message handler\n */\nexport type WebSocketHandler<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> = (\n context: WebSocketHandlerContext<TClient, TServer>,\n) => Promise<void> | void;\n\n/**\n * WebSocket handler context\n */\nexport interface WebSocketHandlerContext<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * Unique connection ID of the client\n */\n connectionId: string;\n\n /**\n * User ID of the connected client (if authenticated and security module is used)\n */\n userId?: string;\n\n /**\n * Room ID that the client sent the message from\n */\n roomId: string;\n\n /**\n * The parsed and validated message from the client\n */\n message: Static<TServer>;\n\n /**\n * Reply function tailored to current context (connectionId, roomId)\n */\n reply: (options: {\n /**\n * Message to send\n */\n message: Static<TClient>;\n\n /**\n * Optional: specify room ID to send to (defaults to sender's room ID)\n */\n roomId?: string;\n\n /**\n * Optional: exclude the sender connection from receiving the message\n * Will populate exceptConnectionIds with sender connection ID behind the scenes\n */\n exceptSelf?: boolean;\n\n /**\n * Optional: exclude specific connection IDs from receiving the message\n */\n exceptConnectionIds?: string[];\n\n /**\n * Optional: exclude specific user IDs from receiving the message\n * Requires alepha/security integration\n */\n exceptUserIds?: string[];\n }) => Promise<void>;\n}\n\n/**\n * Emit options for sending messages from the server\n */\nexport interface EmitOptions<TClient extends TWSObject> {\n /**\n * Message to send to clients\n */\n message: Static<TClient>;\n\n /**\n * Room ID to send the message to\n */\n roomId?: string;\n\n /**\n * Room IDs to send the message to\n */\n roomIds?: string[];\n\n /**\n * User ID to send the message to (if authenticated)\n */\n userId?: string;\n\n /**\n * User IDs to send the message to (if authenticated)\n */\n userIds?: string[];\n\n /**\n * Connection ID to send the message to\n */\n connectionId?: string;\n\n /**\n * Connection IDs to send the message to\n */\n connectionIds?: string[];\n\n /**\n * Optional: exclude specific connection IDs from receiving the message\n */\n exceptConnectionIds?: string[];\n\n /**\n * Optional: exclude specific user IDs from receiving the message\n * Requires alepha/security integration\n */\n exceptUserIds?: string[];\n}\n","import {\n $env,\n $inject,\n Alepha,\n AlephaError,\n type Static,\n TypeBoxValue,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { ChannelDescriptor, TWSObject } from \"../descriptors/$channel.ts\";\n\nconst envSchema = t.object({\n WEBSOCKET_URL: t.text({\n default: \"\",\n description:\n \"WebSocket server URL (e.g., ws://localhost:3001). Leave empty to auto-detect.\",\n }),\n WEBSOCKET_RECONNECT_INTERVAL: t.integer({\n default: 3000,\n description: \"Reconnection interval in milliseconds\",\n }),\n WEBSOCKET_MAX_RECONNECT_ATTEMPTS: t.integer({\n default: 10,\n description:\n \"Maximum number of reconnection attempts. Set to -1 for infinite.\",\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n/**\n * Room subscription\n */\ninterface RoomSubscription<TClient extends TWSObject> {\n roomId: string;\n handler: (message: Static<TClient>) => void;\n}\n\n/**\n * WebSocket channel connection\n *\n * Manages a single WebSocket connection to a channel with multiple room subscriptions.\n * One connection can handle multiple rooms on the same channel.\n */\nexport class WebSocketChannelConnection<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected ws?: WebSocket;\n protected reconnectAttempts = 0;\n protected reconnectTimer?: number;\n protected messageQueue: Array<{ roomId: string; message: Static<TServer> }> =\n [];\n\n // Room subscriptions: Map<roomId, handler>\n protected subscriptions = new Map<\n string,\n (message: Static<TClient>) => void\n >();\n\n // Connection state\n public isConnected = false;\n public isConnecting = false;\n public isError = false;\n public error?: Error;\n\n // Connection callbacks\n protected onConnectCallbacks = new Set<() => void>();\n protected onDisconnectCallbacks = new Set<() => void>();\n protected onErrorCallbacks = new Set<(error: Error) => void>();\n\n constructor(\n protected readonly channel: ChannelDescriptor<TClient, TServer>,\n protected readonly options: {\n url?: string;\n autoReconnect?: boolean;\n reconnectInterval?: number;\n maxReconnectAttempts?: number;\n },\n protected readonly env: Static<typeof envSchema>,\n ) {}\n\n /**\n * Build WebSocket URL\n */\n protected buildUrl(): string {\n this.log.trace(\"Building WebSocket URL\", {\n hasCustomUrl: !!this.options.url,\n channelPath: this.channel.options.path,\n });\n\n if (this.options.url) {\n this.log.debug(\"Using custom WebSocket URL\", { url: this.options.url });\n return this.options.url;\n }\n\n // Auto-detect URL from current location (browser only)\n if (typeof window !== \"undefined\") {\n const protocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const host = window.location.host;\n const path = this.channel.options.path;\n // Send all room IDs as query params\n const roomIds = Array.from(this.subscriptions.keys());\n const roomParam =\n roomIds.length > 0 ? `?roomIds=${roomIds.join(\",\")}` : \"\";\n const url = `${protocol}//${host}${path}${roomParam}`;\n this.log.debug(\"Auto-detected WebSocket URL\", { url, roomIds });\n return url;\n }\n\n // Fallback to env URL\n const url = `${this.env.WEBSOCKET_URL}${this.channel.options.path}`;\n this.log.debug(\"Using env WebSocket URL\", { url });\n return url;\n }\n\n /**\n * Subscribe to a room on this channel\n */\n public subscribe(\n roomId: string,\n handler: (message: Static<TClient>) => void,\n callbacks?: {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n },\n ): () => void {\n this.log.debug(\"Subscribing to room\", {\n roomId,\n channelPath: this.channel.options.path,\n existingSubscriptions: this.subscriptions.size,\n });\n\n // Add subscription\n this.subscriptions.set(roomId, handler);\n\n // Add callbacks\n if (callbacks?.onConnect) this.onConnectCallbacks.add(callbacks.onConnect);\n if (callbacks?.onDisconnect)\n this.onDisconnectCallbacks.add(callbacks.onDisconnect);\n if (callbacks?.onError) this.onErrorCallbacks.add(callbacks.onError);\n\n // Connect if not already connected\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n this.log.trace(\"No active connection, initiating connect\");\n this.connect().catch((error) => {\n this.log.error(\"Failed to connect:\", error);\n });\n } else {\n this.log.trace(\"Already connected, reusing existing connection\");\n }\n\n // Return unsubscribe function\n return () => {\n this.log.debug(\"Unsubscribing from room\", { roomId });\n this.subscriptions.delete(roomId);\n if (callbacks?.onConnect)\n this.onConnectCallbacks.delete(callbacks.onConnect);\n if (callbacks?.onDisconnect)\n this.onDisconnectCallbacks.delete(callbacks.onDisconnect);\n if (callbacks?.onError) this.onErrorCallbacks.delete(callbacks.onError);\n\n // Disconnect if no more subscriptions\n if (this.subscriptions.size === 0) {\n this.log.debug(\"No more subscriptions, disconnecting\");\n this.disconnect();\n }\n };\n }\n\n /**\n * Connect to WebSocket server\n */\n protected async connect(): Promise<void> {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.log.trace(\"Already connected, skipping connect\");\n return;\n }\n\n this.isConnecting = true;\n this.isError = false;\n this.error = undefined;\n\n const url = this.buildUrl();\n this.log.info(\"Connecting to WebSocket server\", { url });\n\n return new Promise((resolve, reject) => {\n try {\n const ws = new WebSocket(url);\n this.ws = ws;\n\n ws.onopen = () => {\n this.isConnected = true;\n this.isConnecting = false;\n this.isError = false;\n this.error = undefined;\n this.reconnectAttempts = 0;\n\n this.log.info(\"WebSocket connected\", {\n channelPath: this.channel.options.path,\n rooms: Array.from(this.subscriptions.keys()),\n });\n\n // Flush queued messages\n if (this.messageQueue.length > 0) {\n this.log.debug(\"Flushing queued messages\", {\n count: this.messageQueue.length,\n });\n }\n while (this.messageQueue.length > 0) {\n const msg = this.messageQueue.shift();\n if (msg) {\n this.log.trace(\"Sending queued message\", { roomId: msg.roomId });\n ws.send(\n JSON.stringify({\n roomId: msg.roomId,\n message: msg.message,\n }),\n );\n }\n }\n\n // Call all connect callbacks\n for (const callback of this.onConnectCallbacks) {\n callback();\n }\n\n resolve();\n };\n\n ws.onmessage = (event) => {\n this.log.trace(\"Message received\", {\n dataLength: event.data?.length,\n });\n this.handleMessage(event.data);\n };\n\n ws.onclose = (event) => {\n this.isConnected = false;\n this.isConnecting = false;\n this.ws = undefined;\n\n this.log.info(\"WebSocket disconnected\", {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean,\n });\n\n // Call all disconnect callbacks\n for (const callback of this.onDisconnectCallbacks) {\n callback();\n }\n\n // Attempt reconnection\n if (this.options.autoReconnect !== false) {\n this.scheduleReconnect();\n }\n };\n\n ws.onerror = () => {\n const err = new Error(\"WebSocket connection error\");\n this.isError = true;\n this.error = err;\n this.isConnecting = false;\n\n this.log.error(\"WebSocket error\", { url });\n\n // Call all error callbacks\n for (const callback of this.onErrorCallbacks) {\n callback(err);\n }\n\n reject(err);\n };\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Connection failed\");\n this.isError = true;\n this.error = error;\n this.isConnecting = false;\n\n this.log.error(\"Failed to create WebSocket\", { error: error.message });\n\n // Call all error callbacks\n for (const callback of this.onErrorCallbacks) {\n callback(error);\n }\n\n reject(error);\n }\n });\n }\n\n /**\n * Handle incoming message\n */\n protected handleMessage(data: string): void {\n try {\n const parsed = JSON.parse(data);\n this.log.trace(\"Parsed incoming message\", { parsed });\n\n // Validate incoming message against schema\n const inSchema = this.channel.options.schema.in;\n this.alepha.codec.validate(inSchema, parsed);\n\n this.log.debug(\"Dispatching message to handlers\", {\n handlerCount: this.subscriptions.size,\n });\n\n // Extract roomId from message if present (server should send it back)\n // For now, broadcast to all subscribed rooms\n // TODO: Server should include roomId in response\n for (const handler of this.subscriptions.values()) {\n handler(parsed as Static<TClient>);\n }\n } catch (err) {\n this.log.error(\"Error handling message:\", err);\n }\n }\n\n /**\n * Send message to a specific room\n */\n public async send(roomId: string, message: Static<TServer>): Promise<void> {\n this.log.trace(\"Sending message\", { roomId, message });\n\n // Validate outgoing message against schema\n const outSchema = this.channel.options.schema.out;\n if (!TypeBoxValue.Check(outSchema, message)) {\n const errors = Array.from(TypeBoxValue.Errors(outSchema, message));\n this.log.warn(\"Message validation failed\", { errors });\n throw new Error(\n `Message validation failed: ${errors.map((e) => e.message).join(\", \")}`,\n );\n }\n\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n // Queue message\n this.log.debug(\"Connection not ready, queuing message\", {\n roomId,\n queueSize: this.messageQueue.length + 1,\n });\n this.messageQueue.push({ roomId, message });\n return;\n }\n\n this.log.debug(\"Sending message to server\", { roomId });\n this.ws.send(\n JSON.stringify({\n roomId,\n message,\n }),\n );\n }\n\n /**\n * Schedule reconnection\n */\n protected scheduleReconnect(): void {\n const maxAttempts =\n this.options.maxReconnectAttempts ??\n this.env.WEBSOCKET_MAX_RECONNECT_ATTEMPTS ??\n 10;\n const reconnectInterval =\n this.options.reconnectInterval ??\n this.env.WEBSOCKET_RECONNECT_INTERVAL ??\n 3000;\n\n if (maxAttempts !== -1 && this.reconnectAttempts >= maxAttempts) {\n this.log.warn(\"Max reconnection attempts reached\", {\n attempts: this.reconnectAttempts,\n maxAttempts,\n });\n return;\n }\n\n this.reconnectAttempts++;\n\n this.log.debug(\"Scheduling reconnection\", {\n attempt: this.reconnectAttempts,\n maxAttempts,\n intervalMs: reconnectInterval,\n });\n\n this.reconnectTimer = window.setTimeout(() => {\n this.log.info(\"Reconnecting...\", {\n attempt: this.reconnectAttempts,\n maxAttempts,\n });\n this.connect().catch((error) => {\n this.log.error(\"Reconnection failed:\", error);\n });\n }, reconnectInterval);\n }\n\n /**\n * Disconnect from server\n */\n public disconnect(): void {\n this.log.debug(\"Disconnecting\", {\n hasTimer: !!this.reconnectTimer,\n hasConnection: !!this.ws,\n });\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n\n if (this.ws) {\n this.ws.close();\n this.ws = undefined;\n }\n\n this.isConnected = false;\n this.isConnecting = false;\n\n this.log.info(\"Disconnected\");\n }\n\n /**\n * Reconnect manually\n */\n public reconnect(): void {\n this.log.info(\"Manual reconnect requested\");\n this.disconnect();\n this.connect().catch((error) => {\n this.log.error(\"Manual reconnection failed:\", error);\n });\n }\n\n /**\n * Check if subscribed to a room\n */\n public hasRoom(roomId: string): boolean {\n return this.subscriptions.has(roomId);\n }\n\n /**\n * Get all subscribed rooms\n */\n public getRooms(): string[] {\n return Array.from(this.subscriptions.keys());\n }\n}\n\n/**\n * WebSocket Client Service\n *\n * Manages WebSocket connections from the client side (browser).\n * One connection per channel, multiple rooms per connection.\n */\nexport class WebSocketClient {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(envSchema);\n\n // Map<channelPath, connection>\n protected connections = new Map<\n string,\n WebSocketChannelConnection<any, any>\n >();\n\n /**\n * Subscribe to a room on a channel\n */\n public subscribe<TClient extends TWSObject, TServer extends TWSObject>(\n roomId: string,\n channel: ChannelDescriptor<TClient, TServer>,\n handler: (message: Static<TClient>) => void,\n options: {\n url?: string;\n autoReconnect?: boolean;\n reconnectInterval?: number;\n maxReconnectAttempts?: number;\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n } = {},\n ): () => void {\n const channelPath = channel.options.path;\n\n this.log.debug(\"WebSocketClient.subscribe\", {\n roomId,\n channelPath,\n existingConnections: this.connections.size,\n });\n\n // Get or create connection for this channel\n let connection = this.connections.get(\n channelPath,\n ) as WebSocketChannelConnection<TClient, TServer>;\n\n if (!connection) {\n this.log.debug(\"Creating new connection for channel\", { channelPath });\n connection = this.alepha.inject(WebSocketChannelConnection, {\n lifetime: \"transient\",\n args: [\n channel,\n {\n url: options.url,\n autoReconnect: options.autoReconnect,\n reconnectInterval: options.reconnectInterval,\n maxReconnectAttempts: options.maxReconnectAttempts,\n },\n this.env,\n ],\n }) as WebSocketChannelConnection<any, any>;\n\n this.connections.set(channelPath, connection);\n } else {\n this.log.trace(\"Reusing existing connection for channel\", {\n channelPath,\n });\n }\n\n // Subscribe to the room on this connection\n const unsubscribe = connection.subscribe(roomId, handler, {\n onConnect: options.onConnect,\n onDisconnect: options.onDisconnect,\n onError: options.onError,\n });\n\n // Return unsubscribe function\n return () => {\n this.log.debug(\"WebSocketClient.unsubscribe\", { roomId, channelPath });\n unsubscribe();\n\n // Clean up connection if no more rooms\n if (connection.getRooms().length === 0) {\n this.log.debug(\"Removing connection for channel (no more rooms)\", {\n channelPath,\n });\n this.connections.delete(channelPath);\n }\n };\n }\n\n /**\n * Send message to a room on a channel\n */\n public async send<TClient extends TWSObject, TServer extends TWSObject>(\n roomId: string,\n channel: ChannelDescriptor<TClient, TServer>,\n message: Static<TServer>,\n ): Promise<void> {\n const channelPath = channel.options.path;\n\n this.log.trace(\"WebSocketClient.send\", { roomId, channelPath });\n\n const connection = this.connections.get(\n channelPath,\n ) as WebSocketChannelConnection<TClient, TServer>;\n\n if (!connection) {\n this.log.warn(\"Attempted to send on unsubscribed channel\", {\n channelPath,\n });\n throw new AlephaError(\n `Not subscribed to channel ${channelPath}. Subscribe first before sending messages.`,\n );\n }\n\n await connection.send(roomId, message);\n }\n\n /**\n * Get connection for a channel\n */\n public getConnection<TClient extends TWSObject, TServer extends TWSObject>(\n channel: ChannelDescriptor<TClient, TServer>,\n ): WebSocketChannelConnection<TClient, TServer> | undefined {\n const channelPath = channel.options.path;\n const connection = this.connections.get(channelPath) as\n | WebSocketChannelConnection<TClient, TServer>\n | undefined;\n\n this.log.trace(\"WebSocketClient.getConnection\", {\n channelPath,\n found: !!connection,\n });\n\n return connection;\n }\n\n /**\n * Disconnect all connections\n */\n public disconnectAll(): void {\n this.log.info(\"Disconnecting all connections\", {\n count: this.connections.size,\n });\n\n for (const connection of this.connections.values()) {\n connection.disconnect();\n }\n this.connections.clear();\n\n this.log.debug(\"All connections disconnected\");\n }\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { AlephaTopic } from \"alepha/topic\";\nimport { $channel } from \"./descriptors/$channel.ts\";\nimport { $websocket } from \"./descriptors/$websocket.ts\";\nimport { NodeWebSocketServerProvider } from \"./providers/NodeWebSocketServerProvider.ts\";\nimport { WebSocketServerProvider } from \"./providers/WebSocketServerProvider.ts\";\nimport { RoomManager } from \"./services/RoomManager.ts\";\nimport { WebSocketTopicService } from \"./services/WebSocketTopicService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n /**\n * Fires when a WebSocket connection is established\n */\n \"websocket:connect\": {\n connectionId: string;\n path: string;\n };\n\n /**\n * Fires when a WebSocket connection is closed\n */\n \"websocket:disconnect\": {\n connectionId: string;\n path: string;\n code?: number;\n reason?: string;\n };\n\n /**\n * Fires when a WebSocket message is received\n */\n \"websocket:message\": {\n connectionId: string;\n path: string;\n message: any;\n };\n\n /**\n * Fires when a WebSocket error occurs\n */\n \"websocket:error\": {\n connectionId: string;\n path: string;\n error: Error;\n };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./index.shared.ts\";\nexport * from \"./providers/NodeWebSocketServerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides real-time bidirectional communication using WebSockets.\n *\n * The WebSockets module enables building real-time applications using the `$websocket` descriptor\n * on class properties. It provides automatic connection management, message routing, type-safe\n * message handling, and seamless integration with other Alepha modules.\n *\n * On the server side (Node.js), it uses the 'ws' library to create a WebSocket server.\n * On the client side (browser), it uses the native WebSocket API.\n *\n * @see {@link $websocket}\n * @module alepha.websockets\n */\nexport const AlephaWebSocket = $module({\n name: \"alepha.websocket\",\n descriptors: [$channel, $websocket],\n services: [\n WebSocketServerProvider,\n NodeWebSocketServerProvider,\n RoomManager,\n WebSocketTopicService,\n ],\n register: (alepha: Alepha) => {\n alepha.with(AlephaServer);\n alepha.with(AlephaTopic);\n\n alepha.with({\n provide: WebSocketServerProvider,\n use: NodeWebSocketServerProvider,\n });\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["code?: number","envSchema","userId: string | undefined","roomIds: string[]","id: string","ws: WebSocket","provider: NodeWebSocketServerProvider","endpoint: WebSocketPrimitiveOptions<any, any>","WebSocket","parsed: any","context: WebSocketHandlerContext<any, any>","channel: ChannelPrimitive<TClient, TServer>","options: {\n url?: string;\n autoReconnect?: boolean;\n reconnectInterval?: number;\n maxReconnectAttempts?: number;\n }","env: Static<typeof envSchema>","url"],"sources":["../../src/websocket/primitives/$channel.ts","../../src/websocket/providers/WebSocketServerProvider.ts","../../src/websocket/primitives/$websocket.ts","../../src/websocket/errors/WebSocketError.ts","../../src/websocket/services/RoomManager.ts","../../src/websocket/services/WebSocketTopicService.ts","../../src/websocket/providers/NodeWebSocketServerProvider.ts","../../src/websocket/interfaces/WebSocketInterfaces.ts","../../src/websocket/services/WebSocketClient.ts","../../src/websocket/index.ts"],"sourcesContent":["import {\n createPrimitive,\n KIND,\n Primitive,\n type TObject,\n type TString,\n type TUnion,\n} from \"alepha\";\n\nexport type TWSObject = TObject | TUnion;\n\n/**\n * Channel primitive options\n */\nexport interface ChannelPrimitiveOptions<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * WebSocket endpoint path (e.g., \"/ws/chat\")\n */\n path: string;\n\n /**\n * Optional description for documentation\n */\n description?: string;\n\n /**\n * Message schemas for bidirectional communication\n */\n schema: {\n /**\n * Optional room ID schema validation\n * Default: t.text() (any string)\n * Can be enforced at application level: t.uuid(), t.regex(/^[a-f0-9\\-]{36}$/)\n */\n roomId?: TString;\n\n /**\n * Messages from server to client\n * This is what clients will receive\n */\n in: TClient;\n\n /**\n * Messages from client to server\n * This is what the server will receive\n */\n out: TServer;\n };\n}\n\n/**\n * Defines a WebSocket channel with specified client and server message schemas.\n *\n * Channels must be defined as class properties to be registered in the Alepha context.\n * They define the \"vocabulary\" for communication - the schema for messages flowing\n * in both directions (server→client and client→server).\n *\n * @example Server-side with $websocket\n * ```typescript\n * class ChatController {\n * // Channel must be defined inside a class\n * chatChannel = $channel({\n * path: \"/ws/chat\",\n * description: \"Real-time chat channel\",\n * schema: {\n * // Server → Client messages\n * in: t.union([\n * t.object({\n * type: t.const(\"append\"),\n * content: t.text(),\n * username: t.text()\n * }),\n * t.object({\n * type: t.const(\"system\"),\n * message: t.text()\n * })\n * ]),\n * // Client → Server messages\n * out: t.object({\n * content: t.text()\n * })\n * }\n * });\n *\n * chat = $websocket({\n * channel: this.chatChannel,\n * handler: async ({ message, reply }) => {\n * await reply({\n * message: { type: \"append\", content: message.content, username: \"user\" }\n * });\n * }\n * });\n * }\n * ```\n *\n * @example Browser-side with useRoom\n * ```typescript\n * // Define channel in a class for browser context\n * class ChatClient {\n * chatChannel = $channel({\n * path: \"/ws/chat\",\n * schema: { in: inSchema, out: outSchema }\n * });\n * }\n *\n * // Use in React component\n * function Chat() {\n * const client = useInject(ChatClient);\n * const chat = useRoom({ roomId: \"lobby\", channel: client.chatChannel, handler: ... }, []);\n * }\n * ```\n */\nexport const $channel = <TClient extends TWSObject, TServer extends TWSObject>(\n options: ChannelPrimitiveOptions<TClient, TServer>,\n): ChannelPrimitive<TClient, TServer> => {\n return createPrimitive(ChannelPrimitive<TClient, TServer>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ChannelPrimitive<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> extends Primitive<ChannelPrimitiveOptions<TClient, TServer>> {\n // Channels are just schema definitions - no initialization logic needed\n}\n\n$channel[KIND] = ChannelPrimitive;\n","import type {\n EmitOptions,\n WebSocketConnection,\n WebSocketPrimitiveOptions,\n} from \"../interfaces/WebSocketInterfaces.ts\";\nimport type { TWSObject } from \"../primitives/$channel.ts\";\n\n/**\n * Abstract WebSocket server provider\n *\n * This class provides the base interface that must be implemented by\n * platform-specific providers (Node.js, Browser, etc.)\n */\nexport abstract class WebSocketServerProvider {\n /**\n * Register a WebSocket endpoint with its channel configuration\n */\n abstract registerEndpoint<\n TClient extends TWSObject,\n TServer extends TWSObject,\n >(config: WebSocketPrimitiveOptions<TClient, TServer>): void;\n\n /**\n * Emit a message to clients based on targeting criteria\n *\n * This method distributes messages across all server instances via pub/sub.\n */\n abstract emit<TClient extends TWSObject>(\n channelPath: string,\n options: EmitOptions<TClient>,\n ): Promise<void>;\n\n /**\n * Get all active connections (local to this server instance)\n */\n abstract getConnections(): WebSocketConnection[];\n\n /**\n * Get connections in a specific room (local to this server instance)\n */\n abstract getRoomConnections(roomId: string): WebSocketConnection[];\n\n /**\n * Get connections for a specific user (local to this server instance)\n */\n abstract getUserConnections(userId: string): WebSocketConnection[];\n\n /**\n * Close a specific connection\n */\n abstract closeConnection(\n connectionId: string,\n code?: number,\n reason?: string,\n ): Promise<void>;\n}\n","import { $inject, createPrimitive, KIND, Primitive } from \"alepha\";\nimport type {\n EmitOptions,\n WebSocketPrimitiveOptions,\n} from \"../interfaces/WebSocketInterfaces.ts\";\nimport { WebSocketServerProvider } from \"../providers/WebSocketServerProvider.ts\";\nimport type { TWSObject } from \"./$channel.ts\";\n\n/**\n * Defines a WebSocket server endpoint for a specific channel.\n *\n * Server-side only. Creates a WebSocket endpoint that:\n * - Accepts connections from clients\n * - Validates incoming messages against the channel schema\n * - Provides room-based messaging\n * - Integrates with alepha/security for authentication (optional)\n * - Supports horizontal scaling via alepha/topic\n *\n * @example\n * ```typescript\n * class ChatController {\n * chat = $websocket({\n * channel: chatChannel,\n * handler: async ({ connectionId, userId, roomId, message, reply }) => {\n * // Broadcast to all in room except sender\n * await reply({\n * message: {\n * type: \"append\",\n * username: userId,\n * content: message.content\n * },\n * exceptSelf: true\n * });\n * }\n * });\n *\n * async broadcastAnnouncement(roomId: string, text: string) {\n * await this.chat.emit({\n * roomId,\n * message: {\n * type: \"append\",\n * username: \"System\",\n * content: text\n * }\n * });\n * }\n * }\n * ```\n */\nexport const $websocket = <\n TClient extends TWSObject,\n TServer extends TWSObject,\n>(\n options: WebSocketPrimitiveOptions<TClient, TServer>,\n): WebSocketPrimitive<TClient, TServer> => {\n return createPrimitive(WebSocketPrimitive<TClient, TServer>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class WebSocketPrimitive<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> extends Primitive<WebSocketPrimitiveOptions<TClient, TServer>> {\n protected readonly webSocketServerProvider = $inject(WebSocketServerProvider);\n\n protected onInit() {\n this.webSocketServerProvider.registerEndpoint(this.options);\n }\n\n /**\n * Emit message to clients\n *\n * Send messages from the server to connected clients based on targeting criteria.\n * Messages are distributed across all server instances via pub/sub.\n *\n * @example\n * ```typescript\n * // Send to specific room\n * await websocket.emit({\n * roomId: \"room-123\",\n * message: { type: \"update\", data: {...} }\n * });\n *\n * // Send to specific user (all their connections)\n * await websocket.emit({\n * userId: \"user-456\",\n * message: { type: \"notification\", text: \"Hello!\" }\n * });\n *\n * // Send to multiple rooms, except certain users\n * await websocket.emit({\n * roomIds: [\"room-1\", \"room-2\"],\n * exceptUserIds: [\"user-123\"],\n * message: { type: \"broadcast\", content: \"System announcement\" }\n * });\n * ```\n */\n public async emit(options: EmitOptions<TClient>): Promise<void> {\n await this.webSocketServerProvider.emit(\n this.options.channel.options.path,\n options,\n );\n }\n}\n\n$websocket[KIND] = WebSocketPrimitive;\n","/**\n * Base WebSocket error class\n */\nexport class WebSocketError extends Error {\n constructor(\n message: string,\n public readonly code?: number,\n ) {\n super(message);\n this.name = \"WebSocketError\";\n }\n}\n\n/**\n * Error thrown when WebSocket connection fails\n */\nexport class WebSocketConnectionError extends WebSocketError {\n constructor(message: string, code?: number) {\n super(message, code);\n this.name = \"WebSocketConnectionError\";\n }\n}\n\n/**\n * Error thrown when WebSocket message validation fails\n */\nexport class WebSocketValidationError extends WebSocketError {\n constructor(message: string) {\n super(message);\n this.name = \"WebSocketValidationError\";\n }\n}\n","import { $logger } from \"alepha/logger\";\n\n/**\n * Manages WebSocket room memberships\n *\n * Rooms are logical groupings of connections. A connection can be in multiple rooms,\n * and messages can be targeted to specific rooms.\n */\nexport class RoomManager {\n protected readonly log = $logger();\n\n /**\n * Maps roomId → Set<connectionId>\n */\n protected readonly rooms = new Map<string, Set<string>>();\n\n /**\n * Maps connectionId → Set<roomId>\n * Inverse index for fast lookup of connection's rooms\n */\n protected readonly connectionRooms = new Map<string, Set<string>>();\n\n /**\n * Join a connection to one or more rooms\n */\n public joinRooms(connectionId: string, roomIds: string[]): void {\n for (const roomId of roomIds) {\n this.joinRoom(connectionId, roomId);\n }\n }\n\n /**\n * Join a connection to a room\n */\n public joinRoom(connectionId: string, roomId: string): void {\n // Add to room\n let room = this.rooms.get(roomId);\n if (!room) {\n room = new Set();\n this.rooms.set(roomId, room);\n }\n room.add(connectionId);\n\n // Update inverse index\n let connRooms = this.connectionRooms.get(connectionId);\n if (!connRooms) {\n connRooms = new Set();\n this.connectionRooms.set(connectionId, connRooms);\n }\n connRooms.add(roomId);\n\n this.log.debug(`Connection ${connectionId} joined room ${roomId}`);\n }\n\n /**\n * Leave a connection from a room\n */\n public leaveRoom(connectionId: string, roomId: string): void {\n // Remove from room\n const room = this.rooms.get(roomId);\n if (room) {\n room.delete(connectionId);\n if (room.size === 0) {\n this.rooms.delete(roomId);\n }\n }\n\n // Update inverse index\n const connRooms = this.connectionRooms.get(connectionId);\n if (connRooms) {\n connRooms.delete(roomId);\n if (connRooms.size === 0) {\n this.connectionRooms.delete(connectionId);\n }\n }\n\n this.log.debug(`Connection ${connectionId} left room ${roomId}`);\n }\n\n /**\n * Remove a connection from all rooms\n */\n public leaveAllRooms(connectionId: string): void {\n const connRooms = this.connectionRooms.get(connectionId);\n if (!connRooms) {\n return;\n }\n\n for (const roomId of connRooms) {\n const room = this.rooms.get(roomId);\n if (room) {\n room.delete(connectionId);\n if (room.size === 0) {\n this.rooms.delete(roomId);\n }\n }\n }\n\n this.connectionRooms.delete(connectionId);\n this.log.debug(`Connection ${connectionId} left all rooms`);\n }\n\n /**\n * Get all connection IDs in a room\n */\n public getRoomConnections(roomId: string): string[] {\n const room = this.rooms.get(roomId);\n return room ? Array.from(room) : [];\n }\n\n /**\n * Get all room IDs for a connection\n */\n public getConnectionRooms(connectionId: string): string[] {\n const connRooms = this.connectionRooms.get(connectionId);\n return connRooms ? Array.from(connRooms) : [];\n }\n\n /**\n * Check if a connection is in a room\n */\n public isInRoom(connectionId: string, roomId: string): boolean {\n const connRooms = this.connectionRooms.get(connectionId);\n return connRooms ? connRooms.has(roomId) : false;\n }\n\n /**\n * Get all active rooms\n */\n public getAllRooms(): string[] {\n return Array.from(this.rooms.keys());\n }\n\n /**\n * Get total number of connections across all rooms\n */\n public getTotalConnections(): number {\n return this.connectionRooms.size;\n }\n\n /**\n * Get room statistics\n */\n public getStats(): {\n totalRooms: number;\n totalConnections: number;\n roomSizes: Map<string, number>;\n } {\n const roomSizes = new Map<string, number>();\n for (const [roomId, connections] of this.rooms) {\n roomSizes.set(roomId, connections.size);\n }\n\n return {\n totalRooms: this.rooms.size,\n totalConnections: this.connectionRooms.size,\n roomSizes,\n };\n }\n}\n","import { type Static, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { $topic } from \"alepha/topic\";\n\n/**\n * WebSocket message distribution event\n */\nconst webSocketMessageSchema = {\n payload: t.object({\n /**\n * Channel path (e.g., \"/ws/chat\")\n */\n channelPath: t.text(),\n\n /**\n * Target room ID(s)\n */\n roomIds: t.optional(t.array(t.text())),\n\n /**\n * Target user ID(s)\n */\n userIds: t.optional(t.array(t.text())),\n\n /**\n * Target connection ID(s)\n */\n connectionIds: t.optional(t.array(t.text())),\n\n /**\n * Exclude connection ID(s) from receiving the message\n */\n exceptConnectionIds: t.optional(t.array(t.text())),\n\n /**\n * Exclude user ID(s) from receiving the message\n */\n exceptUserIds: t.optional(t.array(t.text())),\n\n /**\n * The message payload to send\n */\n message: t.any(),\n }),\n};\n\n/**\n * WebSocket Topic Service\n *\n * Manages pub/sub messaging for WebSocket connections across multiple server instances.\n * Uses alepha/topic for cross-instance message distribution, enabling horizontal scaling.\n *\n * When a WebSocket message needs to be sent:\n * 1. Server instance A publishes to the topic\n * 2. All server instances (A, B, C, etc.) receive the message\n * 3. Each instance sends to its local connections that match the criteria\n *\n * This enables:\n * - Multiple server instances handling WebSocket connections\n * - Redis-backed message distribution (with alepha/topic/redis)\n * - Horizontal scaling without losing messages\n */\nexport class WebSocketTopicService {\n protected readonly log = $logger();\n\n /**\n * Handler function to be called when a message is received from the topic\n * This is set by the WebSocket provider during initialization\n */\n public messageHandler?: (\n event: Static<(typeof webSocketMessageSchema)[\"payload\"]>,\n ) => Promise<void>;\n\n /**\n * Topic for distributing WebSocket messages across server instances\n */\n public readonly topic = $topic({\n name: \"websocket:broadcast\",\n description:\n \"Distributes WebSocket messages across server instances for horizontal scaling\",\n schema: webSocketMessageSchema,\n handler: async (message) => {\n if (this.messageHandler) {\n await this.messageHandler(message.payload);\n }\n },\n });\n\n /**\n * Publish a message to be distributed across all server instances\n */\n public async publish(\n event: Static<(typeof webSocketMessageSchema)[\"payload\"]>,\n ): Promise<void> {\n await this.topic.publish(event);\n }\n\n /**\n * Set the handler for incoming messages\n */\n public setMessageHandler(\n handler: (\n event: Static<(typeof webSocketMessageSchema)[\"payload\"]>,\n ) => Promise<void>,\n ): void {\n this.messageHandler = handler;\n }\n}\n","import type { IncomingMessage } from \"node:http\";\nimport {\n $env,\n $hook,\n $inject,\n Alepha,\n type Static,\n TypeBoxValue,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { WebSocket, WebSocketServer } from \"ws\";\nimport { WebSocketValidationError } from \"../errors/WebSocketError.ts\";\nimport type {\n EmitOptions,\n WebSocketConnection,\n WebSocketHandlerContext,\n WebSocketPrimitiveOptions,\n WebSocketState,\n} from \"../interfaces/WebSocketInterfaces.ts\";\nimport type { TWSObject } from \"../primitives/$channel.ts\";\nimport { RoomManager } from \"../services/RoomManager.ts\";\nimport { WebSocketTopicService } from \"../services/WebSocketTopicService.ts\";\nimport { WebSocketServerProvider } from \"./WebSocketServerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nconst envSchema = t.object({\n WEBSOCKET_PATH: t.text({\n default: \"/ws\",\n description: \"Base path for WebSocket endpoints\",\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class NodeWebSocketServerProvider extends WebSocketServerProvider {\n protected readonly alepha = $inject(Alepha);\n protected readonly roomManager = $inject(RoomManager);\n protected readonly topicService = $inject(WebSocketTopicService);\n protected readonly log = $logger();\n protected readonly env = $env(envSchema);\n\n protected wss?: WebSocketServer;\n protected endpoints = new Map<string, WebSocketPrimitiveOptions<any, any>>();\n protected connections = new Map<string, WebSocketConnection>();\n protected userConnections = new Map<string, Set<string>>(); // userId → Set<connectionId>\n protected nextConnectionId = 1;\n\n // -------------------------------------------------------------------------------------------------------------------\n\n public registerEndpoint<TClient extends TWSObject, TServer extends TWSObject>(\n config: WebSocketPrimitiveOptions<TClient, TServer>,\n ): void {\n const path = config.channel.options.path;\n this.endpoints.set(path, config);\n }\n\n public async emit<TClient extends TWSObject>(\n channelPath: string,\n options: EmitOptions<TClient>,\n ): Promise<void> {\n // Publish to topic so all server instances receive it\n await this.topicService.publish({\n channelPath,\n roomIds: options.roomIds\n ? options.roomIds\n : options.roomId\n ? [options.roomId]\n : undefined,\n userIds: options.userIds\n ? options.userIds\n : options.userId\n ? [options.userId]\n : undefined,\n connectionIds: options.connectionIds\n ? options.connectionIds\n : options.connectionId\n ? [options.connectionId]\n : undefined,\n exceptConnectionIds: options.exceptConnectionIds,\n exceptUserIds: options.exceptUserIds,\n message: options.message,\n });\n }\n\n public getConnections(): WebSocketConnection[] {\n return Array.from(this.connections.values());\n }\n\n public getRoomConnections(roomId: string): WebSocketConnection[] {\n const connectionIds = this.roomManager.getRoomConnections(roomId);\n return connectionIds\n .map((id) => this.connections.get(id))\n .filter((conn): conn is WebSocketConnection => conn !== undefined);\n }\n\n public getUserConnections(userId: string): WebSocketConnection[] {\n const connectionIds = this.userConnections.get(userId);\n if (!connectionIds) {\n return [];\n }\n return Array.from(connectionIds)\n .map((id) => this.connections.get(id))\n .filter((conn): conn is WebSocketConnection => conn !== undefined);\n }\n\n public async closeConnection(\n connectionId: string,\n code?: number,\n reason?: string,\n ): Promise<void> {\n const connection = this.connections.get(connectionId);\n if (!connection) {\n this.log.warn(`Connection not found: ${connectionId}`);\n return;\n }\n await connection.close(code, reason);\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n protected handleUpgrade(\n request: IncomingMessage,\n socket: any,\n head: Buffer,\n ): boolean {\n const url = new URL(request.url || \"/\", \"http://localhost\");\n const path = url.pathname;\n\n const endpoint = this.endpoints.get(path);\n if (!endpoint) {\n // Not our endpoint - in Vite dev mode, let Vite HMR handle it\n // In production, destroy the socket\n if (!this.alepha.isViteDev()) {\n this.log.warn(`No WebSocket endpoint found for path: ${path}`);\n socket.destroy();\n }\n return false;\n }\n\n this.log.debug(`WebSocket upgrade request: ${path}`);\n\n this.wss?.handleUpgrade(request, socket, head, (ws) => {\n this.handleConnection(ws, endpoint, request);\n });\n\n return true;\n }\n\n protected handleConnection<\n TClient extends TWSObject,\n TServer extends TWSObject,\n >(\n ws: WebSocket,\n endpoint: WebSocketPrimitiveOptions<TClient, TServer>,\n request: IncomingMessage,\n ): void {\n const connectionId = `ws-${this.nextConnectionId++}`;\n\n // TODO: Extract userId from security context when alepha/security is available\n const userId: string | undefined = undefined;\n\n // Extract roomIds from query params (e.g., ?roomId=room1&roomId=room2 or ?roomIds=room1,room2)\n const url = new URL(request.url || \"/\", \"http://localhost\");\n const roomIds = this.extractRoomIds(url);\n\n const connection = this.alepha.inject(NodeWebSocketConnection, {\n lifetime: \"transient\",\n args: [connectionId, userId, roomIds, ws, this, endpoint],\n });\n\n this.connections.set(connectionId, connection);\n\n // Track user connections\n if (userId) {\n let userConns = this.userConnections.get(userId);\n if (!userConns) {\n userConns = new Set();\n this.userConnections.set(userId, userConns);\n }\n userConns.add(connectionId);\n\n // Check max connections per user\n if (\n endpoint.maxConnectionsPerUser &&\n userConns.size > endpoint.maxConnectionsPerUser\n ) {\n this.log.warn(\n `User ${userId} exceeded max connections (${endpoint.maxConnectionsPerUser})`,\n );\n ws.close(1008, \"Max connections per user exceeded\");\n return;\n }\n }\n\n // Join rooms\n if (roomIds.length > 0) {\n this.roomManager.joinRooms(connectionId, roomIds);\n }\n\n this.log.info(`WebSocket connection established: ${connectionId}`, {\n path: endpoint.channel.options.path,\n userId,\n roomIds,\n remoteAddress: request.socket.remoteAddress,\n });\n\n // Call onConnect handler if provided\n if (endpoint.onConnect) {\n Promise.resolve(\n endpoint.onConnect({ connectionId, userId, roomIds }),\n ).catch((error) => {\n this.log.error(\"Error in onConnect handler:\", error);\n });\n }\n\n ws.on(\"message\", async (data) => {\n await connection.handleMessage(data);\n });\n\n ws.on(\"close\", (code, reason) => {\n this.log.info(`WebSocket connection closed: ${connectionId}`, {\n code,\n reason: reason.toString(),\n });\n\n // Clean up\n this.connections.delete(connectionId);\n this.roomManager.leaveAllRooms(connectionId);\n\n if (userId) {\n const userConns = this.userConnections.get(userId);\n if (userConns) {\n userConns.delete(connectionId);\n if (userConns.size === 0) {\n this.userConnections.delete(userId);\n }\n }\n }\n\n // Call onDisconnect handler if provided\n if (endpoint.onDisconnect) {\n Promise.resolve(\n endpoint.onDisconnect({ connectionId, userId, roomIds }),\n ).catch((error) => {\n this.log.error(\"Error in onDisconnect handler:\", error);\n });\n }\n });\n\n ws.on(\"error\", (error) => {\n this.log.error(`WebSocket error on ${connectionId}:`, error);\n });\n }\n\n protected extractRoomIds(url: URL): string[] {\n const roomIds: string[] = [];\n\n // Check for roomId parameter (can be multiple)\n const roomIdParams = url.searchParams.getAll(\"roomId\");\n roomIds.push(...roomIdParams);\n\n // Check for roomIds parameter (comma-separated)\n const roomIdsParam = url.searchParams.get(\"roomIds\");\n if (roomIdsParam) {\n roomIds.push(...roomIdsParam.split(\",\").map((id) => id.trim()));\n }\n\n // Default room if none specified\n if (roomIds.length === 0) {\n roomIds.push(\"default\");\n }\n\n return roomIds;\n }\n\n /**\n * Send message to local connections based on targeting criteria\n * This is called by the topic service when a message is received\n */\n protected async sendToLocalConnections(\n channelPath: string,\n message: any,\n criteria: {\n roomIds?: string[];\n userIds?: string[];\n connectionIds?: string[];\n exceptConnectionIds?: string[];\n exceptUserIds?: string[];\n },\n ): Promise<void> {\n const targetConnections = new Set<string>();\n\n // Collect target connections based on criteria\n if (criteria.roomIds) {\n for (const roomId of criteria.roomIds) {\n const roomConns = this.roomManager.getRoomConnections(roomId);\n for (const connId of roomConns) {\n targetConnections.add(connId);\n }\n }\n }\n\n if (criteria.userIds) {\n for (const userId of criteria.userIds) {\n const userConns = this.userConnections.get(userId);\n if (userConns) {\n for (const connId of userConns) {\n targetConnections.add(connId);\n }\n }\n }\n }\n\n if (criteria.connectionIds) {\n for (const connId of criteria.connectionIds) {\n targetConnections.add(connId);\n }\n }\n\n // If no specific targeting, send to all connections on this channel\n if (!criteria.roomIds && !criteria.userIds && !criteria.connectionIds) {\n for (const conn of this.connections.values()) {\n targetConnections.add(conn.id);\n }\n }\n\n // Remove exceptions\n if (criteria.exceptConnectionIds) {\n for (const connId of criteria.exceptConnectionIds) {\n targetConnections.delete(connId);\n }\n }\n\n if (criteria.exceptUserIds) {\n for (const userId of criteria.exceptUserIds) {\n const userConns = this.userConnections.get(userId);\n if (userConns) {\n for (const connId of userConns) {\n targetConnections.delete(connId);\n }\n }\n }\n }\n\n // Send to all target connections\n const serialized = JSON.stringify(message);\n await Promise.all(\n Array.from(targetConnections).map(async (connId) => {\n const conn = this.connections.get(connId);\n if (conn) {\n try {\n await conn.send(serialized);\n } catch (error) {\n this.log.error(`Failed to send to connection ${connId}:`, error);\n }\n }\n }),\n );\n }\n\n // -------------------------------------------------------------------------------------------------------------------\n\n protected readonly start = $hook({\n on: \"start\",\n handler: async () => {\n if (this.alepha.isServerless()) {\n this.log.debug(\"WebSocket server disabled in serverless mode\");\n return;\n }\n\n this.wss = new WebSocketServer({ noServer: true });\n\n for (const [path, endpoint] of this.endpoints.entries()) {\n this.log.debug(`WebSocket endpoint registered: ${path}`);\n }\n\n // Set up topic service message handler\n this.topicService.setMessageHandler(async (event) => {\n await this.sendToLocalConnections(event.channelPath, event.message, {\n roomIds: event.roomIds,\n userIds: event.userIds,\n connectionIds: event.connectionIds,\n exceptConnectionIds: event.exceptConnectionIds,\n exceptUserIds: event.exceptUserIds,\n });\n });\n\n this.log.info(\"WebSocket server OK\", {\n basePath: this.env.WEBSOCKET_PATH,\n });\n },\n });\n\n protected readonly ready = $hook({\n on: \"ready\",\n handler: async () => {\n if (this.alepha.isServerless() || !this.wss) {\n return;\n }\n\n // Attach upgrade handler to the HTTP server (must be done after HTTP server starts)\n const httpServer = this.alepha.store.get(\"alepha.node.server\");\n if (httpServer) {\n httpServer.on(\"upgrade\", (request, socket, head) => {\n this.handleUpgrade(request, socket, head);\n });\n this.log.debug(\"WebSocket upgrade handler attached to HTTP server\");\n } else {\n this.log.warn(\n \"No HTTP server found - WebSocket upgrade handler not attached\",\n );\n }\n },\n });\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: async () => {\n if (!this.wss) {\n return;\n }\n\n // Close all connections\n for (const connection of this.connections.values()) {\n await connection.close(1001, \"Server shutting down\");\n }\n\n await new Promise<void>((resolve, reject) => {\n this.wss?.close((err) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n });\n\n this.log.info(\"WebSocket server closed\");\n },\n });\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class NodeWebSocketConnection implements WebSocketConnection {\n protected readonly log = $logger();\n public metadata?: Record<string, any>;\n\n constructor(\n public readonly id: string,\n public readonly userId: string | undefined,\n public readonly roomIds: string[],\n protected readonly ws: WebSocket,\n protected readonly provider: NodeWebSocketServerProvider,\n protected readonly endpoint: WebSocketPrimitiveOptions<any, any>,\n ) {}\n\n public get readyState(): WebSocketState {\n return this.ws.readyState;\n }\n\n public async send(message: any): Promise<void> {\n if (this.ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"WebSocket is not open\");\n }\n\n const data =\n typeof message === \"string\" ? message : JSON.stringify(message);\n await new Promise<void>((resolve, reject) => {\n this.ws.send(data, (err) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n });\n }\n\n public async close(code?: number, reason?: string): Promise<void> {\n this.ws.close(code, reason);\n }\n\n public async handleMessage(data: any): Promise<void> {\n try {\n const rawMessage = data.toString();\n let parsed: any;\n\n try {\n parsed = JSON.parse(rawMessage);\n } catch {\n this.log.warn(\"Received non-JSON message\");\n return;\n }\n\n // Extract roomId from message (or use first room in connection's rooms)\n const roomId = parsed.roomId || this.roomIds[0] || \"default\";\n\n // Extract message payload\n const message = parsed.message || parsed;\n\n // Validate message against schema (out = client→server)\n const outSchema = this.endpoint.channel.options.schema.out;\n if (!TypeBoxValue.Check(outSchema, message)) {\n const errors = Array.from(TypeBoxValue.Errors(outSchema, message));\n throw new WebSocketValidationError(\n `Message validation failed: ${errors.map((e: any) => e.message).join(\", \")}`,\n );\n }\n\n // Create reply function scoped to this context\n const reply = async (options: {\n message: any;\n roomId?: string;\n exceptSelf?: boolean;\n exceptConnectionIds?: string[];\n exceptUserIds?: string[];\n }) => {\n const targetRoomId = options.roomId || roomId;\n const exceptConnectionIds = options.exceptConnectionIds || [];\n\n if (options.exceptSelf) {\n exceptConnectionIds.push(this.id);\n }\n\n await this.provider.emit(this.endpoint.channel.options.path, {\n message: options.message,\n roomId: targetRoomId,\n exceptConnectionIds,\n exceptUserIds: options.exceptUserIds,\n });\n };\n\n const context: WebSocketHandlerContext<any, any> = {\n connectionId: this.id,\n userId: this.userId,\n roomId,\n message,\n reply,\n };\n\n await this.endpoint.handler(context);\n } catch (error) {\n this.log.error(`Error handling WebSocket message on ${this.id}:`, error);\n\n // Send error back to client\n await this.send({\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n }\n}\n","import type { Static } from \"alepha\";\nimport type { ChannelPrimitive, TWSObject } from \"../primitives/$channel.ts\";\n\n/**\n * WebSocket connection interface\n */\nexport interface WebSocketConnection {\n /**\n * Unique connection ID\n */\n id: string;\n\n /**\n * User ID (if authenticated and security module is used)\n */\n userId?: string;\n\n /**\n * Room IDs that this connection is currently in\n */\n roomIds: string[];\n\n /**\n * Send a message to this connection\n */\n send(message: any): Promise<void>;\n\n /**\n * Close this connection\n */\n close(code?: number, reason?: string): Promise<void>;\n\n /**\n * Connection metadata (custom data)\n */\n metadata?: Record<string, any>;\n\n /**\n * Connection state\n */\n readyState: WebSocketState;\n}\n\n/**\n * WebSocket state enum\n */\nexport enum WebSocketState {\n CONNECTING = 0,\n OPEN = 1,\n CLOSING = 2,\n CLOSED = 3,\n}\n\n/**\n * WebSocket endpoint configuration (server-side)\n */\nexport interface WebSocketPrimitiveOptions<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * Channel definition with schema and path\n */\n channel: ChannelPrimitive<TClient, TServer>;\n\n /**\n * Handler for incoming messages from clients\n */\n handler: WebSocketHandler<TClient, TServer>;\n\n /**\n * Optional connection handler (called when a client connects)\n */\n onConnect?: (params: {\n /**\n * Unique connection ID of the client\n */\n connectionId: string;\n\n /**\n * User ID of the connected client (if authenticated and security module is used)\n */\n userId?: string;\n\n /**\n * Room IDs that the client is connecting to\n */\n roomIds: string[];\n }) => Promise<void> | void;\n\n /**\n * Optional disconnection handler (called when a client disconnects)\n */\n onDisconnect?: (params: {\n /**\n * Unique connection ID of the client\n */\n connectionId: string;\n\n /**\n * User ID of the connected client (if authenticated and security module is used)\n */\n userId?: string;\n\n /**\n * Room IDs that the client was connected to\n */\n roomIds: string[];\n }) => Promise<void> | void;\n\n /**\n * Change WebSocket server provider (for custom implementations)\n */\n provider?: any;\n\n /**\n * Whether to enforce security (authentication, authorization) on this WebSocket endpoint\n * Requires alepha/security integration\n */\n secure?: boolean;\n\n /**\n * Limit number of connections per user (if authenticated)\n */\n maxConnectionsPerUser?: number;\n}\n\n/**\n * WebSocket message handler\n */\nexport type WebSocketHandler<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> = (\n context: WebSocketHandlerContext<TClient, TServer>,\n) => Promise<void> | void;\n\n/**\n * WebSocket handler context\n */\nexport interface WebSocketHandlerContext<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n /**\n * Unique connection ID of the client\n */\n connectionId: string;\n\n /**\n * User ID of the connected client (if authenticated and security module is used)\n */\n userId?: string;\n\n /**\n * Room ID that the client sent the message from\n */\n roomId: string;\n\n /**\n * The parsed and validated message from the client\n */\n message: Static<TServer>;\n\n /**\n * Reply function tailored to current context (connectionId, roomId)\n */\n reply: (options: {\n /**\n * Message to send\n */\n message: Static<TClient>;\n\n /**\n * Optional: specify room ID to send to (defaults to sender's room ID)\n */\n roomId?: string;\n\n /**\n * Optional: exclude the sender connection from receiving the message\n * Will populate exceptConnectionIds with sender connection ID behind the scenes\n */\n exceptSelf?: boolean;\n\n /**\n * Optional: exclude specific connection IDs from receiving the message\n */\n exceptConnectionIds?: string[];\n\n /**\n * Optional: exclude specific user IDs from receiving the message\n * Requires alepha/security integration\n */\n exceptUserIds?: string[];\n }) => Promise<void>;\n}\n\n/**\n * Emit options for sending messages from the server\n */\nexport interface EmitOptions<TClient extends TWSObject> {\n /**\n * Message to send to clients\n */\n message: Static<TClient>;\n\n /**\n * Room ID to send the message to\n */\n roomId?: string;\n\n /**\n * Room IDs to send the message to\n */\n roomIds?: string[];\n\n /**\n * User ID to send the message to (if authenticated)\n */\n userId?: string;\n\n /**\n * User IDs to send the message to (if authenticated)\n */\n userIds?: string[];\n\n /**\n * Connection ID to send the message to\n */\n connectionId?: string;\n\n /**\n * Connection IDs to send the message to\n */\n connectionIds?: string[];\n\n /**\n * Optional: exclude specific connection IDs from receiving the message\n */\n exceptConnectionIds?: string[];\n\n /**\n * Optional: exclude specific user IDs from receiving the message\n * Requires alepha/security integration\n */\n exceptUserIds?: string[];\n}\n","import {\n $env,\n $inject,\n Alepha,\n AlephaError,\n type Static,\n TypeBoxValue,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { ChannelPrimitive, TWSObject } from \"../primitives/$channel.ts\";\n\nconst envSchema = t.object({\n WEBSOCKET_URL: t.text({\n default: \"\",\n description:\n \"WebSocket server URL (e.g., ws://localhost:3001). Leave empty to auto-detect.\",\n }),\n WEBSOCKET_RECONNECT_INTERVAL: t.integer({\n default: 3000,\n description: \"Reconnection interval in milliseconds\",\n }),\n WEBSOCKET_MAX_RECONNECT_ATTEMPTS: t.integer({\n default: 10,\n description:\n \"Maximum number of reconnection attempts. Set to -1 for infinite.\",\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\n/**\n * Room subscription\n */\ninterface RoomSubscription<TClient extends TWSObject> {\n roomId: string;\n handler: (message: Static<TClient>) => void;\n}\n\n/**\n * WebSocket channel connection\n *\n * Manages a single WebSocket connection to a channel with multiple room subscriptions.\n * One connection can handle multiple rooms on the same channel.\n */\nexport class WebSocketChannelConnection<\n TClient extends TWSObject,\n TServer extends TWSObject,\n> {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected ws?: WebSocket;\n protected reconnectAttempts = 0;\n protected reconnectTimer?: number;\n protected messageQueue: Array<{ roomId: string; message: Static<TServer> }> =\n [];\n\n // Room subscriptions: Map<roomId, handler>\n protected subscriptions = new Map<\n string,\n (message: Static<TClient>) => void\n >();\n\n // Connection state\n public isConnected = false;\n public isConnecting = false;\n public isError = false;\n public error?: Error;\n\n // Connection callbacks\n protected onConnectCallbacks = new Set<() => void>();\n protected onDisconnectCallbacks = new Set<() => void>();\n protected onErrorCallbacks = new Set<(error: Error) => void>();\n\n constructor(\n protected readonly channel: ChannelPrimitive<TClient, TServer>,\n protected readonly options: {\n url?: string;\n autoReconnect?: boolean;\n reconnectInterval?: number;\n maxReconnectAttempts?: number;\n },\n protected readonly env: Static<typeof envSchema>,\n ) {}\n\n /**\n * Build WebSocket URL\n */\n protected buildUrl(): string {\n this.log.trace(\"Building WebSocket URL\", {\n hasCustomUrl: !!this.options.url,\n channelPath: this.channel.options.path,\n });\n\n if (this.options.url) {\n this.log.debug(\"Using custom WebSocket URL\", { url: this.options.url });\n return this.options.url;\n }\n\n // Auto-detect URL from current location (browser only)\n if (typeof window !== \"undefined\") {\n const protocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const host = window.location.host;\n const path = this.channel.options.path;\n // Send all room IDs as query params\n const roomIds = Array.from(this.subscriptions.keys());\n const roomParam =\n roomIds.length > 0 ? `?roomIds=${roomIds.join(\",\")}` : \"\";\n const url = `${protocol}//${host}${path}${roomParam}`;\n this.log.debug(\"Auto-detected WebSocket URL\", { url, roomIds });\n return url;\n }\n\n // Fallback to env URL\n const url = `${this.env.WEBSOCKET_URL}${this.channel.options.path}`;\n this.log.debug(\"Using env WebSocket URL\", { url });\n return url;\n }\n\n /**\n * Subscribe to a room on this channel\n */\n public subscribe(\n roomId: string,\n handler: (message: Static<TClient>) => void,\n callbacks?: {\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n },\n ): () => void {\n this.log.debug(\"Subscribing to room\", {\n roomId,\n channelPath: this.channel.options.path,\n existingSubscriptions: this.subscriptions.size,\n });\n\n // Add subscription\n this.subscriptions.set(roomId, handler);\n\n // Add callbacks\n if (callbacks?.onConnect) this.onConnectCallbacks.add(callbacks.onConnect);\n if (callbacks?.onDisconnect)\n this.onDisconnectCallbacks.add(callbacks.onDisconnect);\n if (callbacks?.onError) this.onErrorCallbacks.add(callbacks.onError);\n\n // Connect if not already connected\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n this.log.trace(\"No active connection, initiating connect\");\n this.connect().catch((error) => {\n this.log.error(\"Failed to connect:\", error);\n });\n } else {\n this.log.trace(\"Already connected, reusing existing connection\");\n }\n\n // Return unsubscribe function\n return () => {\n this.log.debug(\"Unsubscribing from room\", { roomId });\n this.subscriptions.delete(roomId);\n if (callbacks?.onConnect)\n this.onConnectCallbacks.delete(callbacks.onConnect);\n if (callbacks?.onDisconnect)\n this.onDisconnectCallbacks.delete(callbacks.onDisconnect);\n if (callbacks?.onError) this.onErrorCallbacks.delete(callbacks.onError);\n\n // Disconnect if no more subscriptions\n if (this.subscriptions.size === 0) {\n this.log.debug(\"No more subscriptions, disconnecting\");\n this.disconnect();\n }\n };\n }\n\n /**\n * Connect to WebSocket server\n */\n protected async connect(): Promise<void> {\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.log.trace(\"Already connected, skipping connect\");\n return;\n }\n\n this.isConnecting = true;\n this.isError = false;\n this.error = undefined;\n\n const url = this.buildUrl();\n this.log.info(\"Connecting to WebSocket server\", { url });\n\n return new Promise((resolve, reject) => {\n try {\n const ws = new WebSocket(url);\n this.ws = ws;\n\n ws.onopen = () => {\n this.isConnected = true;\n this.isConnecting = false;\n this.isError = false;\n this.error = undefined;\n this.reconnectAttempts = 0;\n\n this.log.info(\"WebSocket connected\", {\n channelPath: this.channel.options.path,\n rooms: Array.from(this.subscriptions.keys()),\n });\n\n // Flush queued messages\n if (this.messageQueue.length > 0) {\n this.log.debug(\"Flushing queued messages\", {\n count: this.messageQueue.length,\n });\n }\n while (this.messageQueue.length > 0) {\n const msg = this.messageQueue.shift();\n if (msg) {\n this.log.trace(\"Sending queued message\", { roomId: msg.roomId });\n ws.send(\n JSON.stringify({\n roomId: msg.roomId,\n message: msg.message,\n }),\n );\n }\n }\n\n // Call all connect callbacks\n for (const callback of this.onConnectCallbacks) {\n callback();\n }\n\n resolve();\n };\n\n ws.onmessage = (event) => {\n this.log.trace(\"Message received\", {\n dataLength: event.data?.length,\n });\n this.handleMessage(event.data);\n };\n\n ws.onclose = (event) => {\n this.isConnected = false;\n this.isConnecting = false;\n this.ws = undefined;\n\n this.log.info(\"WebSocket disconnected\", {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean,\n });\n\n // Call all disconnect callbacks\n for (const callback of this.onDisconnectCallbacks) {\n callback();\n }\n\n // Attempt reconnection\n if (this.options.autoReconnect !== false) {\n this.scheduleReconnect();\n }\n };\n\n ws.onerror = () => {\n const err = new Error(\"WebSocket connection error\");\n this.isError = true;\n this.error = err;\n this.isConnecting = false;\n\n this.log.error(\"WebSocket error\", { url });\n\n // Call all error callbacks\n for (const callback of this.onErrorCallbacks) {\n callback(err);\n }\n\n reject(err);\n };\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Connection failed\");\n this.isError = true;\n this.error = error;\n this.isConnecting = false;\n\n this.log.error(\"Failed to create WebSocket\", { error: error.message });\n\n // Call all error callbacks\n for (const callback of this.onErrorCallbacks) {\n callback(error);\n }\n\n reject(error);\n }\n });\n }\n\n /**\n * Handle incoming message\n */\n protected handleMessage(data: string): void {\n try {\n const parsed = JSON.parse(data);\n this.log.trace(\"Parsed incoming message\", { parsed });\n\n // Validate incoming message against schema\n const inSchema = this.channel.options.schema.in;\n this.alepha.codec.validate(inSchema, parsed);\n\n this.log.debug(\"Dispatching message to handlers\", {\n handlerCount: this.subscriptions.size,\n });\n\n // Extract roomId from message if present (server should send it back)\n // For now, broadcast to all subscribed rooms\n // TODO: Server should include roomId in response\n for (const handler of this.subscriptions.values()) {\n handler(parsed as Static<TClient>);\n }\n } catch (err) {\n this.log.error(\"Error handling message:\", err);\n }\n }\n\n /**\n * Send message to a specific room\n */\n public async send(roomId: string, message: Static<TServer>): Promise<void> {\n this.log.trace(\"Sending message\", { roomId, message });\n\n // Validate outgoing message against schema\n const outSchema = this.channel.options.schema.out;\n if (!TypeBoxValue.Check(outSchema, message)) {\n const errors = Array.from(TypeBoxValue.Errors(outSchema, message));\n this.log.warn(\"Message validation failed\", { errors });\n throw new Error(\n `Message validation failed: ${errors.map((e) => e.message).join(\", \")}`,\n );\n }\n\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n // Queue message\n this.log.debug(\"Connection not ready, queuing message\", {\n roomId,\n queueSize: this.messageQueue.length + 1,\n });\n this.messageQueue.push({ roomId, message });\n return;\n }\n\n this.log.debug(\"Sending message to server\", { roomId });\n this.ws.send(\n JSON.stringify({\n roomId,\n message,\n }),\n );\n }\n\n /**\n * Schedule reconnection\n */\n protected scheduleReconnect(): void {\n const maxAttempts =\n this.options.maxReconnectAttempts ??\n this.env.WEBSOCKET_MAX_RECONNECT_ATTEMPTS ??\n 10;\n const reconnectInterval =\n this.options.reconnectInterval ??\n this.env.WEBSOCKET_RECONNECT_INTERVAL ??\n 3000;\n\n if (maxAttempts !== -1 && this.reconnectAttempts >= maxAttempts) {\n this.log.warn(\"Max reconnection attempts reached\", {\n attempts: this.reconnectAttempts,\n maxAttempts,\n });\n return;\n }\n\n this.reconnectAttempts++;\n\n this.log.debug(\"Scheduling reconnection\", {\n attempt: this.reconnectAttempts,\n maxAttempts,\n intervalMs: reconnectInterval,\n });\n\n this.reconnectTimer = window.setTimeout(() => {\n this.log.info(\"Reconnecting...\", {\n attempt: this.reconnectAttempts,\n maxAttempts,\n });\n this.connect().catch((error) => {\n this.log.error(\"Reconnection failed:\", error);\n });\n }, reconnectInterval);\n }\n\n /**\n * Disconnect from server\n */\n public disconnect(): void {\n this.log.debug(\"Disconnecting\", {\n hasTimer: !!this.reconnectTimer,\n hasConnection: !!this.ws,\n });\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n\n if (this.ws) {\n this.ws.close();\n this.ws = undefined;\n }\n\n this.isConnected = false;\n this.isConnecting = false;\n\n this.log.info(\"Disconnected\");\n }\n\n /**\n * Reconnect manually\n */\n public reconnect(): void {\n this.log.info(\"Manual reconnect requested\");\n this.disconnect();\n this.connect().catch((error) => {\n this.log.error(\"Manual reconnection failed:\", error);\n });\n }\n\n /**\n * Check if subscribed to a room\n */\n public hasRoom(roomId: string): boolean {\n return this.subscriptions.has(roomId);\n }\n\n /**\n * Get all subscribed rooms\n */\n public getRooms(): string[] {\n return Array.from(this.subscriptions.keys());\n }\n}\n\n/**\n * WebSocket Client Service\n *\n * Manages WebSocket connections from the client side (browser).\n * One connection per channel, multiple rooms per connection.\n */\nexport class WebSocketClient {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly env = $env(envSchema);\n\n // Map<channelPath, connection>\n protected connections = new Map<\n string,\n WebSocketChannelConnection<any, any>\n >();\n\n /**\n * Subscribe to a room on a channel\n */\n public subscribe<TClient extends TWSObject, TServer extends TWSObject>(\n roomId: string,\n channel: ChannelPrimitive<TClient, TServer>,\n handler: (message: Static<TClient>) => void,\n options: {\n url?: string;\n autoReconnect?: boolean;\n reconnectInterval?: number;\n maxReconnectAttempts?: number;\n onConnect?: () => void;\n onDisconnect?: () => void;\n onError?: (error: Error) => void;\n } = {},\n ): () => void {\n const channelPath = channel.options.path;\n\n this.log.debug(\"WebSocketClient.subscribe\", {\n roomId,\n channelPath,\n existingConnections: this.connections.size,\n });\n\n // Get or create connection for this channel\n let connection = this.connections.get(\n channelPath,\n ) as WebSocketChannelConnection<TClient, TServer>;\n\n if (!connection) {\n this.log.debug(\"Creating new connection for channel\", { channelPath });\n connection = this.alepha.inject(WebSocketChannelConnection, {\n lifetime: \"transient\",\n args: [\n channel,\n {\n url: options.url,\n autoReconnect: options.autoReconnect,\n reconnectInterval: options.reconnectInterval,\n maxReconnectAttempts: options.maxReconnectAttempts,\n },\n this.env,\n ],\n }) as WebSocketChannelConnection<any, any>;\n\n this.connections.set(channelPath, connection);\n } else {\n this.log.trace(\"Reusing existing connection for channel\", {\n channelPath,\n });\n }\n\n // Subscribe to the room on this connection\n const unsubscribe = connection.subscribe(roomId, handler, {\n onConnect: options.onConnect,\n onDisconnect: options.onDisconnect,\n onError: options.onError,\n });\n\n // Return unsubscribe function\n return () => {\n this.log.debug(\"WebSocketClient.unsubscribe\", { roomId, channelPath });\n unsubscribe();\n\n // Clean up connection if no more rooms\n if (connection.getRooms().length === 0) {\n this.log.debug(\"Removing connection for channel (no more rooms)\", {\n channelPath,\n });\n this.connections.delete(channelPath);\n }\n };\n }\n\n /**\n * Send message to a room on a channel\n */\n public async send<TClient extends TWSObject, TServer extends TWSObject>(\n roomId: string,\n channel: ChannelPrimitive<TClient, TServer>,\n message: Static<TServer>,\n ): Promise<void> {\n const channelPath = channel.options.path;\n\n this.log.trace(\"WebSocketClient.send\", { roomId, channelPath });\n\n const connection = this.connections.get(\n channelPath,\n ) as WebSocketChannelConnection<TClient, TServer>;\n\n if (!connection) {\n this.log.warn(\"Attempted to send on unsubscribed channel\", {\n channelPath,\n });\n throw new AlephaError(\n `Not subscribed to channel ${channelPath}. Subscribe first before sending messages.`,\n );\n }\n\n await connection.send(roomId, message);\n }\n\n /**\n * Get connection for a channel\n */\n public getConnection<TClient extends TWSObject, TServer extends TWSObject>(\n channel: ChannelPrimitive<TClient, TServer>,\n ): WebSocketChannelConnection<TClient, TServer> | undefined {\n const channelPath = channel.options.path;\n const connection = this.connections.get(channelPath) as\n | WebSocketChannelConnection<TClient, TServer>\n | undefined;\n\n this.log.trace(\"WebSocketClient.getConnection\", {\n channelPath,\n found: !!connection,\n });\n\n return connection;\n }\n\n /**\n * Disconnect all connections\n */\n public disconnectAll(): void {\n this.log.info(\"Disconnecting all connections\", {\n count: this.connections.size,\n });\n\n for (const connection of this.connections.values()) {\n connection.disconnect();\n }\n this.connections.clear();\n\n this.log.debug(\"All connections disconnected\");\n }\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { AlephaServer } from \"alepha/server\";\nimport { AlephaTopic } from \"alepha/topic\";\nimport { $channel } from \"./primitives/$channel.ts\";\nimport { $websocket } from \"./primitives/$websocket.ts\";\nimport { NodeWebSocketServerProvider } from \"./providers/NodeWebSocketServerProvider.ts\";\nimport { WebSocketServerProvider } from \"./providers/WebSocketServerProvider.ts\";\nimport { RoomManager } from \"./services/RoomManager.ts\";\nimport { WebSocketTopicService } from \"./services/WebSocketTopicService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n /**\n * Fires when a WebSocket connection is established\n */\n \"websocket:connect\": {\n connectionId: string;\n path: string;\n };\n\n /**\n * Fires when a WebSocket connection is closed\n */\n \"websocket:disconnect\": {\n connectionId: string;\n path: string;\n code?: number;\n reason?: string;\n };\n\n /**\n * Fires when a WebSocket message is received\n */\n \"websocket:message\": {\n connectionId: string;\n path: string;\n message: any;\n };\n\n /**\n * Fires when a WebSocket error occurs\n */\n \"websocket:error\": {\n connectionId: string;\n path: string;\n error: Error;\n };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./index.shared.ts\";\nexport * from \"./providers/NodeWebSocketServerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides real-time bidirectional communication using WebSockets.\n *\n * The WebSockets module enables building real-time applications using the `$websocket` primitive\n * on class properties. It provides automatic connection management, message routing, type-safe\n * message handling, and seamless integration with other Alepha modules.\n *\n * On the server side (Node.js), it uses the 'ws' library to create a WebSocket server.\n * On the client side (browser), it uses the native WebSocket API.\n *\n * @see {@link $websocket}\n * @module alepha.websockets\n */\nexport const AlephaWebSocket = $module({\n name: \"alepha.websocket\",\n primitives: [$channel, $websocket],\n services: [\n WebSocketServerProvider,\n NodeWebSocketServerProvider,\n RoomManager,\n WebSocketTopicService,\n ],\n register: (alepha: Alepha) => {\n alepha.with(AlephaServer);\n alepha.with(AlephaTopic);\n\n alepha.with({\n provide: WebSocketServerProvider,\n use: NodeWebSocketServerProvider,\n });\n },\n});\n"],"mappings":"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"}
|