alepha 0.13.0 → 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 (195) hide show
  1. package/dist/api-jobs/index.d.ts +26 -26
  2. package/dist/api-users/index.d.ts +1 -1
  3. package/dist/cli/{dist-Sz2EXvQX.cjs → dist-Dl9Vl7Ur.js} +17 -13
  4. package/dist/cli/{dist-BBPjuQ56.js.map → dist-Dl9Vl7Ur.js.map} +1 -1
  5. package/dist/cli/index.d.ts +3 -11
  6. package/dist/cli/index.js +106 -74
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/email/index.js +71 -73
  9. package/dist/email/index.js.map +1 -1
  10. package/dist/orm/index.d.ts +1 -1
  11. package/dist/orm/index.js.map +1 -1
  12. package/dist/queue/index.d.ts +4 -4
  13. package/dist/retry/index.d.ts +1 -1
  14. package/dist/retry/index.js +2 -2
  15. package/dist/retry/index.js.map +1 -1
  16. package/dist/scheduler/index.d.ts +6 -6
  17. package/dist/security/index.d.ts +28 -28
  18. package/dist/server/index.js +1 -1
  19. package/dist/server/index.js.map +1 -1
  20. package/dist/server-health/index.d.ts +17 -17
  21. package/dist/server-metrics/index.js +170 -174
  22. package/dist/server-metrics/index.js.map +1 -1
  23. package/dist/server-security/index.d.ts +9 -9
  24. package/dist/vite/index.js +4 -5
  25. package/dist/vite/index.js.map +1 -1
  26. package/dist/websocket/index.d.ts +7 -7
  27. package/package.json +52 -103
  28. package/src/cli/apps/AlephaPackageBuilderCli.ts +7 -2
  29. package/src/cli/assets/appRouterTs.ts +9 -0
  30. package/src/cli/assets/indexHtml.ts +2 -1
  31. package/src/cli/assets/mainBrowserTs.ts +10 -0
  32. package/src/cli/commands/CoreCommands.ts +6 -5
  33. package/src/cli/commands/DrizzleCommands.ts +65 -57
  34. package/src/cli/commands/VerifyCommands.ts +1 -1
  35. package/src/cli/services/ProjectUtils.ts +44 -38
  36. package/src/orm/providers/DrizzleKitProvider.ts +1 -1
  37. package/src/retry/descriptors/$retry.ts +5 -3
  38. package/src/server/providers/NodeHttpServerProvider.ts +1 -1
  39. package/src/vite/helpers/boot.ts +3 -3
  40. package/dist/api-files/index.cjs +0 -1293
  41. package/dist/api-files/index.cjs.map +0 -1
  42. package/dist/api-files/index.d.cts +0 -829
  43. package/dist/api-jobs/index.cjs +0 -274
  44. package/dist/api-jobs/index.cjs.map +0 -1
  45. package/dist/api-jobs/index.d.cts +0 -654
  46. package/dist/api-notifications/index.cjs +0 -380
  47. package/dist/api-notifications/index.cjs.map +0 -1
  48. package/dist/api-notifications/index.d.cts +0 -289
  49. package/dist/api-parameters/index.cjs +0 -66
  50. package/dist/api-parameters/index.cjs.map +0 -1
  51. package/dist/api-parameters/index.d.cts +0 -84
  52. package/dist/api-users/index.cjs +0 -6009
  53. package/dist/api-users/index.cjs.map +0 -1
  54. package/dist/api-users/index.d.cts +0 -4740
  55. package/dist/api-verifications/index.cjs +0 -407
  56. package/dist/api-verifications/index.cjs.map +0 -1
  57. package/dist/api-verifications/index.d.cts +0 -207
  58. package/dist/batch/index.cjs +0 -408
  59. package/dist/batch/index.cjs.map +0 -1
  60. package/dist/batch/index.d.cts +0 -330
  61. package/dist/bin/index.cjs +0 -17
  62. package/dist/bin/index.cjs.map +0 -1
  63. package/dist/bin/index.d.cts +0 -1
  64. package/dist/bucket/index.cjs +0 -303
  65. package/dist/bucket/index.cjs.map +0 -1
  66. package/dist/bucket/index.d.cts +0 -355
  67. package/dist/cache/index.cjs +0 -241
  68. package/dist/cache/index.cjs.map +0 -1
  69. package/dist/cache/index.d.cts +0 -202
  70. package/dist/cache-redis/index.cjs +0 -84
  71. package/dist/cache-redis/index.cjs.map +0 -1
  72. package/dist/cache-redis/index.d.cts +0 -40
  73. package/dist/cli/chunk-DSlc6foC.cjs +0 -43
  74. package/dist/cli/dist-BBPjuQ56.js +0 -2778
  75. package/dist/cli/dist-Sz2EXvQX.cjs.map +0 -1
  76. package/dist/cli/index.cjs +0 -1241
  77. package/dist/cli/index.cjs.map +0 -1
  78. package/dist/cli/index.d.cts +0 -422
  79. package/dist/command/index.cjs +0 -693
  80. package/dist/command/index.cjs.map +0 -1
  81. package/dist/command/index.d.cts +0 -340
  82. package/dist/core/index.cjs +0 -2264
  83. package/dist/core/index.cjs.map +0 -1
  84. package/dist/core/index.d.cts +0 -1927
  85. package/dist/datetime/index.cjs +0 -318
  86. package/dist/datetime/index.cjs.map +0 -1
  87. package/dist/datetime/index.d.cts +0 -145
  88. package/dist/email/index.cjs +0 -10874
  89. package/dist/email/index.cjs.map +0 -1
  90. package/dist/email/index.d.cts +0 -186
  91. package/dist/fake/index.cjs +0 -34641
  92. package/dist/fake/index.cjs.map +0 -1
  93. package/dist/fake/index.d.cts +0 -74
  94. package/dist/file/index.cjs +0 -1212
  95. package/dist/file/index.cjs.map +0 -1
  96. package/dist/file/index.d.cts +0 -698
  97. package/dist/lock/index.cjs +0 -226
  98. package/dist/lock/index.cjs.map +0 -1
  99. package/dist/lock/index.d.cts +0 -361
  100. package/dist/lock-redis/index.cjs +0 -113
  101. package/dist/lock-redis/index.cjs.map +0 -1
  102. package/dist/lock-redis/index.d.cts +0 -24
  103. package/dist/logger/index.cjs +0 -521
  104. package/dist/logger/index.cjs.map +0 -1
  105. package/dist/logger/index.d.cts +0 -281
  106. package/dist/orm/index.cjs +0 -2986
  107. package/dist/orm/index.cjs.map +0 -1
  108. package/dist/orm/index.d.cts +0 -2213
  109. package/dist/queue/index.cjs +0 -1044
  110. package/dist/queue/index.cjs.map +0 -1
  111. package/dist/queue/index.d.cts +0 -1265
  112. package/dist/queue-redis/index.cjs +0 -873
  113. package/dist/queue-redis/index.cjs.map +0 -1
  114. package/dist/queue-redis/index.d.cts +0 -82
  115. package/dist/redis/index.cjs +0 -153
  116. package/dist/redis/index.cjs.map +0 -1
  117. package/dist/redis/index.d.cts +0 -82
  118. package/dist/retry/index.cjs +0 -146
  119. package/dist/retry/index.cjs.map +0 -1
  120. package/dist/retry/index.d.cts +0 -172
  121. package/dist/router/index.cjs +0 -111
  122. package/dist/router/index.cjs.map +0 -1
  123. package/dist/router/index.d.cts +0 -46
  124. package/dist/scheduler/index.cjs +0 -576
  125. package/dist/scheduler/index.cjs.map +0 -1
  126. package/dist/scheduler/index.d.cts +0 -145
  127. package/dist/security/index.cjs +0 -2402
  128. package/dist/security/index.cjs.map +0 -1
  129. package/dist/security/index.d.cts +0 -598
  130. package/dist/server/index.cjs +0 -1680
  131. package/dist/server/index.cjs.map +0 -1
  132. package/dist/server/index.d.cts +0 -810
  133. package/dist/server-auth/index.cjs +0 -3146
  134. package/dist/server-auth/index.cjs.map +0 -1
  135. package/dist/server-auth/index.d.cts +0 -1164
  136. package/dist/server-cache/index.cjs +0 -252
  137. package/dist/server-cache/index.cjs.map +0 -1
  138. package/dist/server-cache/index.d.cts +0 -164
  139. package/dist/server-compress/index.cjs +0 -141
  140. package/dist/server-compress/index.cjs.map +0 -1
  141. package/dist/server-compress/index.d.cts +0 -38
  142. package/dist/server-cookies/index.cjs +0 -234
  143. package/dist/server-cookies/index.cjs.map +0 -1
  144. package/dist/server-cookies/index.d.cts +0 -144
  145. package/dist/server-cors/index.cjs +0 -201
  146. package/dist/server-cors/index.cjs.map +0 -1
  147. package/dist/server-cors/index.d.cts +0 -140
  148. package/dist/server-health/index.cjs +0 -62
  149. package/dist/server-health/index.cjs.map +0 -1
  150. package/dist/server-health/index.d.cts +0 -58
  151. package/dist/server-helmet/index.cjs +0 -131
  152. package/dist/server-helmet/index.cjs.map +0 -1
  153. package/dist/server-helmet/index.d.cts +0 -97
  154. package/dist/server-links/index.cjs +0 -992
  155. package/dist/server-links/index.cjs.map +0 -1
  156. package/dist/server-links/index.d.cts +0 -513
  157. package/dist/server-metrics/index.cjs +0 -4535
  158. package/dist/server-metrics/index.cjs.map +0 -1
  159. package/dist/server-metrics/index.d.cts +0 -35
  160. package/dist/server-multipart/index.cjs +0 -237
  161. package/dist/server-multipart/index.cjs.map +0 -1
  162. package/dist/server-multipart/index.d.cts +0 -50
  163. package/dist/server-proxy/index.cjs +0 -186
  164. package/dist/server-proxy/index.cjs.map +0 -1
  165. package/dist/server-proxy/index.d.cts +0 -234
  166. package/dist/server-rate-limit/index.cjs +0 -241
  167. package/dist/server-rate-limit/index.cjs.map +0 -1
  168. package/dist/server-rate-limit/index.d.cts +0 -183
  169. package/dist/server-security/index.cjs +0 -316
  170. package/dist/server-security/index.cjs.map +0 -1
  171. package/dist/server-security/index.d.cts +0 -173
  172. package/dist/server-static/index.cjs +0 -170
  173. package/dist/server-static/index.cjs.map +0 -1
  174. package/dist/server-static/index.d.cts +0 -121
  175. package/dist/server-swagger/index.cjs +0 -1021
  176. package/dist/server-swagger/index.cjs.map +0 -1
  177. package/dist/server-swagger/index.d.cts +0 -382
  178. package/dist/sms/index.cjs +0 -221
  179. package/dist/sms/index.cjs.map +0 -1
  180. package/dist/sms/index.d.cts +0 -130
  181. package/dist/thread/index.cjs +0 -350
  182. package/dist/thread/index.cjs.map +0 -1
  183. package/dist/thread/index.d.cts +0 -260
  184. package/dist/topic/index.cjs +0 -282
  185. package/dist/topic/index.cjs.map +0 -1
  186. package/dist/topic/index.d.cts +0 -523
  187. package/dist/topic-redis/index.cjs +0 -71
  188. package/dist/topic-redis/index.cjs.map +0 -1
  189. package/dist/topic-redis/index.d.cts +0 -42
  190. package/dist/vite/index.cjs +0 -1077
  191. package/dist/vite/index.cjs.map +0 -1
  192. package/dist/vite/index.d.cts +0 -542
  193. package/dist/websocket/index.cjs +0 -1117
  194. package/dist/websocket/index.cjs.map +0 -1
  195. 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