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.
Files changed (296) hide show
  1. package/README.md +1 -1
  2. package/dist/api-files/index.d.ts +28 -91
  3. package/dist/api-files/index.js +10 -755
  4. package/dist/api-files/index.js.map +1 -1
  5. package/dist/api-jobs/index.d.ts +46 -46
  6. package/dist/api-jobs/index.js +13 -13
  7. package/dist/api-jobs/index.js.map +1 -1
  8. package/dist/api-notifications/index.d.ts +129 -146
  9. package/dist/api-notifications/index.js +17 -39
  10. package/dist/api-notifications/index.js.map +1 -1
  11. package/dist/api-parameters/index.d.ts +21 -22
  12. package/dist/api-parameters/index.js +22 -22
  13. package/dist/api-parameters/index.js.map +1 -1
  14. package/dist/api-users/index.d.ts +223 -2000
  15. package/dist/api-users/index.js +914 -4787
  16. package/dist/api-users/index.js.map +1 -1
  17. package/dist/api-verifications/index.d.ts +96 -96
  18. package/dist/batch/index.d.ts +13 -13
  19. package/dist/batch/index.js +8 -8
  20. package/dist/batch/index.js.map +1 -1
  21. package/dist/bucket/index.d.ts +14 -14
  22. package/dist/bucket/index.js +12 -12
  23. package/dist/bucket/index.js.map +1 -1
  24. package/dist/cache/index.d.ts +11 -11
  25. package/dist/cache/index.js +9 -9
  26. package/dist/cache/index.js.map +1 -1
  27. package/dist/cli/index.d.ts +28 -26
  28. package/dist/cli/index.js +50 -13
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/command/index.d.ts +19 -19
  31. package/dist/command/index.js +25 -25
  32. package/dist/command/index.js.map +1 -1
  33. package/dist/core/index.browser.js +218 -218
  34. package/dist/core/index.browser.js.map +1 -1
  35. package/dist/core/index.d.ts +232 -232
  36. package/dist/core/index.js +218 -218
  37. package/dist/core/index.js.map +1 -1
  38. package/dist/core/index.native.js +2113 -0
  39. package/dist/core/index.native.js.map +1 -0
  40. package/dist/datetime/index.d.ts +9 -9
  41. package/dist/datetime/index.js +7 -7
  42. package/dist/datetime/index.js.map +1 -1
  43. package/dist/email/index.d.ts +16 -16
  44. package/dist/email/index.js +9 -9
  45. package/dist/email/index.js.map +1 -1
  46. package/dist/file/index.js +1 -1
  47. package/dist/file/index.js.map +1 -1
  48. package/dist/lock/index.d.ts +9 -9
  49. package/dist/lock/index.js +8 -8
  50. package/dist/lock/index.js.map +1 -1
  51. package/dist/lock-redis/index.js +3 -66
  52. package/dist/lock-redis/index.js.map +1 -1
  53. package/dist/logger/index.d.ts +5 -5
  54. package/dist/logger/index.js +8 -8
  55. package/dist/logger/index.js.map +1 -1
  56. package/dist/orm/index.browser.js +114 -114
  57. package/dist/orm/index.browser.js.map +1 -1
  58. package/dist/orm/index.d.ts +218 -218
  59. package/dist/orm/index.js +46 -46
  60. package/dist/orm/index.js.map +1 -1
  61. package/dist/queue/index.d.ts +29 -29
  62. package/dist/queue/index.js +20 -20
  63. package/dist/queue/index.js.map +1 -1
  64. package/dist/queue-redis/index.d.ts +2 -2
  65. package/dist/redis/index.d.ts +10 -10
  66. package/dist/retry/index.d.ts +19 -19
  67. package/dist/retry/index.js +7 -7
  68. package/dist/retry/index.js.map +1 -1
  69. package/dist/scheduler/index.d.ts +16 -16
  70. package/dist/scheduler/index.js +9 -9
  71. package/dist/scheduler/index.js.map +1 -1
  72. package/dist/security/index.d.ts +80 -80
  73. package/dist/security/index.js +32 -32
  74. package/dist/security/index.js.map +1 -1
  75. package/dist/server/index.browser.js +1 -1
  76. package/dist/server/index.browser.js.map +1 -1
  77. package/dist/server/index.d.ts +101 -101
  78. package/dist/server/index.js +16 -16
  79. package/dist/server/index.js.map +1 -1
  80. package/dist/server-auth/index.browser.js +4 -982
  81. package/dist/server-auth/index.browser.js.map +1 -1
  82. package/dist/server-auth/index.d.ts +204 -785
  83. package/dist/server-auth/index.js +47 -1239
  84. package/dist/server-auth/index.js.map +1 -1
  85. package/dist/server-cache/index.d.ts +10 -10
  86. package/dist/server-cache/index.js +2 -2
  87. package/dist/server-cache/index.js.map +1 -1
  88. package/dist/server-compress/index.d.ts +4 -4
  89. package/dist/server-compress/index.js +1 -1
  90. package/dist/server-compress/index.js.map +1 -1
  91. package/dist/server-cookies/index.browser.js +8 -8
  92. package/dist/server-cookies/index.browser.js.map +1 -1
  93. package/dist/server-cookies/index.d.ts +17 -17
  94. package/dist/server-cookies/index.js +10 -10
  95. package/dist/server-cookies/index.js.map +1 -1
  96. package/dist/server-cors/index.d.ts +17 -17
  97. package/dist/server-cors/index.js +9 -9
  98. package/dist/server-cors/index.js.map +1 -1
  99. package/dist/server-health/index.d.ts +19 -19
  100. package/dist/server-helmet/index.d.ts +1 -1
  101. package/dist/server-links/index.browser.js +12 -12
  102. package/dist/server-links/index.browser.js.map +1 -1
  103. package/dist/server-links/index.d.ts +59 -251
  104. package/dist/server-links/index.js +23 -502
  105. package/dist/server-links/index.js.map +1 -1
  106. package/dist/server-metrics/index.d.ts +4 -4
  107. package/dist/server-multipart/index.d.ts +2 -2
  108. package/dist/server-proxy/index.d.ts +12 -12
  109. package/dist/server-proxy/index.js +10 -10
  110. package/dist/server-proxy/index.js.map +1 -1
  111. package/dist/server-rate-limit/index.d.ts +22 -22
  112. package/dist/server-rate-limit/index.js +12 -12
  113. package/dist/server-rate-limit/index.js.map +1 -1
  114. package/dist/server-security/index.d.ts +22 -22
  115. package/dist/server-security/index.js +15 -15
  116. package/dist/server-security/index.js.map +1 -1
  117. package/dist/server-static/index.d.ts +14 -14
  118. package/dist/server-static/index.js +8 -8
  119. package/dist/server-static/index.js.map +1 -1
  120. package/dist/server-swagger/index.d.ts +25 -184
  121. package/dist/server-swagger/index.js +21 -724
  122. package/dist/server-swagger/index.js.map +1 -1
  123. package/dist/sms/index.d.ts +14 -14
  124. package/dist/sms/index.js +9 -9
  125. package/dist/sms/index.js.map +1 -1
  126. package/dist/thread/index.d.ts +11 -11
  127. package/dist/thread/index.js +17 -17
  128. package/dist/thread/index.js.map +1 -1
  129. package/dist/topic/index.d.ts +26 -26
  130. package/dist/topic/index.js +16 -16
  131. package/dist/topic/index.js.map +1 -1
  132. package/dist/topic-redis/index.d.ts +1 -1
  133. package/dist/vite/index.d.ts +3 -3
  134. package/dist/vite/index.js +8 -8
  135. package/dist/vite/index.js.map +1 -1
  136. package/dist/websocket/index.browser.js +11 -11
  137. package/dist/websocket/index.browser.js.map +1 -1
  138. package/dist/websocket/index.d.ts +58 -58
  139. package/dist/websocket/index.js +13 -13
  140. package/dist/websocket/index.js.map +1 -1
  141. package/package.json +113 -52
  142. package/src/api-files/services/FileService.ts +5 -7
  143. package/src/api-jobs/index.ts +1 -1
  144. package/src/api-jobs/{descriptors → primitives}/$job.ts +8 -8
  145. package/src/api-jobs/providers/JobProvider.ts +9 -9
  146. package/src/api-jobs/services/JobService.ts +5 -5
  147. package/src/api-notifications/index.ts +5 -15
  148. package/src/api-notifications/{descriptors → primitives}/$notification.ts +10 -10
  149. package/src/api-notifications/services/NotificationSenderService.ts +3 -3
  150. package/src/api-parameters/index.ts +1 -1
  151. package/src/api-parameters/{descriptors → primitives}/$config.ts +7 -12
  152. package/src/api-users/index.ts +1 -1
  153. package/src/api-users/{descriptors → primitives}/$userRealm.ts +8 -8
  154. package/src/api-users/providers/UserRealmProvider.ts +1 -1
  155. package/src/batch/index.ts +3 -3
  156. package/src/batch/{descriptors → primitives}/$batch.ts +13 -16
  157. package/src/bucket/index.ts +8 -8
  158. package/src/bucket/{descriptors → primitives}/$bucket.ts +8 -8
  159. package/src/bucket/providers/LocalFileStorageProvider.ts +3 -3
  160. package/src/cache/index.ts +4 -4
  161. package/src/cache/{descriptors → primitives}/$cache.ts +15 -15
  162. package/src/cli/apps/AlephaPackageBuilderCli.ts +24 -2
  163. package/src/cli/commands/DrizzleCommands.ts +6 -6
  164. package/src/cli/commands/VerifyCommands.ts +1 -1
  165. package/src/cli/commands/ViteCommands.ts +6 -1
  166. package/src/cli/services/ProjectUtils.ts +34 -3
  167. package/src/command/index.ts +5 -5
  168. package/src/command/{descriptors → primitives}/$command.ts +9 -12
  169. package/src/command/providers/CliProvider.ts +10 -10
  170. package/src/core/Alepha.ts +30 -33
  171. package/src/core/constants/KIND.ts +1 -1
  172. package/src/core/constants/OPTIONS.ts +1 -1
  173. package/src/core/helpers/{descriptor.ts → primitive.ts} +18 -18
  174. package/src/core/helpers/ref.ts +1 -1
  175. package/src/core/index.shared.ts +8 -8
  176. package/src/core/{descriptors → primitives}/$context.ts +5 -5
  177. package/src/core/{descriptors → primitives}/$hook.ts +4 -4
  178. package/src/core/{descriptors → primitives}/$inject.ts +2 -2
  179. package/src/core/{descriptors → primitives}/$module.ts +9 -9
  180. package/src/core/{descriptors → primitives}/$use.ts +2 -2
  181. package/src/core/providers/CodecManager.ts +1 -1
  182. package/src/core/providers/JsonSchemaCodec.ts +1 -1
  183. package/src/core/providers/StateManager.ts +2 -2
  184. package/src/datetime/index.ts +3 -3
  185. package/src/datetime/{descriptors → primitives}/$interval.ts +6 -6
  186. package/src/email/index.ts +4 -4
  187. package/src/email/{descriptors → primitives}/$email.ts +8 -8
  188. package/src/file/index.ts +1 -1
  189. package/src/lock/index.ts +3 -3
  190. package/src/lock/{descriptors → primitives}/$lock.ts +10 -10
  191. package/src/logger/index.ts +8 -8
  192. package/src/logger/{descriptors → primitives}/$logger.ts +2 -2
  193. package/src/logger/services/Logger.ts +1 -1
  194. package/src/orm/constants/PG_SYMBOLS.ts +2 -2
  195. package/src/orm/index.browser.ts +2 -2
  196. package/src/orm/index.ts +8 -8
  197. package/src/orm/{descriptors → primitives}/$entity.ts +11 -11
  198. package/src/orm/{descriptors → primitives}/$repository.ts +2 -2
  199. package/src/orm/{descriptors → primitives}/$sequence.ts +8 -8
  200. package/src/orm/{descriptors → primitives}/$transaction.ts +4 -4
  201. package/src/orm/providers/PostgresTypeProvider.ts +3 -3
  202. package/src/orm/providers/RepositoryProvider.ts +4 -4
  203. package/src/orm/providers/drivers/DatabaseProvider.ts +7 -7
  204. package/src/orm/services/ModelBuilder.ts +9 -9
  205. package/src/orm/services/PgRelationManager.ts +2 -2
  206. package/src/orm/services/PostgresModelBuilder.ts +5 -5
  207. package/src/orm/services/Repository.ts +7 -7
  208. package/src/orm/services/SqliteModelBuilder.ts +5 -5
  209. package/src/queue/index.ts +7 -7
  210. package/src/queue/{descriptors → primitives}/$consumer.ts +15 -15
  211. package/src/queue/{descriptors → primitives}/$queue.ts +12 -12
  212. package/src/queue/providers/WorkerProvider.ts +7 -7
  213. package/src/retry/index.ts +3 -3
  214. package/src/retry/{descriptors → primitives}/$retry.ts +14 -14
  215. package/src/scheduler/index.ts +3 -3
  216. package/src/scheduler/{descriptors → primitives}/$scheduler.ts +9 -9
  217. package/src/scheduler/providers/CronProvider.ts +1 -1
  218. package/src/security/index.ts +9 -9
  219. package/src/security/{descriptors → primitives}/$permission.ts +7 -7
  220. package/src/security/{descriptors → primitives}/$realm.ts +6 -12
  221. package/src/security/{descriptors → primitives}/$role.ts +12 -12
  222. package/src/security/{descriptors → primitives}/$serviceAccount.ts +8 -8
  223. package/src/server/index.browser.ts +1 -1
  224. package/src/server/index.ts +14 -14
  225. package/src/server/{descriptors → primitives}/$action.ts +13 -13
  226. package/src/server/{descriptors → primitives}/$route.ts +9 -9
  227. package/src/server/providers/NodeHttpServerProvider.ts +1 -1
  228. package/src/server/services/HttpClient.ts +1 -1
  229. package/src/server-auth/index.browser.ts +1 -1
  230. package/src/server-auth/index.ts +6 -6
  231. package/src/server-auth/{descriptors → primitives}/$auth.ts +10 -10
  232. package/src/server-auth/{descriptors → primitives}/$authCredentials.ts +4 -4
  233. package/src/server-auth/{descriptors → primitives}/$authGithub.ts +4 -4
  234. package/src/server-auth/{descriptors → primitives}/$authGoogle.ts +4 -4
  235. package/src/server-auth/providers/ServerAuthProvider.ts +4 -4
  236. package/src/server-cache/providers/ServerCacheProvider.ts +7 -7
  237. package/src/server-compress/providers/ServerCompressProvider.ts +3 -3
  238. package/src/server-cookies/index.browser.ts +2 -2
  239. package/src/server-cookies/index.ts +5 -5
  240. package/src/server-cookies/{descriptors → primitives}/$cookie.browser.ts +12 -12
  241. package/src/server-cookies/{descriptors → primitives}/$cookie.ts +13 -13
  242. package/src/server-cookies/providers/ServerCookiesProvider.ts +4 -4
  243. package/src/server-cookies/services/CookieParser.ts +1 -1
  244. package/src/server-cors/index.ts +3 -3
  245. package/src/server-cors/{descriptors → primitives}/$cors.ts +11 -13
  246. package/src/server-cors/providers/ServerCorsProvider.ts +5 -5
  247. package/src/server-links/index.browser.ts +5 -5
  248. package/src/server-links/index.ts +9 -9
  249. package/src/server-links/{descriptors → primitives}/$remote.ts +11 -11
  250. package/src/server-links/providers/LinkProvider.ts +7 -7
  251. package/src/server-links/providers/{RemoteDescriptorProvider.ts → RemotePrimitiveProvider.ts} +6 -6
  252. package/src/server-links/providers/ServerLinksProvider.ts +3 -3
  253. package/src/server-proxy/index.ts +3 -3
  254. package/src/server-proxy/{descriptors → primitives}/$proxy.ts +8 -8
  255. package/src/server-proxy/providers/ServerProxyProvider.ts +4 -4
  256. package/src/server-rate-limit/index.ts +6 -6
  257. package/src/server-rate-limit/{descriptors → primitives}/$rateLimit.ts +13 -13
  258. package/src/server-rate-limit/providers/ServerRateLimitProvider.ts +5 -5
  259. package/src/server-security/index.ts +3 -3
  260. package/src/server-security/{descriptors → primitives}/$basicAuth.ts +13 -13
  261. package/src/server-security/providers/ServerBasicAuthProvider.ts +5 -5
  262. package/src/server-security/providers/ServerSecurityProvider.ts +4 -4
  263. package/src/server-static/index.ts +3 -3
  264. package/src/server-static/{descriptors → primitives}/$serve.ts +8 -10
  265. package/src/server-static/providers/ServerStaticProvider.ts +6 -6
  266. package/src/server-swagger/index.ts +5 -5
  267. package/src/server-swagger/{descriptors → primitives}/$swagger.ts +9 -9
  268. package/src/server-swagger/providers/ServerSwaggerProvider.ts +11 -10
  269. package/src/sms/index.ts +4 -4
  270. package/src/sms/{descriptors → primitives}/$sms.ts +8 -8
  271. package/src/thread/index.ts +3 -3
  272. package/src/thread/{descriptors → primitives}/$thread.ts +13 -13
  273. package/src/thread/providers/ThreadProvider.ts +7 -9
  274. package/src/topic/index.ts +5 -5
  275. package/src/topic/{descriptors → primitives}/$subscriber.ts +14 -14
  276. package/src/topic/{descriptors → primitives}/$topic.ts +10 -10
  277. package/src/topic/providers/TopicProvider.ts +4 -4
  278. package/src/vite/tasks/copyAssets.ts +1 -1
  279. package/src/vite/tasks/generateSitemap.ts +3 -3
  280. package/src/vite/tasks/prerenderPages.ts +2 -2
  281. package/src/vite/tasks/runAlepha.ts +2 -2
  282. package/src/websocket/index.browser.ts +3 -3
  283. package/src/websocket/index.shared.ts +2 -2
  284. package/src/websocket/index.ts +4 -4
  285. package/src/websocket/interfaces/WebSocketInterfaces.ts +3 -3
  286. package/src/websocket/{descriptors → primitives}/$channel.ts +10 -10
  287. package/src/websocket/{descriptors → primitives}/$websocket.ts +8 -8
  288. package/src/websocket/providers/NodeWebSocketServerProvider.ts +7 -7
  289. package/src/websocket/providers/WebSocketServerProvider.ts +3 -3
  290. package/src/websocket/services/WebSocketClient.ts +5 -5
  291. package/src/api-notifications/providers/MemorySmsProvider.ts +0 -20
  292. package/src/api-notifications/providers/SmsProvider.ts +0 -8
  293. /package/src/core/{descriptors → primitives}/$atom.ts +0 -0
  294. /package/src/core/{descriptors → primitives}/$env.ts +0 -0
  295. /package/src/server-auth/{descriptors → primitives}/$authApple.ts +0 -0
  296. /package/src/server-links/{descriptors → primitives}/$client.ts +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.js","names":["channel: ChannelDescriptor<TClient, TServer>","options: {\n url?: string;\n autoReconnect?: boolean;\n reconnectInterval?: number;\n maxReconnectAttempts?: number;\n }","env: Static<typeof envSchema>","url","code?: number"],"sources":["../../src/websocket/descriptors/$channel.ts","../../src/websocket/providers/WebSocketServerProvider.ts","../../src/websocket/descriptors/$websocket.ts","../../src/websocket/services/WebSocketClient.ts","../../src/websocket/errors/WebSocketError.ts","../../src/websocket/interfaces/WebSocketInterfaces.ts","../../src/websocket/services/RoomManager.ts","../../src/websocket/services/WebSocketTopicService.ts","../../src/websocket/index.browser.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","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","/**\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 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 { $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 { $module, type Alepha } from \"alepha\";\nimport { AlephaTopic } from \"alepha/topic\";\nimport { $channel } from \"./descriptors/$channel.ts\";\nimport { $websocket } from \"./descriptors/$websocket.ts\";\nimport { WebSocketClient } from \"./services/WebSocketClient.ts\";\n\n/**\n * alepha/websocket (Browser)\n *\n * Browser-side WebSocket client module. Provides WebSocketClient service\n * for managing WebSocket connections from the browser.\n *\n * For React applications, use alepha/react/websocket with the useRoom hook.\n */\nexport * from \"./index.shared.ts\";\n\nexport const AlephaWebSocket = $module({\n name: \"alepha.websocket\",\n descriptors: [$channel, $websocket],\n services: [WebSocketClient],\n register: (alepha: Alepha) => {\n alepha.with(AlephaTopic);\n alepha.with(WebSocketClient);\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.browser.js","names":["channel: ChannelPrimitive<TClient, TServer>","options: {\n url?: string;\n autoReconnect?: boolean;\n reconnectInterval?: number;\n maxReconnectAttempts?: number;\n }","env: Static<typeof envSchema>","url","code?: number"],"sources":["../../src/websocket/primitives/$channel.ts","../../src/websocket/providers/WebSocketServerProvider.ts","../../src/websocket/primitives/$websocket.ts","../../src/websocket/services/WebSocketClient.ts","../../src/websocket/errors/WebSocketError.ts","../../src/websocket/interfaces/WebSocketInterfaces.ts","../../src/websocket/services/RoomManager.ts","../../src/websocket/services/WebSocketTopicService.ts","../../src/websocket/index.browser.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","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","/**\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 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 { $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 { $module, type Alepha } from \"alepha\";\nimport { AlephaTopic } from \"alepha/topic\";\nimport { $channel } from \"./primitives/$channel.ts\";\nimport { $websocket } from \"./primitives/$websocket.ts\";\nimport { WebSocketClient } from \"./services/WebSocketClient.ts\";\n\n/**\n * alepha/websocket (Browser)\n *\n * Browser-side WebSocket client module. Provides WebSocketClient service\n * for managing WebSocket connections from the browser.\n *\n * For React applications, use alepha/react/websocket with the useRoom hook.\n */\nexport * from \"./index.shared.ts\";\n\nexport const AlephaWebSocket = $module({\n name: \"alepha.websocket\",\n primitives: [$channel, $websocket],\n services: [WebSocketClient],\n register: (alepha: Alepha) => {\n alepha.with(AlephaTopic);\n alepha.with(WebSocketClient);\n },\n});\n"],"mappings":"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"}
@@ -1,16 +1,37 @@
1
1
  import * as alepha42 from "alepha";
2
- import { Alepha, Descriptor, KIND, Static, TObject, TString, TUnion } from "alepha";
2
+ import { Alepha, KIND, Primitive, Static, TObject, TString, TUnion } from "alepha";
3
3
  import * as alepha_topic0 from "alepha/topic";
4
- import * as alepha_logger2 from "alepha/logger";
4
+ import * as alepha_logger1 from "alepha/logger";
5
5
  import { WebSocket as WebSocket$1, WebSocketServer } from "ws";
6
6
  import { IncomingMessage } from "node:http";
7
7
 
8
- //#region src/websocket/descriptors/$channel.d.ts
8
+ //#region src/websocket/errors/WebSocketError.d.ts
9
+ /**
10
+ * Base WebSocket error class
11
+ */
12
+ declare class WebSocketError extends Error {
13
+ readonly code?: number | undefined;
14
+ constructor(message: string, code?: number | undefined);
15
+ }
16
+ /**
17
+ * Error thrown when WebSocket connection fails
18
+ */
19
+ declare class WebSocketConnectionError extends WebSocketError {
20
+ constructor(message: string, code?: number);
21
+ }
22
+ /**
23
+ * Error thrown when WebSocket message validation fails
24
+ */
25
+ declare class WebSocketValidationError extends WebSocketError {
26
+ constructor(message: string);
27
+ }
28
+ //#endregion
29
+ //#region src/websocket/primitives/$channel.d.ts
9
30
  type TWSObject = TObject | TUnion;
10
31
  /**
11
- * Channel descriptor options
32
+ * Channel primitive options
12
33
  */
13
- interface ChannelDescriptorOptions<TClient extends TWSObject, TServer extends TWSObject> {
34
+ interface ChannelPrimitiveOptions<TClient extends TWSObject, TServer extends TWSObject> {
14
35
  /**
15
36
  * WebSocket endpoint path (e.g., "/ws/chat")
16
37
  */
@@ -104,10 +125,10 @@ interface ChannelDescriptorOptions<TClient extends TWSObject, TServer extends TW
104
125
  * ```
105
126
  */
106
127
  declare const $channel: {
107
- <TClient extends TWSObject, TServer extends TWSObject>(options: ChannelDescriptorOptions<TClient, TServer>): ChannelDescriptor<TClient, TServer>;
108
- [KIND]: typeof ChannelDescriptor;
128
+ <TClient extends TWSObject, TServer extends TWSObject>(options: ChannelPrimitiveOptions<TClient, TServer>): ChannelPrimitive<TClient, TServer>;
129
+ [KIND]: typeof ChannelPrimitive;
109
130
  };
110
- declare class ChannelDescriptor<TClient extends TWSObject, TServer extends TWSObject> extends Descriptor<ChannelDescriptorOptions<TClient, TServer>> {}
131
+ declare class ChannelPrimitive<TClient extends TWSObject, TServer extends TWSObject> extends Primitive<ChannelPrimitiveOptions<TClient, TServer>> {}
111
132
  //#endregion
112
133
  //#region src/websocket/interfaces/WebSocketInterfaces.d.ts
113
134
  /**
@@ -155,11 +176,11 @@ declare enum WebSocketState {
155
176
  /**
156
177
  * WebSocket endpoint configuration (server-side)
157
178
  */
158
- interface WebSocketDescriptorOptions<TClient extends TWSObject, TServer extends TWSObject> {
179
+ interface WebSocketPrimitiveOptions<TClient extends TWSObject, TServer extends TWSObject> {
159
180
  /**
160
181
  * Channel definition with schema and path
161
182
  */
162
- channel: ChannelDescriptor<TClient, TServer>;
183
+ channel: ChannelPrimitive<TClient, TServer>;
163
184
  /**
164
185
  * Handler for incoming messages from clients
165
186
  */
@@ -318,7 +339,7 @@ declare abstract class WebSocketServerProvider {
318
339
  /**
319
340
  * Register a WebSocket endpoint with its channel configuration
320
341
  */
321
- abstract registerEndpoint<TClient extends TWSObject, TServer extends TWSObject>(config: WebSocketDescriptorOptions<TClient, TServer>): void;
342
+ abstract registerEndpoint<TClient extends TWSObject, TServer extends TWSObject>(config: WebSocketPrimitiveOptions<TClient, TServer>): void;
322
343
  /**
323
344
  * Emit a message to clients based on targeting criteria
324
345
  *
@@ -343,7 +364,7 @@ declare abstract class WebSocketServerProvider {
343
364
  abstract closeConnection(connectionId: string, code?: number, reason?: string): Promise<void>;
344
365
  }
345
366
  //#endregion
346
- //#region src/websocket/descriptors/$websocket.d.ts
367
+ //#region src/websocket/primitives/$websocket.d.ts
347
368
  /**
348
369
  * Defines a WebSocket server endpoint for a specific channel.
349
370
  *
@@ -386,10 +407,10 @@ declare abstract class WebSocketServerProvider {
386
407
  * ```
387
408
  */
388
409
  declare const $websocket: {
389
- <TClient extends TWSObject, TServer extends TWSObject>(options: WebSocketDescriptorOptions<TClient, TServer>): WebSocketDescriptor<TClient, TServer>;
390
- [KIND]: typeof WebSocketDescriptor;
410
+ <TClient extends TWSObject, TServer extends TWSObject>(options: WebSocketPrimitiveOptions<TClient, TServer>): WebSocketPrimitive<TClient, TServer>;
411
+ [KIND]: typeof WebSocketPrimitive;
391
412
  };
392
- declare class WebSocketDescriptor<TClient extends TWSObject, TServer extends TWSObject> extends Descriptor<WebSocketDescriptorOptions<TClient, TServer>> {
413
+ declare class WebSocketPrimitive<TClient extends TWSObject, TServer extends TWSObject> extends Primitive<WebSocketPrimitiveOptions<TClient, TServer>> {
393
414
  protected readonly webSocketServerProvider: WebSocketServerProvider;
394
415
  protected onInit(): void;
395
416
  /**
@@ -423,27 +444,6 @@ declare class WebSocketDescriptor<TClient extends TWSObject, TServer extends TWS
423
444
  emit(options: EmitOptions<TClient>): Promise<void>;
424
445
  }
425
446
  //#endregion
426
- //#region src/websocket/errors/WebSocketError.d.ts
427
- /**
428
- * Base WebSocket error class
429
- */
430
- declare class WebSocketError extends Error {
431
- readonly code?: number | undefined;
432
- constructor(message: string, code?: number | undefined);
433
- }
434
- /**
435
- * Error thrown when WebSocket connection fails
436
- */
437
- declare class WebSocketConnectionError extends WebSocketError {
438
- constructor(message: string, code?: number);
439
- }
440
- /**
441
- * Error thrown when WebSocket message validation fails
442
- */
443
- declare class WebSocketValidationError extends WebSocketError {
444
- constructor(message: string);
445
- }
446
- //#endregion
447
447
  //#region src/websocket/services/RoomManager.d.ts
448
448
  /**
449
449
  * Manages WebSocket room memberships
@@ -452,7 +452,7 @@ declare class WebSocketValidationError extends WebSocketError {
452
452
  * and messages can be targeted to specific rooms.
453
453
  */
454
454
  declare class RoomManager {
455
- protected readonly log: alepha_logger2.Logger;
455
+ protected readonly log: alepha_logger1.Logger;
456
456
  /**
457
457
  * Maps roomId → Set<connectionId>
458
458
  */
@@ -524,7 +524,7 @@ declare module "alepha" {
524
524
  * One connection can handle multiple rooms on the same channel.
525
525
  */
526
526
  declare class WebSocketChannelConnection<TClient extends TWSObject, TServer extends TWSObject> {
527
- protected readonly channel: ChannelDescriptor<TClient, TServer>;
527
+ protected readonly channel: ChannelPrimitive<TClient, TServer>;
528
528
  protected readonly options: {
529
529
  url?: string;
530
530
  autoReconnect?: boolean;
@@ -533,7 +533,7 @@ declare class WebSocketChannelConnection<TClient extends TWSObject, TServer exte
533
533
  };
534
534
  protected readonly env: Static<typeof envSchema$1>;
535
535
  protected readonly alepha: Alepha;
536
- protected readonly log: alepha_logger2.Logger;
536
+ protected readonly log: alepha_logger1.Logger;
537
537
  protected ws?: WebSocket;
538
538
  protected reconnectAttempts: number;
539
539
  protected reconnectTimer?: number;
@@ -549,7 +549,7 @@ declare class WebSocketChannelConnection<TClient extends TWSObject, TServer exte
549
549
  protected onConnectCallbacks: Set<() => void>;
550
550
  protected onDisconnectCallbacks: Set<() => void>;
551
551
  protected onErrorCallbacks: Set<(error: Error) => void>;
552
- constructor(channel: ChannelDescriptor<TClient, TServer>, options: {
552
+ constructor(channel: ChannelPrimitive<TClient, TServer>, options: {
553
553
  url?: string;
554
554
  autoReconnect?: boolean;
555
555
  reconnectInterval?: number;
@@ -607,7 +607,7 @@ declare class WebSocketChannelConnection<TClient extends TWSObject, TServer exte
607
607
  * One connection per channel, multiple rooms per connection.
608
608
  */
609
609
  declare class WebSocketClient {
610
- protected readonly log: alepha_logger2.Logger;
610
+ protected readonly log: alepha_logger1.Logger;
611
611
  protected readonly alepha: Alepha;
612
612
  protected readonly env: {
613
613
  WEBSOCKET_URL: string;
@@ -618,7 +618,7 @@ declare class WebSocketClient {
618
618
  /**
619
619
  * Subscribe to a room on a channel
620
620
  */
621
- subscribe<TClient extends TWSObject, TServer extends TWSObject>(roomId: string, channel: ChannelDescriptor<TClient, TServer>, handler: (message: Static<TClient>) => void, options?: {
621
+ subscribe<TClient extends TWSObject, TServer extends TWSObject>(roomId: string, channel: ChannelPrimitive<TClient, TServer>, handler: (message: Static<TClient>) => void, options?: {
622
622
  url?: string;
623
623
  autoReconnect?: boolean;
624
624
  reconnectInterval?: number;
@@ -630,11 +630,11 @@ declare class WebSocketClient {
630
630
  /**
631
631
  * Send message to a room on a channel
632
632
  */
633
- send<TClient extends TWSObject, TServer extends TWSObject>(roomId: string, channel: ChannelDescriptor<TClient, TServer>, message: Static<TServer>): Promise<void>;
633
+ send<TClient extends TWSObject, TServer extends TWSObject>(roomId: string, channel: ChannelPrimitive<TClient, TServer>, message: Static<TServer>): Promise<void>;
634
634
  /**
635
635
  * Get connection for a channel
636
636
  */
637
- getConnection<TClient extends TWSObject, TServer extends TWSObject>(channel: ChannelDescriptor<TClient, TServer>): WebSocketChannelConnection<TClient, TServer> | undefined;
637
+ getConnection<TClient extends TWSObject, TServer extends TWSObject>(channel: ChannelPrimitive<TClient, TServer>): WebSocketChannelConnection<TClient, TServer> | undefined;
638
638
  /**
639
639
  * Disconnect all connections
640
640
  */
@@ -694,7 +694,7 @@ declare const webSocketMessageSchema: {
694
694
  * - Horizontal scaling without losing messages
695
695
  */
696
696
  declare class WebSocketTopicService {
697
- protected readonly log: alepha_logger2.Logger;
697
+ protected readonly log: alepha_logger1.Logger;
698
698
  /**
699
699
  * Handler function to be called when a message is received from the topic
700
700
  * This is set by the WebSocket provider during initialization
@@ -703,7 +703,7 @@ declare class WebSocketTopicService {
703
703
  /**
704
704
  * Topic for distributing WebSocket messages across server instances
705
705
  */
706
- readonly topic: alepha_topic0.TopicDescriptor<{
706
+ readonly topic: alepha_topic0.TopicPrimitive<{
707
707
  payload: alepha42.TObject<{
708
708
  /**
709
709
  * Channel path (e.g., "/ws/chat")
@@ -756,23 +756,23 @@ declare class NodeWebSocketServerProvider extends WebSocketServerProvider {
756
756
  protected readonly alepha: Alepha;
757
757
  protected readonly roomManager: RoomManager;
758
758
  protected readonly topicService: WebSocketTopicService;
759
- protected readonly log: alepha_logger2.Logger;
759
+ protected readonly log: alepha_logger1.Logger;
760
760
  protected readonly env: {
761
761
  WEBSOCKET_PATH: string;
762
762
  };
763
763
  protected wss?: WebSocketServer;
764
- protected endpoints: Map<string, WebSocketDescriptorOptions<any, any>>;
764
+ protected endpoints: Map<string, WebSocketPrimitiveOptions<any, any>>;
765
765
  protected connections: Map<string, WebSocketConnection>;
766
766
  protected userConnections: Map<string, Set<string>>;
767
767
  protected nextConnectionId: number;
768
- registerEndpoint<TClient extends TWSObject, TServer extends TWSObject>(config: WebSocketDescriptorOptions<TClient, TServer>): void;
768
+ registerEndpoint<TClient extends TWSObject, TServer extends TWSObject>(config: WebSocketPrimitiveOptions<TClient, TServer>): void;
769
769
  emit<TClient extends TWSObject>(channelPath: string, options: EmitOptions<TClient>): Promise<void>;
770
770
  getConnections(): WebSocketConnection[];
771
771
  getRoomConnections(roomId: string): WebSocketConnection[];
772
772
  getUserConnections(userId: string): WebSocketConnection[];
773
773
  closeConnection(connectionId: string, code?: number, reason?: string): Promise<void>;
774
774
  protected handleUpgrade(request: IncomingMessage, socket: any, head: Buffer): boolean;
775
- protected handleConnection<TClient extends TWSObject, TServer extends TWSObject>(ws: WebSocket$1, endpoint: WebSocketDescriptorOptions<TClient, TServer>, request: IncomingMessage): void;
775
+ protected handleConnection<TClient extends TWSObject, TServer extends TWSObject>(ws: WebSocket$1, endpoint: WebSocketPrimitiveOptions<TClient, TServer>, request: IncomingMessage): void;
776
776
  protected extractRoomIds(url: URL): string[];
777
777
  /**
778
778
  * Send message to local connections based on targeting criteria
@@ -785,9 +785,9 @@ declare class NodeWebSocketServerProvider extends WebSocketServerProvider {
785
785
  exceptConnectionIds?: string[];
786
786
  exceptUserIds?: string[];
787
787
  }): Promise<void>;
788
- protected readonly start: alepha42.HookDescriptor<"start">;
789
- protected readonly ready: alepha42.HookDescriptor<"ready">;
790
- protected readonly stop: alepha42.HookDescriptor<"stop">;
788
+ protected readonly start: alepha42.HookPrimitive<"start">;
789
+ protected readonly ready: alepha42.HookPrimitive<"ready">;
790
+ protected readonly stop: alepha42.HookPrimitive<"stop">;
791
791
  }
792
792
  declare class NodeWebSocketConnection implements WebSocketConnection {
793
793
  readonly id: string;
@@ -795,10 +795,10 @@ declare class NodeWebSocketConnection implements WebSocketConnection {
795
795
  readonly roomIds: string[];
796
796
  protected readonly ws: WebSocket$1;
797
797
  protected readonly provider: NodeWebSocketServerProvider;
798
- protected readonly endpoint: WebSocketDescriptorOptions<any, any>;
799
- protected readonly log: alepha_logger2.Logger;
798
+ protected readonly endpoint: WebSocketPrimitiveOptions<any, any>;
799
+ protected readonly log: alepha_logger1.Logger;
800
800
  metadata?: Record<string, any>;
801
- constructor(id: string, userId: string | undefined, roomIds: string[], ws: WebSocket$1, provider: NodeWebSocketServerProvider, endpoint: WebSocketDescriptorOptions<any, any>);
801
+ constructor(id: string, userId: string | undefined, roomIds: string[], ws: WebSocket$1, provider: NodeWebSocketServerProvider, endpoint: WebSocketPrimitiveOptions<any, any>);
802
802
  get readyState(): WebSocketState;
803
803
  send(message: any): Promise<void>;
804
804
  close(code?: number, reason?: string): Promise<void>;
@@ -845,7 +845,7 @@ declare module "alepha" {
845
845
  /**
846
846
  * Provides real-time bidirectional communication using WebSockets.
847
847
  *
848
- * The WebSockets module enables building real-time applications using the `$websocket` descriptor
848
+ * The WebSockets module enables building real-time applications using the `$websocket` primitive
849
849
  * on class properties. It provides automatic connection management, message routing, type-safe
850
850
  * message handling, and seamless integration with other Alepha modules.
851
851
  *
@@ -857,5 +857,5 @@ declare module "alepha" {
857
857
  */
858
858
  declare const AlephaWebSocket: alepha42.Service<alepha42.Module>;
859
859
  //#endregion
860
- export { $channel, $websocket, AlephaWebSocket, ChannelDescriptor, ChannelDescriptorOptions, EmitOptions, NodeWebSocketConnection, NodeWebSocketServerProvider, RoomManager, TWSObject, WebSocketChannelConnection, WebSocketClient, WebSocketConnection, WebSocketConnectionError, WebSocketDescriptor, WebSocketDescriptorOptions, WebSocketError, WebSocketHandler, WebSocketHandlerContext, WebSocketServerProvider, WebSocketState, WebSocketTopicService, WebSocketValidationError };
860
+ export { $channel, $websocket, AlephaWebSocket, ChannelPrimitive, ChannelPrimitiveOptions, EmitOptions, NodeWebSocketConnection, NodeWebSocketServerProvider, RoomManager, TWSObject, WebSocketChannelConnection, WebSocketClient, WebSocketConnection, WebSocketConnectionError, WebSocketError, WebSocketHandler, WebSocketHandlerContext, WebSocketPrimitive, WebSocketPrimitiveOptions, WebSocketServerProvider, WebSocketState, WebSocketTopicService, WebSocketValidationError };
861
861
  //# sourceMappingURL=index.d.ts.map
@@ -1,10 +1,10 @@
1
- import { $env, $hook, $inject, $module, Alepha, AlephaError, Descriptor, KIND, TypeBoxValue, createDescriptor, t } from "alepha";
1
+ import { $env, $hook, $inject, $module, Alepha, AlephaError, KIND, Primitive, TypeBoxValue, createPrimitive, t } from "alepha";
2
2
  import { AlephaServer } from "alepha/server";
3
3
  import { $topic, AlephaTopic } from "alepha/topic";
4
4
  import { $logger } from "alepha/logger";
5
5
  import { WebSocket as WebSocket$1, WebSocketServer } from "ws";
6
6
 
7
- //#region src/websocket/descriptors/$channel.ts
7
+ //#region src/websocket/primitives/$channel.ts
8
8
  /**
9
9
  * Defines a WebSocket channel with specified client and server message schemas.
10
10
  *
@@ -68,10 +68,10 @@ import { WebSocket as WebSocket$1, WebSocketServer } from "ws";
68
68
  * ```
69
69
  */
70
70
  const $channel = (options) => {
71
- return createDescriptor(ChannelDescriptor, options);
71
+ return createPrimitive(ChannelPrimitive, options);
72
72
  };
73
- var ChannelDescriptor = class extends Descriptor {};
74
- $channel[KIND] = ChannelDescriptor;
73
+ var ChannelPrimitive = class extends Primitive {};
74
+ $channel[KIND] = ChannelPrimitive;
75
75
 
76
76
  //#endregion
77
77
  //#region src/websocket/providers/WebSocketServerProvider.ts
@@ -84,7 +84,7 @@ $channel[KIND] = ChannelDescriptor;
84
84
  var WebSocketServerProvider = class {};
85
85
 
86
86
  //#endregion
87
- //#region src/websocket/descriptors/$websocket.ts
87
+ //#region src/websocket/primitives/$websocket.ts
88
88
  /**
89
89
  * Defines a WebSocket server endpoint for a specific channel.
90
90
  *
@@ -127,9 +127,9 @@ var WebSocketServerProvider = class {};
127
127
  * ```
128
128
  */
129
129
  const $websocket = (options) => {
130
- return createDescriptor(WebSocketDescriptor, options);
130
+ return createPrimitive(WebSocketPrimitive, options);
131
131
  };
132
- var WebSocketDescriptor = class extends Descriptor {
132
+ var WebSocketPrimitive = class extends Primitive {
133
133
  webSocketServerProvider = $inject(WebSocketServerProvider);
134
134
  onInit() {
135
135
  this.webSocketServerProvider.registerEndpoint(this.options);
@@ -166,7 +166,7 @@ var WebSocketDescriptor = class extends Descriptor {
166
166
  await this.webSocketServerProvider.emit(this.options.channel.options.path, options);
167
167
  }
168
168
  };
169
- $websocket[KIND] = WebSocketDescriptor;
169
+ $websocket[KIND] = WebSocketPrimitive;
170
170
 
171
171
  //#endregion
172
172
  //#region src/websocket/errors/WebSocketError.ts
@@ -568,7 +568,7 @@ var NodeWebSocketServerProvider = class extends WebSocketServerProvider {
568
568
  on: "ready",
569
569
  handler: async () => {
570
570
  if (this.alepha.isServerless() || !this.wss) return;
571
- const httpServer = this.alepha.state.get("alepha.node.server");
571
+ const httpServer = this.alepha.store.get("alepha.node.server");
572
572
  if (httpServer) {
573
573
  httpServer.on("upgrade", (request, socket, head) => {
574
574
  this.handleUpgrade(request, socket, head);
@@ -1068,7 +1068,7 @@ var WebSocketClient = class {
1068
1068
  /**
1069
1069
  * Provides real-time bidirectional communication using WebSockets.
1070
1070
  *
1071
- * The WebSockets module enables building real-time applications using the `$websocket` descriptor
1071
+ * The WebSockets module enables building real-time applications using the `$websocket` primitive
1072
1072
  * on class properties. It provides automatic connection management, message routing, type-safe
1073
1073
  * message handling, and seamless integration with other Alepha modules.
1074
1074
  *
@@ -1080,7 +1080,7 @@ var WebSocketClient = class {
1080
1080
  */
1081
1081
  const AlephaWebSocket = $module({
1082
1082
  name: "alepha.websocket",
1083
- descriptors: [$channel, $websocket],
1083
+ primitives: [$channel, $websocket],
1084
1084
  services: [
1085
1085
  WebSocketServerProvider,
1086
1086
  NodeWebSocketServerProvider,
@@ -1098,5 +1098,5 @@ const AlephaWebSocket = $module({
1098
1098
  });
1099
1099
 
1100
1100
  //#endregion
1101
- export { $channel, $websocket, AlephaWebSocket, ChannelDescriptor, NodeWebSocketConnection, NodeWebSocketServerProvider, RoomManager, WebSocketChannelConnection, WebSocketClient, WebSocketConnectionError, WebSocketDescriptor, WebSocketError, WebSocketServerProvider, WebSocketState, WebSocketTopicService, WebSocketValidationError };
1101
+ export { $channel, $websocket, AlephaWebSocket, ChannelPrimitive, NodeWebSocketConnection, NodeWebSocketServerProvider, RoomManager, WebSocketChannelConnection, WebSocketClient, WebSocketConnectionError, WebSocketError, WebSocketPrimitive, WebSocketServerProvider, WebSocketState, WebSocketTopicService, WebSocketValidationError };
1102
1102
  //# sourceMappingURL=index.js.map