alepha 0.12.1 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/dist/api-notifications/index.d.ts +111 -111
  2. package/dist/api-users/index.d.ts +1240 -1240
  3. package/dist/api-verifications/index.d.ts +94 -94
  4. package/dist/cli/{dist-Sz2EXvQX.cjs → dist-Dl9Vl7Ur.js} +17 -13
  5. package/dist/cli/{dist-BBPjuQ56.js.map → dist-Dl9Vl7Ur.js.map} +1 -1
  6. package/dist/cli/index.d.ts +3 -11
  7. package/dist/cli/index.js +106 -74
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/email/index.js +71 -73
  10. package/dist/email/index.js.map +1 -1
  11. package/dist/orm/index.d.ts +1 -1
  12. package/dist/orm/index.js.map +1 -1
  13. package/dist/queue/index.d.ts +4 -4
  14. package/dist/redis/index.d.ts +10 -10
  15. package/dist/retry/index.d.ts +1 -1
  16. package/dist/retry/index.js +2 -2
  17. package/dist/retry/index.js.map +1 -1
  18. package/dist/scheduler/index.d.ts +6 -6
  19. package/dist/server/index.js +1 -1
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server-auth/index.d.ts +193 -193
  22. package/dist/server-health/index.d.ts +17 -17
  23. package/dist/server-links/index.d.ts +34 -34
  24. package/dist/server-metrics/index.js +170 -174
  25. package/dist/server-metrics/index.js.map +1 -1
  26. package/dist/server-security/index.d.ts +9 -9
  27. package/dist/vite/index.js +4 -5
  28. package/dist/vite/index.js.map +1 -1
  29. package/dist/websocket/index.d.ts +7 -7
  30. package/package.json +52 -103
  31. package/src/cli/apps/AlephaPackageBuilderCli.ts +7 -2
  32. package/src/cli/assets/appRouterTs.ts +9 -0
  33. package/src/cli/assets/indexHtml.ts +2 -1
  34. package/src/cli/assets/mainBrowserTs.ts +10 -0
  35. package/src/cli/commands/CoreCommands.ts +6 -5
  36. package/src/cli/commands/DrizzleCommands.ts +65 -57
  37. package/src/cli/commands/VerifyCommands.ts +1 -1
  38. package/src/cli/services/ProjectUtils.ts +44 -38
  39. package/src/orm/providers/DrizzleKitProvider.ts +1 -1
  40. package/src/retry/descriptors/$retry.ts +5 -3
  41. package/src/server/providers/NodeHttpServerProvider.ts +1 -1
  42. package/src/vite/helpers/boot.ts +3 -3
  43. package/dist/api-files/index.cjs +0 -1293
  44. package/dist/api-files/index.cjs.map +0 -1
  45. package/dist/api-files/index.d.cts +0 -829
  46. package/dist/api-jobs/index.cjs +0 -274
  47. package/dist/api-jobs/index.cjs.map +0 -1
  48. package/dist/api-jobs/index.d.cts +0 -654
  49. package/dist/api-notifications/index.cjs +0 -380
  50. package/dist/api-notifications/index.cjs.map +0 -1
  51. package/dist/api-notifications/index.d.cts +0 -289
  52. package/dist/api-parameters/index.cjs +0 -66
  53. package/dist/api-parameters/index.cjs.map +0 -1
  54. package/dist/api-parameters/index.d.cts +0 -84
  55. package/dist/api-users/index.cjs +0 -6009
  56. package/dist/api-users/index.cjs.map +0 -1
  57. package/dist/api-users/index.d.cts +0 -4740
  58. package/dist/api-verifications/index.cjs +0 -407
  59. package/dist/api-verifications/index.cjs.map +0 -1
  60. package/dist/api-verifications/index.d.cts +0 -207
  61. package/dist/batch/index.cjs +0 -408
  62. package/dist/batch/index.cjs.map +0 -1
  63. package/dist/batch/index.d.cts +0 -330
  64. package/dist/bin/index.cjs +0 -17
  65. package/dist/bin/index.cjs.map +0 -1
  66. package/dist/bin/index.d.cts +0 -1
  67. package/dist/bucket/index.cjs +0 -303
  68. package/dist/bucket/index.cjs.map +0 -1
  69. package/dist/bucket/index.d.cts +0 -355
  70. package/dist/cache/index.cjs +0 -241
  71. package/dist/cache/index.cjs.map +0 -1
  72. package/dist/cache/index.d.cts +0 -202
  73. package/dist/cache-redis/index.cjs +0 -84
  74. package/dist/cache-redis/index.cjs.map +0 -1
  75. package/dist/cache-redis/index.d.cts +0 -40
  76. package/dist/cli/chunk-DSlc6foC.cjs +0 -43
  77. package/dist/cli/dist-BBPjuQ56.js +0 -2778
  78. package/dist/cli/dist-Sz2EXvQX.cjs.map +0 -1
  79. package/dist/cli/index.cjs +0 -1241
  80. package/dist/cli/index.cjs.map +0 -1
  81. package/dist/cli/index.d.cts +0 -422
  82. package/dist/command/index.cjs +0 -693
  83. package/dist/command/index.cjs.map +0 -1
  84. package/dist/command/index.d.cts +0 -340
  85. package/dist/core/index.cjs +0 -2264
  86. package/dist/core/index.cjs.map +0 -1
  87. package/dist/core/index.d.cts +0 -1927
  88. package/dist/datetime/index.cjs +0 -318
  89. package/dist/datetime/index.cjs.map +0 -1
  90. package/dist/datetime/index.d.cts +0 -145
  91. package/dist/email/index.cjs +0 -10874
  92. package/dist/email/index.cjs.map +0 -1
  93. package/dist/email/index.d.cts +0 -186
  94. package/dist/fake/index.cjs +0 -34641
  95. package/dist/fake/index.cjs.map +0 -1
  96. package/dist/fake/index.d.cts +0 -74
  97. package/dist/file/index.cjs +0 -1212
  98. package/dist/file/index.cjs.map +0 -1
  99. package/dist/file/index.d.cts +0 -698
  100. package/dist/lock/index.cjs +0 -226
  101. package/dist/lock/index.cjs.map +0 -1
  102. package/dist/lock/index.d.cts +0 -361
  103. package/dist/lock-redis/index.cjs +0 -113
  104. package/dist/lock-redis/index.cjs.map +0 -1
  105. package/dist/lock-redis/index.d.cts +0 -24
  106. package/dist/logger/index.cjs +0 -521
  107. package/dist/logger/index.cjs.map +0 -1
  108. package/dist/logger/index.d.cts +0 -281
  109. package/dist/orm/index.cjs +0 -2986
  110. package/dist/orm/index.cjs.map +0 -1
  111. package/dist/orm/index.d.cts +0 -2213
  112. package/dist/queue/index.cjs +0 -1044
  113. package/dist/queue/index.cjs.map +0 -1
  114. package/dist/queue/index.d.cts +0 -1265
  115. package/dist/queue-redis/index.cjs +0 -873
  116. package/dist/queue-redis/index.cjs.map +0 -1
  117. package/dist/queue-redis/index.d.cts +0 -82
  118. package/dist/redis/index.cjs +0 -153
  119. package/dist/redis/index.cjs.map +0 -1
  120. package/dist/redis/index.d.cts +0 -82
  121. package/dist/retry/index.cjs +0 -146
  122. package/dist/retry/index.cjs.map +0 -1
  123. package/dist/retry/index.d.cts +0 -172
  124. package/dist/router/index.cjs +0 -111
  125. package/dist/router/index.cjs.map +0 -1
  126. package/dist/router/index.d.cts +0 -46
  127. package/dist/scheduler/index.cjs +0 -576
  128. package/dist/scheduler/index.cjs.map +0 -1
  129. package/dist/scheduler/index.d.cts +0 -145
  130. package/dist/security/index.cjs +0 -2402
  131. package/dist/security/index.cjs.map +0 -1
  132. package/dist/security/index.d.cts +0 -598
  133. package/dist/server/index.cjs +0 -1680
  134. package/dist/server/index.cjs.map +0 -1
  135. package/dist/server/index.d.cts +0 -810
  136. package/dist/server-auth/index.cjs +0 -3146
  137. package/dist/server-auth/index.cjs.map +0 -1
  138. package/dist/server-auth/index.d.cts +0 -1164
  139. package/dist/server-cache/index.cjs +0 -252
  140. package/dist/server-cache/index.cjs.map +0 -1
  141. package/dist/server-cache/index.d.cts +0 -164
  142. package/dist/server-compress/index.cjs +0 -141
  143. package/dist/server-compress/index.cjs.map +0 -1
  144. package/dist/server-compress/index.d.cts +0 -38
  145. package/dist/server-cookies/index.cjs +0 -234
  146. package/dist/server-cookies/index.cjs.map +0 -1
  147. package/dist/server-cookies/index.d.cts +0 -144
  148. package/dist/server-cors/index.cjs +0 -201
  149. package/dist/server-cors/index.cjs.map +0 -1
  150. package/dist/server-cors/index.d.cts +0 -140
  151. package/dist/server-health/index.cjs +0 -62
  152. package/dist/server-health/index.cjs.map +0 -1
  153. package/dist/server-health/index.d.cts +0 -58
  154. package/dist/server-helmet/index.cjs +0 -131
  155. package/dist/server-helmet/index.cjs.map +0 -1
  156. package/dist/server-helmet/index.d.cts +0 -97
  157. package/dist/server-links/index.cjs +0 -992
  158. package/dist/server-links/index.cjs.map +0 -1
  159. package/dist/server-links/index.d.cts +0 -513
  160. package/dist/server-metrics/index.cjs +0 -4535
  161. package/dist/server-metrics/index.cjs.map +0 -1
  162. package/dist/server-metrics/index.d.cts +0 -35
  163. package/dist/server-multipart/index.cjs +0 -237
  164. package/dist/server-multipart/index.cjs.map +0 -1
  165. package/dist/server-multipart/index.d.cts +0 -50
  166. package/dist/server-proxy/index.cjs +0 -186
  167. package/dist/server-proxy/index.cjs.map +0 -1
  168. package/dist/server-proxy/index.d.cts +0 -234
  169. package/dist/server-rate-limit/index.cjs +0 -241
  170. package/dist/server-rate-limit/index.cjs.map +0 -1
  171. package/dist/server-rate-limit/index.d.cts +0 -183
  172. package/dist/server-security/index.cjs +0 -316
  173. package/dist/server-security/index.cjs.map +0 -1
  174. package/dist/server-security/index.d.cts +0 -173
  175. package/dist/server-static/index.cjs +0 -170
  176. package/dist/server-static/index.cjs.map +0 -1
  177. package/dist/server-static/index.d.cts +0 -121
  178. package/dist/server-swagger/index.cjs +0 -1021
  179. package/dist/server-swagger/index.cjs.map +0 -1
  180. package/dist/server-swagger/index.d.cts +0 -382
  181. package/dist/sms/index.cjs +0 -221
  182. package/dist/sms/index.cjs.map +0 -1
  183. package/dist/sms/index.d.cts +0 -130
  184. package/dist/thread/index.cjs +0 -350
  185. package/dist/thread/index.cjs.map +0 -1
  186. package/dist/thread/index.d.cts +0 -260
  187. package/dist/topic/index.cjs +0 -282
  188. package/dist/topic/index.cjs.map +0 -1
  189. package/dist/topic/index.d.cts +0 -523
  190. package/dist/topic-redis/index.cjs +0 -71
  191. package/dist/topic-redis/index.cjs.map +0 -1
  192. package/dist/topic-redis/index.d.cts +0 -42
  193. package/dist/vite/index.cjs +0 -1077
  194. package/dist/vite/index.cjs.map +0 -1
  195. package/dist/vite/index.d.cts +0 -542
  196. package/dist/websocket/index.cjs +0 -1117
  197. package/dist/websocket/index.cjs.map +0 -1
  198. package/dist/websocket/index.d.cts +0 -861
@@ -1,1117 +0,0 @@
1
- let alepha = require("alepha");
2
- let alepha_server = require("alepha/server");
3
- let alepha_topic = require("alepha/topic");
4
- let alepha_logger = require("alepha/logger");
5
- let ws = require("ws");
6
-
7
- //#region src/websocket/descriptors/$channel.ts
8
- /**
9
- * Defines a WebSocket channel with specified client and server message schemas.
10
- *
11
- * Channels must be defined as class properties to be registered in the Alepha context.
12
- * They define the "vocabulary" for communication - the schema for messages flowing
13
- * in both directions (server→client and client→server).
14
- *
15
- * @example Server-side with $websocket
16
- * ```typescript
17
- * class ChatController {
18
- * // Channel must be defined inside a class
19
- * chatChannel = $channel({
20
- * path: "/ws/chat",
21
- * description: "Real-time chat channel",
22
- * schema: {
23
- * // Server → Client messages
24
- * in: t.union([
25
- * t.object({
26
- * type: t.const("append"),
27
- * content: t.text(),
28
- * username: t.text()
29
- * }),
30
- * t.object({
31
- * type: t.const("system"),
32
- * message: t.text()
33
- * })
34
- * ]),
35
- * // Client → Server messages
36
- * out: t.object({
37
- * content: t.text()
38
- * })
39
- * }
40
- * });
41
- *
42
- * chat = $websocket({
43
- * channel: this.chatChannel,
44
- * handler: async ({ message, reply }) => {
45
- * await reply({
46
- * message: { type: "append", content: message.content, username: "user" }
47
- * });
48
- * }
49
- * });
50
- * }
51
- * ```
52
- *
53
- * @example Browser-side with useRoom
54
- * ```typescript
55
- * // Define channel in a class for browser context
56
- * class ChatClient {
57
- * chatChannel = $channel({
58
- * path: "/ws/chat",
59
- * schema: { in: inSchema, out: outSchema }
60
- * });
61
- * }
62
- *
63
- * // Use in React component
64
- * function Chat() {
65
- * const client = useInject(ChatClient);
66
- * const chat = useRoom({ roomId: "lobby", channel: client.chatChannel, handler: ... }, []);
67
- * }
68
- * ```
69
- */
70
- const $channel = (options) => {
71
- return (0, alepha.createDescriptor)(ChannelDescriptor, options);
72
- };
73
- var ChannelDescriptor = class extends alepha.Descriptor {};
74
- $channel[alepha.KIND] = ChannelDescriptor;
75
-
76
- //#endregion
77
- //#region src/websocket/providers/WebSocketServerProvider.ts
78
- /**
79
- * Abstract WebSocket server provider
80
- *
81
- * This class provides the base interface that must be implemented by
82
- * platform-specific providers (Node.js, Browser, etc.)
83
- */
84
- var WebSocketServerProvider = class {};
85
-
86
- //#endregion
87
- //#region src/websocket/descriptors/$websocket.ts
88
- /**
89
- * Defines a WebSocket server endpoint for a specific channel.
90
- *
91
- * Server-side only. Creates a WebSocket endpoint that:
92
- * - Accepts connections from clients
93
- * - Validates incoming messages against the channel schema
94
- * - Provides room-based messaging
95
- * - Integrates with alepha/security for authentication (optional)
96
- * - Supports horizontal scaling via alepha/topic
97
- *
98
- * @example
99
- * ```typescript
100
- * class ChatController {
101
- * chat = $websocket({
102
- * channel: chatChannel,
103
- * handler: async ({ connectionId, userId, roomId, message, reply }) => {
104
- * // Broadcast to all in room except sender
105
- * await reply({
106
- * message: {
107
- * type: "append",
108
- * username: userId,
109
- * content: message.content
110
- * },
111
- * exceptSelf: true
112
- * });
113
- * }
114
- * });
115
- *
116
- * async broadcastAnnouncement(roomId: string, text: string) {
117
- * await this.chat.emit({
118
- * roomId,
119
- * message: {
120
- * type: "append",
121
- * username: "System",
122
- * content: text
123
- * }
124
- * });
125
- * }
126
- * }
127
- * ```
128
- */
129
- const $websocket = (options) => {
130
- return (0, alepha.createDescriptor)(WebSocketDescriptor, options);
131
- };
132
- var WebSocketDescriptor = class extends alepha.Descriptor {
133
- webSocketServerProvider = (0, alepha.$inject)(WebSocketServerProvider);
134
- onInit() {
135
- this.webSocketServerProvider.registerEndpoint(this.options);
136
- }
137
- /**
138
- * Emit message to clients
139
- *
140
- * Send messages from the server to connected clients based on targeting criteria.
141
- * Messages are distributed across all server instances via pub/sub.
142
- *
143
- * @example
144
- * ```typescript
145
- * // Send to specific room
146
- * await websocket.emit({
147
- * roomId: "room-123",
148
- * message: { type: "update", data: {...} }
149
- * });
150
- *
151
- * // Send to specific user (all their connections)
152
- * await websocket.emit({
153
- * userId: "user-456",
154
- * message: { type: "notification", text: "Hello!" }
155
- * });
156
- *
157
- * // Send to multiple rooms, except certain users
158
- * await websocket.emit({
159
- * roomIds: ["room-1", "room-2"],
160
- * exceptUserIds: ["user-123"],
161
- * message: { type: "broadcast", content: "System announcement" }
162
- * });
163
- * ```
164
- */
165
- async emit(options) {
166
- await this.webSocketServerProvider.emit(this.options.channel.options.path, options);
167
- }
168
- };
169
- $websocket[alepha.KIND] = WebSocketDescriptor;
170
-
171
- //#endregion
172
- //#region src/websocket/errors/WebSocketError.ts
173
- /**
174
- * Base WebSocket error class
175
- */
176
- var WebSocketError = class extends Error {
177
- constructor(message, code) {
178
- super(message);
179
- this.code = code;
180
- this.name = "WebSocketError";
181
- }
182
- };
183
- /**
184
- * Error thrown when WebSocket connection fails
185
- */
186
- var WebSocketConnectionError = class extends WebSocketError {
187
- constructor(message, code) {
188
- super(message, code);
189
- this.name = "WebSocketConnectionError";
190
- }
191
- };
192
- /**
193
- * Error thrown when WebSocket message validation fails
194
- */
195
- var WebSocketValidationError = class extends WebSocketError {
196
- constructor(message) {
197
- super(message);
198
- this.name = "WebSocketValidationError";
199
- }
200
- };
201
-
202
- //#endregion
203
- //#region src/websocket/services/RoomManager.ts
204
- /**
205
- * Manages WebSocket room memberships
206
- *
207
- * Rooms are logical groupings of connections. A connection can be in multiple rooms,
208
- * and messages can be targeted to specific rooms.
209
- */
210
- var RoomManager = class {
211
- log = (0, alepha_logger.$logger)();
212
- /**
213
- * Maps roomId → Set<connectionId>
214
- */
215
- rooms = /* @__PURE__ */ new Map();
216
- /**
217
- * Maps connectionId → Set<roomId>
218
- * Inverse index for fast lookup of connection's rooms
219
- */
220
- connectionRooms = /* @__PURE__ */ new Map();
221
- /**
222
- * Join a connection to one or more rooms
223
- */
224
- joinRooms(connectionId, roomIds) {
225
- for (const roomId of roomIds) this.joinRoom(connectionId, roomId);
226
- }
227
- /**
228
- * Join a connection to a room
229
- */
230
- joinRoom(connectionId, roomId) {
231
- let room = this.rooms.get(roomId);
232
- if (!room) {
233
- room = /* @__PURE__ */ new Set();
234
- this.rooms.set(roomId, room);
235
- }
236
- room.add(connectionId);
237
- let connRooms = this.connectionRooms.get(connectionId);
238
- if (!connRooms) {
239
- connRooms = /* @__PURE__ */ new Set();
240
- this.connectionRooms.set(connectionId, connRooms);
241
- }
242
- connRooms.add(roomId);
243
- this.log.debug(`Connection ${connectionId} joined room ${roomId}`);
244
- }
245
- /**
246
- * Leave a connection from a room
247
- */
248
- leaveRoom(connectionId, roomId) {
249
- const room = this.rooms.get(roomId);
250
- if (room) {
251
- room.delete(connectionId);
252
- if (room.size === 0) this.rooms.delete(roomId);
253
- }
254
- const connRooms = this.connectionRooms.get(connectionId);
255
- if (connRooms) {
256
- connRooms.delete(roomId);
257
- if (connRooms.size === 0) this.connectionRooms.delete(connectionId);
258
- }
259
- this.log.debug(`Connection ${connectionId} left room ${roomId}`);
260
- }
261
- /**
262
- * Remove a connection from all rooms
263
- */
264
- leaveAllRooms(connectionId) {
265
- const connRooms = this.connectionRooms.get(connectionId);
266
- if (!connRooms) return;
267
- for (const roomId of connRooms) {
268
- const room = this.rooms.get(roomId);
269
- if (room) {
270
- room.delete(connectionId);
271
- if (room.size === 0) this.rooms.delete(roomId);
272
- }
273
- }
274
- this.connectionRooms.delete(connectionId);
275
- this.log.debug(`Connection ${connectionId} left all rooms`);
276
- }
277
- /**
278
- * Get all connection IDs in a room
279
- */
280
- getRoomConnections(roomId) {
281
- const room = this.rooms.get(roomId);
282
- return room ? Array.from(room) : [];
283
- }
284
- /**
285
- * Get all room IDs for a connection
286
- */
287
- getConnectionRooms(connectionId) {
288
- const connRooms = this.connectionRooms.get(connectionId);
289
- return connRooms ? Array.from(connRooms) : [];
290
- }
291
- /**
292
- * Check if a connection is in a room
293
- */
294
- isInRoom(connectionId, roomId) {
295
- const connRooms = this.connectionRooms.get(connectionId);
296
- return connRooms ? connRooms.has(roomId) : false;
297
- }
298
- /**
299
- * Get all active rooms
300
- */
301
- getAllRooms() {
302
- return Array.from(this.rooms.keys());
303
- }
304
- /**
305
- * Get total number of connections across all rooms
306
- */
307
- getTotalConnections() {
308
- return this.connectionRooms.size;
309
- }
310
- /**
311
- * Get room statistics
312
- */
313
- getStats() {
314
- const roomSizes = /* @__PURE__ */ new Map();
315
- for (const [roomId, connections] of this.rooms) roomSizes.set(roomId, connections.size);
316
- return {
317
- totalRooms: this.rooms.size,
318
- totalConnections: this.connectionRooms.size,
319
- roomSizes
320
- };
321
- }
322
- };
323
-
324
- //#endregion
325
- //#region src/websocket/services/WebSocketTopicService.ts
326
- /**
327
- * WebSocket message distribution event
328
- */
329
- const webSocketMessageSchema = { payload: alepha.t.object({
330
- channelPath: alepha.t.text(),
331
- roomIds: alepha.t.optional(alepha.t.array(alepha.t.text())),
332
- userIds: alepha.t.optional(alepha.t.array(alepha.t.text())),
333
- connectionIds: alepha.t.optional(alepha.t.array(alepha.t.text())),
334
- exceptConnectionIds: alepha.t.optional(alepha.t.array(alepha.t.text())),
335
- exceptUserIds: alepha.t.optional(alepha.t.array(alepha.t.text())),
336
- message: alepha.t.any()
337
- }) };
338
- /**
339
- * WebSocket Topic Service
340
- *
341
- * Manages pub/sub messaging for WebSocket connections across multiple server instances.
342
- * Uses alepha/topic for cross-instance message distribution, enabling horizontal scaling.
343
- *
344
- * When a WebSocket message needs to be sent:
345
- * 1. Server instance A publishes to the topic
346
- * 2. All server instances (A, B, C, etc.) receive the message
347
- * 3. Each instance sends to its local connections that match the criteria
348
- *
349
- * This enables:
350
- * - Multiple server instances handling WebSocket connections
351
- * - Redis-backed message distribution (with alepha/topic/redis)
352
- * - Horizontal scaling without losing messages
353
- */
354
- var WebSocketTopicService = class {
355
- log = (0, alepha_logger.$logger)();
356
- /**
357
- * Handler function to be called when a message is received from the topic
358
- * This is set by the WebSocket provider during initialization
359
- */
360
- messageHandler;
361
- /**
362
- * Topic for distributing WebSocket messages across server instances
363
- */
364
- topic = (0, alepha_topic.$topic)({
365
- name: "websocket:broadcast",
366
- description: "Distributes WebSocket messages across server instances for horizontal scaling",
367
- schema: webSocketMessageSchema,
368
- handler: async (message) => {
369
- if (this.messageHandler) await this.messageHandler(message.payload);
370
- }
371
- });
372
- /**
373
- * Publish a message to be distributed across all server instances
374
- */
375
- async publish(event) {
376
- await this.topic.publish(event);
377
- }
378
- /**
379
- * Set the handler for incoming messages
380
- */
381
- setMessageHandler(handler) {
382
- this.messageHandler = handler;
383
- }
384
- };
385
-
386
- //#endregion
387
- //#region src/websocket/providers/NodeWebSocketServerProvider.ts
388
- const envSchema$1 = alepha.t.object({ WEBSOCKET_PATH: alepha.t.text({
389
- default: "/ws",
390
- description: "Base path for WebSocket endpoints"
391
- }) });
392
- var NodeWebSocketServerProvider = class extends WebSocketServerProvider {
393
- alepha = (0, alepha.$inject)(alepha.Alepha);
394
- roomManager = (0, alepha.$inject)(RoomManager);
395
- topicService = (0, alepha.$inject)(WebSocketTopicService);
396
- log = (0, alepha_logger.$logger)();
397
- env = (0, alepha.$env)(envSchema$1);
398
- wss;
399
- endpoints = /* @__PURE__ */ new Map();
400
- connections = /* @__PURE__ */ new Map();
401
- userConnections = /* @__PURE__ */ new Map();
402
- nextConnectionId = 1;
403
- registerEndpoint(config) {
404
- const path = config.channel.options.path;
405
- this.endpoints.set(path, config);
406
- }
407
- async emit(channelPath, options) {
408
- await this.topicService.publish({
409
- channelPath,
410
- roomIds: options.roomIds ? options.roomIds : options.roomId ? [options.roomId] : void 0,
411
- userIds: options.userIds ? options.userIds : options.userId ? [options.userId] : void 0,
412
- connectionIds: options.connectionIds ? options.connectionIds : options.connectionId ? [options.connectionId] : void 0,
413
- exceptConnectionIds: options.exceptConnectionIds,
414
- exceptUserIds: options.exceptUserIds,
415
- message: options.message
416
- });
417
- }
418
- getConnections() {
419
- return Array.from(this.connections.values());
420
- }
421
- getRoomConnections(roomId) {
422
- return this.roomManager.getRoomConnections(roomId).map((id) => this.connections.get(id)).filter((conn) => conn !== void 0);
423
- }
424
- getUserConnections(userId) {
425
- const connectionIds = this.userConnections.get(userId);
426
- if (!connectionIds) return [];
427
- return Array.from(connectionIds).map((id) => this.connections.get(id)).filter((conn) => conn !== void 0);
428
- }
429
- async closeConnection(connectionId, code, reason) {
430
- const connection = this.connections.get(connectionId);
431
- if (!connection) {
432
- this.log.warn(`Connection not found: ${connectionId}`);
433
- return;
434
- }
435
- await connection.close(code, reason);
436
- }
437
- handleUpgrade(request, socket, head) {
438
- const path = new URL(request.url || "/", "http://localhost").pathname;
439
- const endpoint = this.endpoints.get(path);
440
- if (!endpoint) {
441
- if (!this.alepha.isViteDev()) {
442
- this.log.warn(`No WebSocket endpoint found for path: ${path}`);
443
- socket.destroy();
444
- }
445
- return false;
446
- }
447
- this.log.debug(`WebSocket upgrade request: ${path}`);
448
- this.wss?.handleUpgrade(request, socket, head, (ws$1) => {
449
- this.handleConnection(ws$1, endpoint, request);
450
- });
451
- return true;
452
- }
453
- handleConnection(ws$1, endpoint, request) {
454
- const connectionId = `ws-${this.nextConnectionId++}`;
455
- const userId = void 0;
456
- const url = new URL(request.url || "/", "http://localhost");
457
- const roomIds = this.extractRoomIds(url);
458
- const connection = this.alepha.inject(NodeWebSocketConnection, {
459
- lifetime: "transient",
460
- args: [
461
- connectionId,
462
- userId,
463
- roomIds,
464
- ws$1,
465
- this,
466
- endpoint
467
- ]
468
- });
469
- this.connections.set(connectionId, connection);
470
- if (roomIds.length > 0) this.roomManager.joinRooms(connectionId, roomIds);
471
- this.log.info(`WebSocket connection established: ${connectionId}`, {
472
- path: endpoint.channel.options.path,
473
- userId,
474
- roomIds,
475
- remoteAddress: request.socket.remoteAddress
476
- });
477
- if (endpoint.onConnect) Promise.resolve(endpoint.onConnect({
478
- connectionId,
479
- userId,
480
- roomIds
481
- })).catch((error) => {
482
- this.log.error("Error in onConnect handler:", error);
483
- });
484
- ws$1.on("message", async (data) => {
485
- await connection.handleMessage(data);
486
- });
487
- ws$1.on("close", (code, reason) => {
488
- this.log.info(`WebSocket connection closed: ${connectionId}`, {
489
- code,
490
- reason: reason.toString()
491
- });
492
- this.connections.delete(connectionId);
493
- this.roomManager.leaveAllRooms(connectionId);
494
- if (endpoint.onDisconnect) Promise.resolve(endpoint.onDisconnect({
495
- connectionId,
496
- userId,
497
- roomIds
498
- })).catch((error) => {
499
- this.log.error("Error in onDisconnect handler:", error);
500
- });
501
- });
502
- ws$1.on("error", (error) => {
503
- this.log.error(`WebSocket error on ${connectionId}:`, error);
504
- });
505
- }
506
- extractRoomIds(url) {
507
- const roomIds = [];
508
- const roomIdParams = url.searchParams.getAll("roomId");
509
- roomIds.push(...roomIdParams);
510
- const roomIdsParam = url.searchParams.get("roomIds");
511
- if (roomIdsParam) roomIds.push(...roomIdsParam.split(",").map((id) => id.trim()));
512
- if (roomIds.length === 0) roomIds.push("default");
513
- return roomIds;
514
- }
515
- /**
516
- * Send message to local connections based on targeting criteria
517
- * This is called by the topic service when a message is received
518
- */
519
- async sendToLocalConnections(channelPath, message, criteria) {
520
- const targetConnections = /* @__PURE__ */ new Set();
521
- if (criteria.roomIds) for (const roomId of criteria.roomIds) {
522
- const roomConns = this.roomManager.getRoomConnections(roomId);
523
- for (const connId of roomConns) targetConnections.add(connId);
524
- }
525
- if (criteria.userIds) for (const userId of criteria.userIds) {
526
- const userConns = this.userConnections.get(userId);
527
- if (userConns) for (const connId of userConns) targetConnections.add(connId);
528
- }
529
- if (criteria.connectionIds) for (const connId of criteria.connectionIds) targetConnections.add(connId);
530
- if (!criteria.roomIds && !criteria.userIds && !criteria.connectionIds) for (const conn of this.connections.values()) targetConnections.add(conn.id);
531
- if (criteria.exceptConnectionIds) for (const connId of criteria.exceptConnectionIds) targetConnections.delete(connId);
532
- if (criteria.exceptUserIds) for (const userId of criteria.exceptUserIds) {
533
- const userConns = this.userConnections.get(userId);
534
- if (userConns) for (const connId of userConns) targetConnections.delete(connId);
535
- }
536
- const serialized = JSON.stringify(message);
537
- await Promise.all(Array.from(targetConnections).map(async (connId) => {
538
- const conn = this.connections.get(connId);
539
- if (conn) try {
540
- await conn.send(serialized);
541
- } catch (error) {
542
- this.log.error(`Failed to send to connection ${connId}:`, error);
543
- }
544
- }));
545
- }
546
- start = (0, alepha.$hook)({
547
- on: "start",
548
- handler: async () => {
549
- if (this.alepha.isServerless()) {
550
- this.log.debug("WebSocket server disabled in serverless mode");
551
- return;
552
- }
553
- this.wss = new ws.WebSocketServer({ noServer: true });
554
- for (const [path, endpoint] of this.endpoints.entries()) this.log.debug(`WebSocket endpoint registered: ${path}`);
555
- this.topicService.setMessageHandler(async (event) => {
556
- await this.sendToLocalConnections(event.channelPath, event.message, {
557
- roomIds: event.roomIds,
558
- userIds: event.userIds,
559
- connectionIds: event.connectionIds,
560
- exceptConnectionIds: event.exceptConnectionIds,
561
- exceptUserIds: event.exceptUserIds
562
- });
563
- });
564
- this.log.info("WebSocket server OK", { basePath: this.env.WEBSOCKET_PATH });
565
- }
566
- });
567
- ready = (0, alepha.$hook)({
568
- on: "ready",
569
- handler: async () => {
570
- if (this.alepha.isServerless() || !this.wss) return;
571
- const httpServer = this.alepha.state.get("alepha.node.server");
572
- if (httpServer) {
573
- httpServer.on("upgrade", (request, socket, head) => {
574
- this.handleUpgrade(request, socket, head);
575
- });
576
- this.log.debug("WebSocket upgrade handler attached to HTTP server");
577
- } else this.log.warn("No HTTP server found - WebSocket upgrade handler not attached");
578
- }
579
- });
580
- stop = (0, alepha.$hook)({
581
- on: "stop",
582
- handler: async () => {
583
- if (!this.wss) return;
584
- for (const connection of this.connections.values()) await connection.close(1001, "Server shutting down");
585
- await new Promise((resolve, reject) => {
586
- this.wss?.close((err) => {
587
- if (err) reject(err);
588
- else resolve();
589
- });
590
- });
591
- this.log.info("WebSocket server closed");
592
- }
593
- });
594
- };
595
- var NodeWebSocketConnection = class {
596
- log = (0, alepha_logger.$logger)();
597
- metadata;
598
- constructor(id, userId, roomIds, ws$1, provider, endpoint) {
599
- this.id = id;
600
- this.userId = userId;
601
- this.roomIds = roomIds;
602
- this.ws = ws$1;
603
- this.provider = provider;
604
- this.endpoint = endpoint;
605
- }
606
- get readyState() {
607
- return this.ws.readyState;
608
- }
609
- async send(message) {
610
- if (this.ws.readyState !== ws.WebSocket.OPEN) throw new Error("WebSocket is not open");
611
- const data = typeof message === "string" ? message : JSON.stringify(message);
612
- await new Promise((resolve, reject) => {
613
- this.ws.send(data, (err) => {
614
- if (err) reject(err);
615
- else resolve();
616
- });
617
- });
618
- }
619
- async close(code, reason) {
620
- this.ws.close(code, reason);
621
- }
622
- async handleMessage(data) {
623
- try {
624
- const rawMessage = data.toString();
625
- let parsed;
626
- try {
627
- parsed = JSON.parse(rawMessage);
628
- } catch {
629
- this.log.warn("Received non-JSON message");
630
- return;
631
- }
632
- const roomId = parsed.roomId || this.roomIds[0] || "default";
633
- const message = parsed.message || parsed;
634
- const outSchema = this.endpoint.channel.options.schema.out;
635
- if (!alepha.TypeBoxValue.Check(outSchema, message)) throw new WebSocketValidationError(`Message validation failed: ${Array.from(alepha.TypeBoxValue.Errors(outSchema, message)).map((e) => e.message).join(", ")}`);
636
- const reply = async (options) => {
637
- const targetRoomId = options.roomId || roomId;
638
- const exceptConnectionIds = options.exceptConnectionIds || [];
639
- if (options.exceptSelf) exceptConnectionIds.push(this.id);
640
- await this.provider.emit(this.endpoint.channel.options.path, {
641
- message: options.message,
642
- roomId: targetRoomId,
643
- exceptConnectionIds,
644
- exceptUserIds: options.exceptUserIds
645
- });
646
- };
647
- const context = {
648
- connectionId: this.id,
649
- userId: this.userId,
650
- roomId,
651
- message,
652
- reply
653
- };
654
- await this.endpoint.handler(context);
655
- } catch (error) {
656
- this.log.error(`Error handling WebSocket message on ${this.id}:`, error);
657
- await this.send({ error: error instanceof Error ? error.message : "Unknown error" });
658
- }
659
- }
660
- };
661
-
662
- //#endregion
663
- //#region src/websocket/interfaces/WebSocketInterfaces.ts
664
- /**
665
- * WebSocket state enum
666
- */
667
- let WebSocketState = /* @__PURE__ */ function(WebSocketState$1) {
668
- WebSocketState$1[WebSocketState$1["CONNECTING"] = 0] = "CONNECTING";
669
- WebSocketState$1[WebSocketState$1["OPEN"] = 1] = "OPEN";
670
- WebSocketState$1[WebSocketState$1["CLOSING"] = 2] = "CLOSING";
671
- WebSocketState$1[WebSocketState$1["CLOSED"] = 3] = "CLOSED";
672
- return WebSocketState$1;
673
- }({});
674
-
675
- //#endregion
676
- //#region src/websocket/services/WebSocketClient.ts
677
- const envSchema = alepha.t.object({
678
- WEBSOCKET_URL: alepha.t.text({
679
- default: "",
680
- description: "WebSocket server URL (e.g., ws://localhost:3001). Leave empty to auto-detect."
681
- }),
682
- WEBSOCKET_RECONNECT_INTERVAL: alepha.t.integer({
683
- default: 3e3,
684
- description: "Reconnection interval in milliseconds"
685
- }),
686
- WEBSOCKET_MAX_RECONNECT_ATTEMPTS: alepha.t.integer({
687
- default: 10,
688
- description: "Maximum number of reconnection attempts. Set to -1 for infinite."
689
- })
690
- });
691
- /**
692
- * WebSocket channel connection
693
- *
694
- * Manages a single WebSocket connection to a channel with multiple room subscriptions.
695
- * One connection can handle multiple rooms on the same channel.
696
- */
697
- var WebSocketChannelConnection = class {
698
- alepha = (0, alepha.$inject)(alepha.Alepha);
699
- log = (0, alepha_logger.$logger)();
700
- ws;
701
- reconnectAttempts = 0;
702
- reconnectTimer;
703
- messageQueue = [];
704
- subscriptions = /* @__PURE__ */ new Map();
705
- isConnected = false;
706
- isConnecting = false;
707
- isError = false;
708
- error;
709
- onConnectCallbacks = /* @__PURE__ */ new Set();
710
- onDisconnectCallbacks = /* @__PURE__ */ new Set();
711
- onErrorCallbacks = /* @__PURE__ */ new Set();
712
- constructor(channel, options, env) {
713
- this.channel = channel;
714
- this.options = options;
715
- this.env = env;
716
- }
717
- /**
718
- * Build WebSocket URL
719
- */
720
- buildUrl() {
721
- this.log.trace("Building WebSocket URL", {
722
- hasCustomUrl: !!this.options.url,
723
- channelPath: this.channel.options.path
724
- });
725
- if (this.options.url) {
726
- this.log.debug("Using custom WebSocket URL", { url: this.options.url });
727
- return this.options.url;
728
- }
729
- if (typeof window !== "undefined") {
730
- const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
731
- const host = window.location.host;
732
- const path = this.channel.options.path;
733
- const roomIds = Array.from(this.subscriptions.keys());
734
- const url$1 = `${protocol}//${host}${path}${roomIds.length > 0 ? `?roomIds=${roomIds.join(",")}` : ""}`;
735
- this.log.debug("Auto-detected WebSocket URL", {
736
- url: url$1,
737
- roomIds
738
- });
739
- return url$1;
740
- }
741
- const url = `${this.env.WEBSOCKET_URL}${this.channel.options.path}`;
742
- this.log.debug("Using env WebSocket URL", { url });
743
- return url;
744
- }
745
- /**
746
- * Subscribe to a room on this channel
747
- */
748
- subscribe(roomId, handler, callbacks) {
749
- this.log.debug("Subscribing to room", {
750
- roomId,
751
- channelPath: this.channel.options.path,
752
- existingSubscriptions: this.subscriptions.size
753
- });
754
- this.subscriptions.set(roomId, handler);
755
- if (callbacks?.onConnect) this.onConnectCallbacks.add(callbacks.onConnect);
756
- if (callbacks?.onDisconnect) this.onDisconnectCallbacks.add(callbacks.onDisconnect);
757
- if (callbacks?.onError) this.onErrorCallbacks.add(callbacks.onError);
758
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
759
- this.log.trace("No active connection, initiating connect");
760
- this.connect().catch((error) => {
761
- this.log.error("Failed to connect:", error);
762
- });
763
- } else this.log.trace("Already connected, reusing existing connection");
764
- return () => {
765
- this.log.debug("Unsubscribing from room", { roomId });
766
- this.subscriptions.delete(roomId);
767
- if (callbacks?.onConnect) this.onConnectCallbacks.delete(callbacks.onConnect);
768
- if (callbacks?.onDisconnect) this.onDisconnectCallbacks.delete(callbacks.onDisconnect);
769
- if (callbacks?.onError) this.onErrorCallbacks.delete(callbacks.onError);
770
- if (this.subscriptions.size === 0) {
771
- this.log.debug("No more subscriptions, disconnecting");
772
- this.disconnect();
773
- }
774
- };
775
- }
776
- /**
777
- * Connect to WebSocket server
778
- */
779
- async connect() {
780
- if (this.ws?.readyState === WebSocket.OPEN) {
781
- this.log.trace("Already connected, skipping connect");
782
- return;
783
- }
784
- this.isConnecting = true;
785
- this.isError = false;
786
- this.error = void 0;
787
- const url = this.buildUrl();
788
- this.log.info("Connecting to WebSocket server", { url });
789
- return new Promise((resolve, reject) => {
790
- try {
791
- const ws$1 = new WebSocket(url);
792
- this.ws = ws$1;
793
- ws$1.onopen = () => {
794
- this.isConnected = true;
795
- this.isConnecting = false;
796
- this.isError = false;
797
- this.error = void 0;
798
- this.reconnectAttempts = 0;
799
- this.log.info("WebSocket connected", {
800
- channelPath: this.channel.options.path,
801
- rooms: Array.from(this.subscriptions.keys())
802
- });
803
- if (this.messageQueue.length > 0) this.log.debug("Flushing queued messages", { count: this.messageQueue.length });
804
- while (this.messageQueue.length > 0) {
805
- const msg = this.messageQueue.shift();
806
- if (msg) {
807
- this.log.trace("Sending queued message", { roomId: msg.roomId });
808
- ws$1.send(JSON.stringify({
809
- roomId: msg.roomId,
810
- message: msg.message
811
- }));
812
- }
813
- }
814
- for (const callback of this.onConnectCallbacks) callback();
815
- resolve();
816
- };
817
- ws$1.onmessage = (event) => {
818
- this.log.trace("Message received", { dataLength: event.data?.length });
819
- this.handleMessage(event.data);
820
- };
821
- ws$1.onclose = (event) => {
822
- this.isConnected = false;
823
- this.isConnecting = false;
824
- this.ws = void 0;
825
- this.log.info("WebSocket disconnected", {
826
- code: event.code,
827
- reason: event.reason,
828
- wasClean: event.wasClean
829
- });
830
- for (const callback of this.onDisconnectCallbacks) callback();
831
- if (this.options.autoReconnect !== false) this.scheduleReconnect();
832
- };
833
- ws$1.onerror = () => {
834
- const err = /* @__PURE__ */ new Error("WebSocket connection error");
835
- this.isError = true;
836
- this.error = err;
837
- this.isConnecting = false;
838
- this.log.error("WebSocket error", { url });
839
- for (const callback of this.onErrorCallbacks) callback(err);
840
- reject(err);
841
- };
842
- } catch (err) {
843
- const error = err instanceof Error ? err : /* @__PURE__ */ new Error("Connection failed");
844
- this.isError = true;
845
- this.error = error;
846
- this.isConnecting = false;
847
- this.log.error("Failed to create WebSocket", { error: error.message });
848
- for (const callback of this.onErrorCallbacks) callback(error);
849
- reject(error);
850
- }
851
- });
852
- }
853
- /**
854
- * Handle incoming message
855
- */
856
- handleMessage(data) {
857
- try {
858
- const parsed = JSON.parse(data);
859
- this.log.trace("Parsed incoming message", { parsed });
860
- const inSchema = this.channel.options.schema.in;
861
- this.alepha.codec.validate(inSchema, parsed);
862
- this.log.debug("Dispatching message to handlers", { handlerCount: this.subscriptions.size });
863
- for (const handler of this.subscriptions.values()) handler(parsed);
864
- } catch (err) {
865
- this.log.error("Error handling message:", err);
866
- }
867
- }
868
- /**
869
- * Send message to a specific room
870
- */
871
- async send(roomId, message) {
872
- this.log.trace("Sending message", {
873
- roomId,
874
- message
875
- });
876
- const outSchema = this.channel.options.schema.out;
877
- if (!alepha.TypeBoxValue.Check(outSchema, message)) {
878
- const errors = Array.from(alepha.TypeBoxValue.Errors(outSchema, message));
879
- this.log.warn("Message validation failed", { errors });
880
- throw new Error(`Message validation failed: ${errors.map((e) => e.message).join(", ")}`);
881
- }
882
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
883
- this.log.debug("Connection not ready, queuing message", {
884
- roomId,
885
- queueSize: this.messageQueue.length + 1
886
- });
887
- this.messageQueue.push({
888
- roomId,
889
- message
890
- });
891
- return;
892
- }
893
- this.log.debug("Sending message to server", { roomId });
894
- this.ws.send(JSON.stringify({
895
- roomId,
896
- message
897
- }));
898
- }
899
- /**
900
- * Schedule reconnection
901
- */
902
- scheduleReconnect() {
903
- const maxAttempts = this.options.maxReconnectAttempts ?? this.env.WEBSOCKET_MAX_RECONNECT_ATTEMPTS ?? 10;
904
- const reconnectInterval = this.options.reconnectInterval ?? this.env.WEBSOCKET_RECONNECT_INTERVAL ?? 3e3;
905
- if (maxAttempts !== -1 && this.reconnectAttempts >= maxAttempts) {
906
- this.log.warn("Max reconnection attempts reached", {
907
- attempts: this.reconnectAttempts,
908
- maxAttempts
909
- });
910
- return;
911
- }
912
- this.reconnectAttempts++;
913
- this.log.debug("Scheduling reconnection", {
914
- attempt: this.reconnectAttempts,
915
- maxAttempts,
916
- intervalMs: reconnectInterval
917
- });
918
- this.reconnectTimer = window.setTimeout(() => {
919
- this.log.info("Reconnecting...", {
920
- attempt: this.reconnectAttempts,
921
- maxAttempts
922
- });
923
- this.connect().catch((error) => {
924
- this.log.error("Reconnection failed:", error);
925
- });
926
- }, reconnectInterval);
927
- }
928
- /**
929
- * Disconnect from server
930
- */
931
- disconnect() {
932
- this.log.debug("Disconnecting", {
933
- hasTimer: !!this.reconnectTimer,
934
- hasConnection: !!this.ws
935
- });
936
- if (this.reconnectTimer) {
937
- clearTimeout(this.reconnectTimer);
938
- this.reconnectTimer = void 0;
939
- }
940
- if (this.ws) {
941
- this.ws.close();
942
- this.ws = void 0;
943
- }
944
- this.isConnected = false;
945
- this.isConnecting = false;
946
- this.log.info("Disconnected");
947
- }
948
- /**
949
- * Reconnect manually
950
- */
951
- reconnect() {
952
- this.log.info("Manual reconnect requested");
953
- this.disconnect();
954
- this.connect().catch((error) => {
955
- this.log.error("Manual reconnection failed:", error);
956
- });
957
- }
958
- /**
959
- * Check if subscribed to a room
960
- */
961
- hasRoom(roomId) {
962
- return this.subscriptions.has(roomId);
963
- }
964
- /**
965
- * Get all subscribed rooms
966
- */
967
- getRooms() {
968
- return Array.from(this.subscriptions.keys());
969
- }
970
- };
971
- /**
972
- * WebSocket Client Service
973
- *
974
- * Manages WebSocket connections from the client side (browser).
975
- * One connection per channel, multiple rooms per connection.
976
- */
977
- var WebSocketClient = class {
978
- log = (0, alepha_logger.$logger)();
979
- alepha = (0, alepha.$inject)(alepha.Alepha);
980
- env = (0, alepha.$env)(envSchema);
981
- connections = /* @__PURE__ */ new Map();
982
- /**
983
- * Subscribe to a room on a channel
984
- */
985
- subscribe(roomId, channel, handler, options = {}) {
986
- const channelPath = channel.options.path;
987
- this.log.debug("WebSocketClient.subscribe", {
988
- roomId,
989
- channelPath,
990
- existingConnections: this.connections.size
991
- });
992
- let connection = this.connections.get(channelPath);
993
- if (!connection) {
994
- this.log.debug("Creating new connection for channel", { channelPath });
995
- connection = this.alepha.inject(WebSocketChannelConnection, {
996
- lifetime: "transient",
997
- args: [
998
- channel,
999
- {
1000
- url: options.url,
1001
- autoReconnect: options.autoReconnect,
1002
- reconnectInterval: options.reconnectInterval,
1003
- maxReconnectAttempts: options.maxReconnectAttempts
1004
- },
1005
- this.env
1006
- ]
1007
- });
1008
- this.connections.set(channelPath, connection);
1009
- } else this.log.trace("Reusing existing connection for channel", { channelPath });
1010
- const unsubscribe = connection.subscribe(roomId, handler, {
1011
- onConnect: options.onConnect,
1012
- onDisconnect: options.onDisconnect,
1013
- onError: options.onError
1014
- });
1015
- return () => {
1016
- this.log.debug("WebSocketClient.unsubscribe", {
1017
- roomId,
1018
- channelPath
1019
- });
1020
- unsubscribe();
1021
- if (connection.getRooms().length === 0) {
1022
- this.log.debug("Removing connection for channel (no more rooms)", { channelPath });
1023
- this.connections.delete(channelPath);
1024
- }
1025
- };
1026
- }
1027
- /**
1028
- * Send message to a room on a channel
1029
- */
1030
- async send(roomId, channel, message) {
1031
- const channelPath = channel.options.path;
1032
- this.log.trace("WebSocketClient.send", {
1033
- roomId,
1034
- channelPath
1035
- });
1036
- const connection = this.connections.get(channelPath);
1037
- if (!connection) {
1038
- this.log.warn("Attempted to send on unsubscribed channel", { channelPath });
1039
- throw new alepha.AlephaError(`Not subscribed to channel ${channelPath}. Subscribe first before sending messages.`);
1040
- }
1041
- await connection.send(roomId, message);
1042
- }
1043
- /**
1044
- * Get connection for a channel
1045
- */
1046
- getConnection(channel) {
1047
- const channelPath = channel.options.path;
1048
- const connection = this.connections.get(channelPath);
1049
- this.log.trace("WebSocketClient.getConnection", {
1050
- channelPath,
1051
- found: !!connection
1052
- });
1053
- return connection;
1054
- }
1055
- /**
1056
- * Disconnect all connections
1057
- */
1058
- disconnectAll() {
1059
- this.log.info("Disconnecting all connections", { count: this.connections.size });
1060
- for (const connection of this.connections.values()) connection.disconnect();
1061
- this.connections.clear();
1062
- this.log.debug("All connections disconnected");
1063
- }
1064
- };
1065
-
1066
- //#endregion
1067
- //#region src/websocket/index.ts
1068
- /**
1069
- * Provides real-time bidirectional communication using WebSockets.
1070
- *
1071
- * The WebSockets module enables building real-time applications using the `$websocket` descriptor
1072
- * on class properties. It provides automatic connection management, message routing, type-safe
1073
- * message handling, and seamless integration with other Alepha modules.
1074
- *
1075
- * On the server side (Node.js), it uses the 'ws' library to create a WebSocket server.
1076
- * On the client side (browser), it uses the native WebSocket API.
1077
- *
1078
- * @see {@link $websocket}
1079
- * @module alepha.websockets
1080
- */
1081
- const AlephaWebSocket = (0, alepha.$module)({
1082
- name: "alepha.websocket",
1083
- descriptors: [$channel, $websocket],
1084
- services: [
1085
- WebSocketServerProvider,
1086
- NodeWebSocketServerProvider,
1087
- RoomManager,
1088
- WebSocketTopicService
1089
- ],
1090
- register: (alepha$1) => {
1091
- alepha$1.with(alepha_server.AlephaServer);
1092
- alepha$1.with(alepha_topic.AlephaTopic);
1093
- alepha$1.with({
1094
- provide: WebSocketServerProvider,
1095
- use: NodeWebSocketServerProvider
1096
- });
1097
- }
1098
- });
1099
-
1100
- //#endregion
1101
- exports.$channel = $channel;
1102
- exports.$websocket = $websocket;
1103
- exports.AlephaWebSocket = AlephaWebSocket;
1104
- exports.ChannelDescriptor = ChannelDescriptor;
1105
- exports.NodeWebSocketConnection = NodeWebSocketConnection;
1106
- exports.NodeWebSocketServerProvider = NodeWebSocketServerProvider;
1107
- exports.RoomManager = RoomManager;
1108
- exports.WebSocketChannelConnection = WebSocketChannelConnection;
1109
- exports.WebSocketClient = WebSocketClient;
1110
- exports.WebSocketConnectionError = WebSocketConnectionError;
1111
- exports.WebSocketDescriptor = WebSocketDescriptor;
1112
- exports.WebSocketError = WebSocketError;
1113
- exports.WebSocketServerProvider = WebSocketServerProvider;
1114
- exports.WebSocketState = WebSocketState;
1115
- exports.WebSocketTopicService = WebSocketTopicService;
1116
- exports.WebSocketValidationError = WebSocketValidationError;
1117
- //# sourceMappingURL=index.cjs.map