alepha 0.20.6 → 0.20.7

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 (243) hide show
  1. package/AGENTS.md +0 -1
  2. package/CLAUDE.md +0 -1
  3. package/assets/agents-template.md +0 -1
  4. package/dist/api/audits/index.browser.js +1 -0
  5. package/dist/api/audits/index.browser.js.map +1 -1
  6. package/dist/api/audits/index.d.ts +370 -355
  7. package/dist/api/audits/index.d.ts.map +1 -1
  8. package/dist/api/audits/index.js +1 -0
  9. package/dist/api/audits/index.js.map +1 -1
  10. package/dist/api/files/index.browser.js +1 -0
  11. package/dist/api/files/index.browser.js.map +1 -1
  12. package/dist/api/files/index.d.ts +179 -170
  13. package/dist/api/files/index.d.ts.map +1 -1
  14. package/dist/api/files/index.js +1 -0
  15. package/dist/api/files/index.js.map +1 -1
  16. package/dist/api/jobs/index.browser.js +7 -0
  17. package/dist/api/jobs/index.browser.js.map +1 -1
  18. package/dist/api/jobs/index.d.ts +271 -262
  19. package/dist/api/jobs/index.d.ts.map +1 -1
  20. package/dist/api/jobs/index.js +21 -3
  21. package/dist/api/jobs/index.js.map +1 -1
  22. package/dist/api/keys/index.d.ts +198 -192
  23. package/dist/api/keys/index.d.ts.map +1 -1
  24. package/dist/api/keys/index.js +1 -0
  25. package/dist/api/keys/index.js.map +1 -1
  26. package/dist/api/notifications/index.d.ts +246 -245
  27. package/dist/api/notifications/index.d.ts.map +1 -1
  28. package/dist/api/organizations/index.d.ts +100 -97
  29. package/dist/api/organizations/index.d.ts.map +1 -1
  30. package/dist/api/parameters/index.d.ts +323 -320
  31. package/dist/api/parameters/index.d.ts.map +1 -1
  32. package/dist/api/payments/index.d.ts +431 -376
  33. package/dist/api/payments/index.d.ts.map +1 -1
  34. package/dist/api/payments/index.js +202 -87
  35. package/dist/api/payments/index.js.map +1 -1
  36. package/dist/api/subscriptions/index.d.ts +1695 -0
  37. package/dist/api/subscriptions/index.d.ts.map +1 -0
  38. package/dist/api/subscriptions/index.js +1919 -0
  39. package/dist/api/subscriptions/index.js.map +1 -0
  40. package/dist/api/users/index.d.ts +863 -847
  41. package/dist/api/users/index.d.ts.map +1 -1
  42. package/dist/api/verifications/index.d.ts +126 -125
  43. package/dist/api/verifications/index.d.ts.map +1 -1
  44. package/dist/bucket/index.d.ts +3 -2
  45. package/dist/bucket/index.d.ts.map +1 -1
  46. package/dist/cache/core/index.d.ts +114 -4
  47. package/dist/cache/core/index.d.ts.map +1 -1
  48. package/dist/cache/core/index.js +181 -15
  49. package/dist/cache/core/index.js.map +1 -1
  50. package/dist/cache/core/index.workerd.js +181 -15
  51. package/dist/cache/core/index.workerd.js.map +1 -1
  52. package/dist/cache/database/index.d.ts +20 -19
  53. package/dist/cache/database/index.d.ts.map +1 -1
  54. package/dist/cache/redis/index.d.ts +3 -2
  55. package/dist/cache/redis/index.d.ts.map +1 -1
  56. package/dist/cli/core/index.d.ts +113 -129
  57. package/dist/cli/core/index.d.ts.map +1 -1
  58. package/dist/cli/core/index.js +75 -7
  59. package/dist/cli/core/index.js.map +1 -1
  60. package/dist/cli/devtools/index.d.ts +3 -2
  61. package/dist/cli/devtools/index.d.ts.map +1 -1
  62. package/dist/cli/platform/index.d.ts +346 -290
  63. package/dist/cli/platform/index.d.ts.map +1 -1
  64. package/dist/cli/platform/index.js +105 -6
  65. package/dist/cli/platform/index.js.map +1 -1
  66. package/dist/cli/vendor/index.d.ts +12 -11
  67. package/dist/cli/vendor/index.d.ts.map +1 -1
  68. package/dist/command/index.d.ts +5 -4
  69. package/dist/command/index.d.ts.map +1 -1
  70. package/dist/core/index.browser.js +1 -1
  71. package/dist/core/index.browser.js.map +1 -1
  72. package/dist/core/index.d.ts +119 -118
  73. package/dist/core/index.d.ts.map +1 -1
  74. package/dist/core/index.js +1 -1
  75. package/dist/core/index.js.map +1 -1
  76. package/dist/core/index.native.js +1 -1
  77. package/dist/core/index.native.js.map +1 -1
  78. package/dist/core/index.workerd.js +1 -1
  79. package/dist/core/index.workerd.js.map +1 -1
  80. package/dist/crypto/index.d.ts +3 -2
  81. package/dist/crypto/index.d.ts.map +1 -1
  82. package/dist/email/core/index.d.ts +3 -2
  83. package/dist/email/core/index.d.ts.map +1 -1
  84. package/dist/email/smtp/index.d.ts +7 -6
  85. package/dist/email/smtp/index.d.ts.map +1 -1
  86. package/dist/lock/core/index.d.ts +5 -4
  87. package/dist/lock/core/index.d.ts.map +1 -1
  88. package/dist/logger/index.d.ts +10 -9
  89. package/dist/logger/index.d.ts.map +1 -1
  90. package/dist/mcp/index.d.ts +9 -8
  91. package/dist/mcp/index.d.ts.map +1 -1
  92. package/dist/mcp/index.js +1 -1
  93. package/dist/mcp/index.js.map +1 -1
  94. package/dist/orm/core/index.browser.js +9 -3
  95. package/dist/orm/core/index.browser.js.map +1 -1
  96. package/dist/orm/core/index.bun.js +31 -10
  97. package/dist/orm/core/index.bun.js.map +1 -1
  98. package/dist/orm/core/index.d.ts +33 -14
  99. package/dist/orm/core/index.d.ts.map +1 -1
  100. package/dist/orm/core/index.js +31 -10
  101. package/dist/orm/core/index.js.map +1 -1
  102. package/dist/orm/postgres/index.d.ts +6 -5
  103. package/dist/orm/postgres/index.d.ts.map +1 -1
  104. package/dist/queue/core/index.d.ts +5 -4
  105. package/dist/queue/core/index.d.ts.map +1 -1
  106. package/dist/queue/redis/index.d.ts +3 -2
  107. package/dist/queue/redis/index.d.ts.map +1 -1
  108. package/dist/react/form/index.d.ts +5 -0
  109. package/dist/react/form/index.d.ts.map +1 -1
  110. package/dist/react/form/index.js +6 -4
  111. package/dist/react/form/index.js.map +1 -1
  112. package/dist/react/i18n/index.d.ts +2 -1
  113. package/dist/react/i18n/index.d.ts.map +1 -1
  114. package/dist/react/router/index.d.ts +206 -205
  115. package/dist/react/router/index.d.ts.map +1 -1
  116. package/dist/react/ui/index.d.ts +11 -11
  117. package/dist/react/ui/index.d.ts.map +1 -1
  118. package/dist/scheduler/index.d.ts +3 -2
  119. package/dist/scheduler/index.d.ts.map +1 -1
  120. package/dist/security/index.browser.js +29 -1
  121. package/dist/security/index.browser.js.map +1 -1
  122. package/dist/security/index.d.ts +82 -35
  123. package/dist/security/index.d.ts.map +1 -1
  124. package/dist/security/index.js +56 -3
  125. package/dist/security/index.js.map +1 -1
  126. package/dist/server/auth/index.d.ts +163 -158
  127. package/dist/server/auth/index.d.ts.map +1 -1
  128. package/dist/server/auth/index.js +16 -4
  129. package/dist/server/auth/index.js.map +1 -1
  130. package/dist/server/core/index.d.ts +35 -34
  131. package/dist/server/core/index.d.ts.map +1 -1
  132. package/dist/server/cors/index.d.ts +7 -6
  133. package/dist/server/cors/index.d.ts.map +1 -1
  134. package/dist/server/health/index.d.ts +16 -15
  135. package/dist/server/health/index.d.ts.map +1 -1
  136. package/dist/server/links/index.d.ts +51 -50
  137. package/dist/server/links/index.d.ts.map +1 -1
  138. package/dist/server/rate-limit/index.d.ts +6 -5
  139. package/dist/server/rate-limit/index.d.ts.map +1 -1
  140. package/dist/server/swagger/index.d.ts +2 -1
  141. package/dist/server/swagger/index.d.ts.map +1 -1
  142. package/dist/topic/redis/index.d.ts +3 -2
  143. package/dist/topic/redis/index.d.ts.map +1 -1
  144. package/package.json +16 -32
  145. package/src/api/audits/entities/audits.ts +1 -0
  146. package/src/api/files/entities/files.ts +1 -0
  147. package/src/api/jobs/__tests__/$job.spec.ts +92 -40
  148. package/src/api/jobs/entities/jobExecutionEntity.ts +1 -0
  149. package/src/api/jobs/providers/JobProvider.ts +20 -5
  150. package/src/api/jobs/schemas/jobConfigAtom.ts +5 -0
  151. package/src/api/keys/entities/apiKeyEntity.ts +1 -0
  152. package/src/api/payments/controllers/MockCheckoutController.ts +146 -0
  153. package/src/api/payments/index.ts +3 -0
  154. package/src/api/payments/providers/MemoryPaymentProvider.ts +9 -4
  155. package/src/api/payments/providers/PaymentProvider.ts +25 -9
  156. package/src/api/payments/services/PaymentService.ts +3 -0
  157. package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
  158. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
  159. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
  160. package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
  161. package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
  162. package/src/api/subscriptions/entities/subscriptions.ts +68 -0
  163. package/src/api/subscriptions/index.ts +133 -0
  164. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
  165. package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
  166. package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
  167. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
  168. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
  169. package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
  170. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
  171. package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
  172. package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
  173. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
  174. package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
  175. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
  176. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
  177. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
  178. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
  179. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
  180. package/src/api/subscriptions/services/BillingService.ts +437 -0
  181. package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
  182. package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
  183. package/src/api/subscriptions/services/UsageService.ts +118 -0
  184. package/src/cache/core/__tests__/$cache.memory.spec.ts +450 -0
  185. package/src/cache/core/__tests__/$cache.swr.spec.ts +394 -0
  186. package/src/cache/core/index.ts +16 -0
  187. package/src/cache/core/primitives/$cache.ts +347 -21
  188. package/src/cli/core/tasks/BuildCloudflareTask.ts +16 -0
  189. package/src/cli/core/templates/agentMd.ts +39 -4
  190. package/src/cli/core/templates/biomeJson.ts +25 -1
  191. package/src/cli/core/templates/saasAdminLayoutTsx.ts +2 -2
  192. package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +117 -0
  193. package/src/cli/platform/adapters/CloudflareAdapter.ts +104 -7
  194. package/src/cli/platform/atoms/platformOptions.ts +13 -0
  195. package/src/cli/platform/schemas/platform.ts +1 -0
  196. package/src/cli/platform/services/CloudflareApi.ts +61 -0
  197. package/src/cli/platform/services/PlatformOrchestrator.ts +9 -4
  198. package/src/core/__tests__/$module.spec.ts +2 -2
  199. package/src/core/primitives/$module.ts +4 -4
  200. package/src/mcp/providers/McpServerProvider.ts +1 -1
  201. package/src/orm/core/providers/DatabaseTypeProvider.ts +9 -3
  202. package/src/orm/core/providers/drivers/DatabaseProvider.ts +1 -1
  203. package/src/orm/core/schemas/insertSchema.ts +10 -2
  204. package/src/orm/core/services/Repository.ts +27 -7
  205. package/src/react/form/hooks/useFormState.ts +8 -1
  206. package/src/react/form/index.ts +10 -1
  207. package/src/react/form/services/FormModel.ts +9 -3
  208. package/src/security/atoms/currentTenantAtom.ts +34 -0
  209. package/src/security/index.browser.ts +1 -0
  210. package/src/security/index.ts +12 -1
  211. package/src/security/primitives/$issuer.ts +17 -1
  212. package/src/security/providers/SecurityProvider.ts +37 -0
  213. package/src/server/auth/__tests__/validateRedirectUri.spec.ts +78 -0
  214. package/src/server/auth/providers/ServerAuthProvider.ts +21 -5
  215. package/tsconfig.base.json +2 -1
  216. package/dist/react/websocket/index.d.ts +0 -117
  217. package/dist/react/websocket/index.d.ts.map +0 -1
  218. package/dist/react/websocket/index.js +0 -108
  219. package/dist/react/websocket/index.js.map +0 -1
  220. package/dist/websocket/index.browser.js +0 -848
  221. package/dist/websocket/index.browser.js.map +0 -1
  222. package/dist/websocket/index.d.ts +0 -876
  223. package/dist/websocket/index.d.ts.map +0 -1
  224. package/dist/websocket/index.js +0 -1185
  225. package/dist/websocket/index.js.map +0 -1
  226. package/src/react/websocket/hooks/useRoom.tsx +0 -251
  227. package/src/react/websocket/index.ts +0 -7
  228. package/src/websocket/__tests__/$channel.spec.ts +0 -30
  229. package/src/websocket/__tests__/$websocket-new.spec.ts +0 -195
  230. package/src/websocket/__tests__/RoomManager.spec.ts +0 -146
  231. package/src/websocket/__tests__/websocket-integration.spec.ts +0 -951
  232. package/src/websocket/errors/WebSocketError.ts +0 -34
  233. package/src/websocket/index.browser.ts +0 -25
  234. package/src/websocket/index.shared.ts +0 -8
  235. package/src/websocket/index.ts +0 -85
  236. package/src/websocket/interfaces/WebSocketInterfaces.ts +0 -252
  237. package/src/websocket/primitives/$channel.ts +0 -131
  238. package/src/websocket/primitives/$websocket.ts +0 -107
  239. package/src/websocket/providers/NodeWebSocketServerProvider.ts +0 -617
  240. package/src/websocket/providers/WebSocketServerProvider.ts +0 -56
  241. package/src/websocket/services/RoomManager.ts +0 -160
  242. package/src/websocket/services/WebSocketClient.ts +0 -642
  243. package/src/websocket/services/WebSocketTopicService.ts +0 -108
@@ -1,848 +0,0 @@
1
- import { $env, $inject, $module, Alepha, AlephaError, KIND, Primitive, SchemaValidator, createPrimitive, t } from "alepha";
2
- import { $topic, AlephaTopic } from "alepha/topic";
3
- import { $logger } from "alepha/logger";
4
- //#region ../../src/websocket/primitives/$channel.ts
5
- /**
6
- * Defines a WebSocket channel with specified client and server message schemas.
7
- *
8
- * Channels must be defined as class properties to be registered in the Alepha context.
9
- * They define the "vocabulary" for communication - the schema for messages flowing
10
- * in both directions (server→client and client→server).
11
- *
12
- * @example Server-side with $websocket
13
- * ```typescript
14
- * class ChatController {
15
- * // Channel must be defined inside a class
16
- * chatChannel = $channel({
17
- * path: "/ws/chat",
18
- * description: "Real-time chat channel",
19
- * schema: {
20
- * // Server → Client messages
21
- * in: t.union([
22
- * t.object({
23
- * type: t.const("append"),
24
- * content: t.text(),
25
- * username: t.text()
26
- * }),
27
- * t.object({
28
- * type: t.const("system"),
29
- * message: t.text()
30
- * })
31
- * ]),
32
- * // Client → Server messages
33
- * out: t.object({
34
- * content: t.text()
35
- * })
36
- * }
37
- * });
38
- *
39
- * chat = $websocket({
40
- * channel: this.chatChannel,
41
- * handler: async ({ message, reply }) => {
42
- * await reply({
43
- * message: { type: "append", content: message.content, username: "user" }
44
- * });
45
- * }
46
- * });
47
- * }
48
- * ```
49
- *
50
- * @example Browser-side with useRoom
51
- * ```typescript
52
- * // Define channel in a class for browser context
53
- * class ChatClient {
54
- * chatChannel = $channel({
55
- * path: "/ws/chat",
56
- * schema: { in: inSchema, out: outSchema }
57
- * });
58
- * }
59
- *
60
- * // Use in React component
61
- * function Chat() {
62
- * const client = useInject(ChatClient);
63
- * const chat = useRoom({ roomId: "lobby", channel: client.chatChannel, handler: ... }, []);
64
- * }
65
- * ```
66
- */
67
- const $channel = (options) => {
68
- return createPrimitive(ChannelPrimitive, options);
69
- };
70
- var ChannelPrimitive = class extends Primitive {};
71
- $channel[KIND] = ChannelPrimitive;
72
- //#endregion
73
- //#region ../../src/websocket/providers/WebSocketServerProvider.ts
74
- /**
75
- * Abstract WebSocket server provider
76
- *
77
- * This class provides the base interface that must be implemented by
78
- * platform-specific providers (Node.js, Browser, etc.)
79
- */
80
- var WebSocketServerProvider = class {};
81
- //#endregion
82
- //#region ../../src/websocket/primitives/$websocket.ts
83
- /**
84
- * Defines a WebSocket server endpoint for a specific channel.
85
- *
86
- * Server-side only. Creates a WebSocket endpoint that:
87
- * - Accepts connections from clients
88
- * - Validates incoming messages against the channel schema
89
- * - Provides room-based messaging
90
- * - Integrates with alepha/security for authentication (optional)
91
- * - Supports horizontal scaling via alepha/topic
92
- *
93
- * @example
94
- * ```typescript
95
- * class ChatController {
96
- * chat = $websocket({
97
- * channel: chatChannel,
98
- * handler: async ({ connectionId, userId, roomId, message, reply }) => {
99
- * // Broadcast to all in room except sender
100
- * await reply({
101
- * message: {
102
- * type: "append",
103
- * username: userId,
104
- * content: message.content
105
- * },
106
- * exceptSelf: true
107
- * });
108
- * }
109
- * });
110
- *
111
- * async broadcastAnnouncement(roomId: string, text: string) {
112
- * await this.chat.emit({
113
- * roomId,
114
- * message: {
115
- * type: "append",
116
- * username: "System",
117
- * content: text
118
- * }
119
- * });
120
- * }
121
- * }
122
- * ```
123
- */
124
- const $websocket = (options) => {
125
- return createPrimitive(WebSocketPrimitive, options);
126
- };
127
- var WebSocketPrimitive = class extends Primitive {
128
- webSocketServerProvider = $inject(WebSocketServerProvider);
129
- onInit() {
130
- this.webSocketServerProvider.registerEndpoint(this.options);
131
- }
132
- /**
133
- * Emit message to clients
134
- *
135
- * Send messages from the server to connected clients based on targeting criteria.
136
- * Messages are distributed across all server instances via pub/sub.
137
- *
138
- * @example
139
- * ```typescript
140
- * // Send to specific room
141
- * await websocket.emit({
142
- * roomId: "room-123",
143
- * message: { type: "update", data: {...} }
144
- * });
145
- *
146
- * // Send to specific user (all their connections)
147
- * await websocket.emit({
148
- * userId: "user-456",
149
- * message: { type: "notification", text: "Hello!" }
150
- * });
151
- *
152
- * // Send to multiple rooms, except certain users
153
- * await websocket.emit({
154
- * roomIds: ["room-1", "room-2"],
155
- * exceptUserIds: ["user-123"],
156
- * message: { type: "broadcast", content: "System announcement" }
157
- * });
158
- * ```
159
- */
160
- async emit(options) {
161
- await this.webSocketServerProvider.emit(this.options.channel.options.path, options);
162
- }
163
- };
164
- $websocket[KIND] = WebSocketPrimitive;
165
- //#endregion
166
- //#region ../../src/websocket/services/WebSocketClient.ts
167
- const envSchema = t.object({
168
- WEBSOCKET_URL: t.text({
169
- default: "",
170
- description: "WebSocket server URL (e.g., ws://localhost:3001). Leave empty to auto-detect."
171
- }),
172
- WEBSOCKET_RECONNECT_INTERVAL: t.integer({
173
- default: 3e3,
174
- description: "Reconnection interval in milliseconds"
175
- }),
176
- WEBSOCKET_MAX_RECONNECT_ATTEMPTS: t.integer({
177
- default: 10,
178
- description: "Maximum number of reconnection attempts. Set to -1 for infinite."
179
- })
180
- });
181
- /**
182
- * WebSocket channel connection
183
- *
184
- * Manages a single WebSocket connection to a channel with multiple room subscriptions.
185
- * One connection can handle multiple rooms on the same channel.
186
- */
187
- var WebSocketChannelConnection = class WebSocketChannelConnection {
188
- channel;
189
- options;
190
- env;
191
- alepha = $inject(Alepha);
192
- schemaValidator = $inject(SchemaValidator);
193
- log = $logger();
194
- ws;
195
- reconnectAttempts = 0;
196
- reconnectTimer;
197
- static MAX_QUEUE_SIZE = 1e3;
198
- messageQueue = [];
199
- subscriptions = /* @__PURE__ */ new Map();
200
- isConnected = false;
201
- isConnecting = false;
202
- isError = false;
203
- error;
204
- connectPromise;
205
- onConnectCallbacks = /* @__PURE__ */ new Set();
206
- onDisconnectCallbacks = /* @__PURE__ */ new Set();
207
- onErrorCallbacks = /* @__PURE__ */ new Set();
208
- constructor(channel, options, env) {
209
- this.channel = channel;
210
- this.options = options;
211
- this.env = env;
212
- }
213
- /**
214
- * Build WebSocket URL
215
- */
216
- buildUrl() {
217
- this.log.trace("Building WebSocket URL", {
218
- hasCustomUrl: !!this.options.url,
219
- channelPath: this.channel.options.path
220
- });
221
- if (this.options.url) {
222
- this.log.debug("Using custom WebSocket URL", { url: this.options.url });
223
- return this.options.url;
224
- }
225
- if (typeof window !== "undefined") {
226
- const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
227
- const host = window.location.host;
228
- const path = this.channel.options.path;
229
- const roomIds = Array.from(this.subscriptions.keys());
230
- const url = `${protocol}//${host}${path}${roomIds.length > 0 ? `?roomIds=${roomIds.join(",")}` : ""}`;
231
- this.log.debug("Auto-detected WebSocket URL", {
232
- url,
233
- roomIds
234
- });
235
- return url;
236
- }
237
- const url = `${this.env.WEBSOCKET_URL}${this.channel.options.path}`;
238
- this.log.debug("Using env WebSocket URL", { url });
239
- return url;
240
- }
241
- /**
242
- * Subscribe to a room on this channel
243
- */
244
- subscribe(roomId, handler, callbacks) {
245
- this.log.debug("Subscribing to room", {
246
- roomId,
247
- channelPath: this.channel.options.path,
248
- existingSubscriptions: this.subscriptions.size
249
- });
250
- this.subscriptions.set(roomId, handler);
251
- if (callbacks?.onConnect) this.onConnectCallbacks.add(callbacks.onConnect);
252
- if (callbacks?.onDisconnect) this.onDisconnectCallbacks.add(callbacks.onDisconnect);
253
- if (callbacks?.onError) this.onErrorCallbacks.add(callbacks.onError);
254
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
255
- this.log.trace("No active connection, initiating connect");
256
- this.connect().catch((error) => {
257
- this.log.error("Failed to connect:", error);
258
- });
259
- } else {
260
- this.log.trace("Reconnecting to include new room subscription", { roomId });
261
- this.reconnect();
262
- }
263
- return () => {
264
- this.log.debug("Unsubscribing from room", { roomId });
265
- this.subscriptions.delete(roomId);
266
- if (callbacks?.onConnect) this.onConnectCallbacks.delete(callbacks.onConnect);
267
- if (callbacks?.onDisconnect) this.onDisconnectCallbacks.delete(callbacks.onDisconnect);
268
- if (callbacks?.onError) this.onErrorCallbacks.delete(callbacks.onError);
269
- if (this.subscriptions.size === 0) {
270
- this.log.debug("No more subscriptions, disconnecting");
271
- this.disconnect();
272
- }
273
- };
274
- }
275
- /**
276
- * Connect to WebSocket server
277
- */
278
- async connect() {
279
- if (this.ws?.readyState === WebSocket.OPEN) {
280
- this.log.trace("Already connected, skipping connect");
281
- return;
282
- }
283
- if (this.connectPromise) {
284
- this.log.trace("Connection already in progress, reusing promise");
285
- return this.connectPromise;
286
- }
287
- this.isConnecting = true;
288
- this.isError = false;
289
- this.error = void 0;
290
- const url = this.buildUrl();
291
- this.log.info("Connecting to WebSocket server", { url });
292
- this.connectPromise = new Promise((resolve, reject) => {
293
- try {
294
- const ws = new WebSocket(url);
295
- this.ws = ws;
296
- ws.onopen = () => {
297
- this.isConnected = true;
298
- this.isConnecting = false;
299
- this.isError = false;
300
- this.error = void 0;
301
- this.reconnectAttempts = 0;
302
- this.log.info("WebSocket connected", {
303
- channelPath: this.channel.options.path,
304
- rooms: Array.from(this.subscriptions.keys())
305
- });
306
- if (this.messageQueue.length > 0) this.log.debug("Flushing queued messages", { count: this.messageQueue.length });
307
- while (this.messageQueue.length > 0) {
308
- const msg = this.messageQueue.shift();
309
- if (msg) {
310
- this.log.trace("Sending queued message", { roomId: msg.roomId });
311
- ws.send(JSON.stringify({
312
- roomId: msg.roomId,
313
- message: msg.message
314
- }));
315
- }
316
- }
317
- for (const callback of this.onConnectCallbacks) callback();
318
- resolve();
319
- };
320
- ws.onmessage = (event) => {
321
- this.log.trace("Message received", { dataLength: event.data?.length });
322
- this.handleMessage(event.data);
323
- };
324
- ws.onclose = (event) => {
325
- this.isConnected = false;
326
- this.isConnecting = false;
327
- this.ws = void 0;
328
- this.log.info("WebSocket disconnected", {
329
- code: event.code,
330
- reason: event.reason,
331
- wasClean: event.wasClean
332
- });
333
- for (const callback of this.onDisconnectCallbacks) callback();
334
- if (this.options.autoReconnect !== false) this.scheduleReconnect();
335
- };
336
- ws.onerror = () => {
337
- const err = /* @__PURE__ */ new Error("WebSocket connection error");
338
- this.isError = true;
339
- this.error = err;
340
- this.isConnecting = false;
341
- this.log.error("WebSocket error", { url });
342
- for (const callback of this.onErrorCallbacks) callback(err);
343
- reject(err);
344
- };
345
- } catch (err) {
346
- const error = err instanceof Error ? err : /* @__PURE__ */ new Error("Connection failed");
347
- this.isError = true;
348
- this.error = error;
349
- this.isConnecting = false;
350
- this.log.error("Failed to create WebSocket", { error: error.message });
351
- for (const callback of this.onErrorCallbacks) callback(error);
352
- reject(error);
353
- }
354
- }).finally(() => {
355
- this.connectPromise = void 0;
356
- });
357
- return this.connectPromise;
358
- }
359
- /**
360
- * Handle incoming message
361
- */
362
- handleMessage(data) {
363
- try {
364
- const parsed = JSON.parse(data);
365
- this.log.trace("Parsed incoming message", { parsed });
366
- const inSchema = this.channel.options.schema.in;
367
- this.alepha.codec.validate(inSchema, parsed);
368
- this.log.debug("Dispatching message to handlers", { handlerCount: this.subscriptions.size });
369
- for (const handler of this.subscriptions.values()) handler(parsed);
370
- } catch (err) {
371
- this.log.error("Error handling message:", err);
372
- }
373
- }
374
- /**
375
- * Send message to a specific room
376
- */
377
- async send(roomId, message) {
378
- this.log.trace("Sending message", {
379
- roomId,
380
- message
381
- });
382
- const outSchema = this.channel.options.schema.out;
383
- try {
384
- this.schemaValidator.validate(outSchema, message, {
385
- trim: false,
386
- nullToUndefined: false,
387
- deleteUndefined: false
388
- });
389
- } catch (err) {
390
- this.log.warn("Message validation failed", { error: err });
391
- throw new AlephaError(`Message validation failed: ${err.message}`);
392
- }
393
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
394
- if (this.messageQueue.length >= WebSocketChannelConnection.MAX_QUEUE_SIZE) {
395
- this.log.warn("Message queue full, dropping oldest message", {
396
- roomId,
397
- queueSize: this.messageQueue.length
398
- });
399
- this.messageQueue.shift();
400
- }
401
- this.log.debug("Connection not ready, queuing message", {
402
- roomId,
403
- queueSize: this.messageQueue.length + 1
404
- });
405
- this.messageQueue.push({
406
- roomId,
407
- message
408
- });
409
- return;
410
- }
411
- this.log.debug("Sending message to server", { roomId });
412
- this.ws.send(JSON.stringify({
413
- roomId,
414
- message
415
- }));
416
- }
417
- /**
418
- * Schedule reconnection
419
- */
420
- scheduleReconnect() {
421
- if (this.reconnectTimer) {
422
- clearTimeout(this.reconnectTimer);
423
- this.reconnectTimer = void 0;
424
- }
425
- const maxAttempts = this.options.maxReconnectAttempts ?? this.env.WEBSOCKET_MAX_RECONNECT_ATTEMPTS ?? 10;
426
- const reconnectInterval = this.options.reconnectInterval ?? this.env.WEBSOCKET_RECONNECT_INTERVAL ?? 3e3;
427
- if (maxAttempts !== -1 && this.reconnectAttempts >= maxAttempts) {
428
- this.log.warn("Max reconnection attempts reached", {
429
- attempts: this.reconnectAttempts,
430
- maxAttempts
431
- });
432
- return;
433
- }
434
- this.reconnectAttempts++;
435
- this.log.debug("Scheduling reconnection", {
436
- attempt: this.reconnectAttempts,
437
- maxAttempts,
438
- intervalMs: reconnectInterval
439
- });
440
- this.reconnectTimer = window.setTimeout(() => {
441
- this.log.info("Reconnecting...", {
442
- attempt: this.reconnectAttempts,
443
- maxAttempts
444
- });
445
- this.connect().catch((error) => {
446
- this.log.error("Reconnection failed:", error);
447
- });
448
- }, reconnectInterval);
449
- }
450
- /**
451
- * Disconnect from server
452
- */
453
- disconnect() {
454
- this.log.debug("Disconnecting", {
455
- hasTimer: !!this.reconnectTimer,
456
- hasConnection: !!this.ws
457
- });
458
- if (this.reconnectTimer) {
459
- clearTimeout(this.reconnectTimer);
460
- this.reconnectTimer = void 0;
461
- }
462
- if (this.ws) {
463
- this.ws.close();
464
- this.ws = void 0;
465
- }
466
- this.isConnected = false;
467
- this.isConnecting = false;
468
- this.connectPromise = void 0;
469
- this.log.info("Disconnected");
470
- }
471
- /**
472
- * Reconnect manually
473
- */
474
- reconnect() {
475
- this.log.info("Manual reconnect requested");
476
- this.disconnect();
477
- this.connect().catch((error) => {
478
- this.log.error("Manual reconnection failed:", error);
479
- });
480
- }
481
- /**
482
- * Check if subscribed to a room
483
- */
484
- hasRoom(roomId) {
485
- return this.subscriptions.has(roomId);
486
- }
487
- /**
488
- * Get all subscribed rooms
489
- */
490
- getRooms() {
491
- return Array.from(this.subscriptions.keys());
492
- }
493
- };
494
- /**
495
- * WebSocket Client Service
496
- *
497
- * Manages WebSocket connections from the client side (browser).
498
- * One connection per channel, multiple rooms per connection.
499
- */
500
- var WebSocketClient = class {
501
- log = $logger();
502
- alepha = $inject(Alepha);
503
- env = $env(envSchema);
504
- connections = /* @__PURE__ */ new Map();
505
- /**
506
- * Subscribe to a room on a channel
507
- */
508
- subscribe(roomId, channel, handler, options = {}) {
509
- const channelPath = channel.options.path;
510
- this.log.debug("WebSocketClient.subscribe", {
511
- roomId,
512
- channelPath,
513
- existingConnections: this.connections.size
514
- });
515
- let connection = this.connections.get(channelPath);
516
- if (!connection) {
517
- this.log.debug("Creating new connection for channel", { channelPath });
518
- connection = this.alepha.inject(WebSocketChannelConnection, {
519
- lifetime: "transient",
520
- args: [
521
- channel,
522
- {
523
- url: options.url,
524
- autoReconnect: options.autoReconnect,
525
- reconnectInterval: options.reconnectInterval,
526
- maxReconnectAttempts: options.maxReconnectAttempts
527
- },
528
- this.env
529
- ]
530
- });
531
- this.connections.set(channelPath, connection);
532
- } else this.log.trace("Reusing existing connection for channel", { channelPath });
533
- const unsubscribe = connection.subscribe(roomId, handler, {
534
- onConnect: options.onConnect,
535
- onDisconnect: options.onDisconnect,
536
- onError: options.onError
537
- });
538
- return () => {
539
- this.log.debug("WebSocketClient.unsubscribe", {
540
- roomId,
541
- channelPath
542
- });
543
- unsubscribe();
544
- if (connection.getRooms().length === 0) {
545
- this.log.debug("Removing connection for channel (no more rooms)", { channelPath });
546
- this.connections.delete(channelPath);
547
- }
548
- };
549
- }
550
- /**
551
- * Send message to a room on a channel
552
- */
553
- async send(roomId, channel, message) {
554
- const channelPath = channel.options.path;
555
- this.log.trace("WebSocketClient.send", {
556
- roomId,
557
- channelPath
558
- });
559
- const connection = this.connections.get(channelPath);
560
- if (!connection) {
561
- this.log.warn("Attempted to send on unsubscribed channel", { channelPath });
562
- throw new AlephaError(`Not subscribed to channel ${channelPath}. Subscribe first before sending messages.`);
563
- }
564
- await connection.send(roomId, message);
565
- }
566
- /**
567
- * Get connection for a channel
568
- */
569
- getConnection(channel) {
570
- const channelPath = channel.options.path;
571
- const connection = this.connections.get(channelPath);
572
- this.log.trace("WebSocketClient.getConnection", {
573
- channelPath,
574
- found: !!connection
575
- });
576
- return connection;
577
- }
578
- /**
579
- * Disconnect all connections
580
- */
581
- disconnectAll() {
582
- this.log.info("Disconnecting all connections", { count: this.connections.size });
583
- for (const connection of this.connections.values()) connection.disconnect();
584
- this.connections.clear();
585
- this.log.debug("All connections disconnected");
586
- }
587
- };
588
- //#endregion
589
- //#region ../../src/websocket/errors/WebSocketError.ts
590
- /**
591
- * Base WebSocket error class
592
- */
593
- var WebSocketError = class extends AlephaError {
594
- code;
595
- constructor(message, code) {
596
- super(message);
597
- this.code = code;
598
- this.name = "WebSocketError";
599
- }
600
- };
601
- /**
602
- * Error thrown when WebSocket connection fails
603
- */
604
- var WebSocketConnectionError = class extends WebSocketError {
605
- constructor(message, code) {
606
- super(message, code);
607
- this.name = "WebSocketConnectionError";
608
- }
609
- };
610
- /**
611
- * Error thrown when WebSocket message validation fails
612
- */
613
- var WebSocketValidationError = class extends WebSocketError {
614
- constructor(message) {
615
- super(message);
616
- this.name = "WebSocketValidationError";
617
- }
618
- };
619
- //#endregion
620
- //#region ../../src/websocket/interfaces/WebSocketInterfaces.ts
621
- /**
622
- * WebSocket state enum
623
- */
624
- let WebSocketState = /* @__PURE__ */ function(WebSocketState) {
625
- WebSocketState[WebSocketState["CONNECTING"] = 0] = "CONNECTING";
626
- WebSocketState[WebSocketState["OPEN"] = 1] = "OPEN";
627
- WebSocketState[WebSocketState["CLOSING"] = 2] = "CLOSING";
628
- WebSocketState[WebSocketState["CLOSED"] = 3] = "CLOSED";
629
- return WebSocketState;
630
- }({});
631
- //#endregion
632
- //#region ../../src/websocket/services/RoomManager.ts
633
- /**
634
- * Manages WebSocket room memberships
635
- *
636
- * Rooms are logical groupings of connections. A connection can be in multiple rooms,
637
- * and messages can be targeted to specific rooms.
638
- */
639
- var RoomManager = class {
640
- log = $logger();
641
- /**
642
- * Maps roomId → Set<connectionId>
643
- */
644
- rooms = /* @__PURE__ */ new Map();
645
- /**
646
- * Maps connectionId → Set<roomId>
647
- * Inverse index for fast lookup of connection's rooms
648
- */
649
- connectionRooms = /* @__PURE__ */ new Map();
650
- /**
651
- * Join a connection to one or more rooms
652
- */
653
- joinRooms(connectionId, roomIds) {
654
- for (const roomId of roomIds) this.joinRoom(connectionId, roomId);
655
- }
656
- /**
657
- * Join a connection to a room
658
- */
659
- joinRoom(connectionId, roomId) {
660
- let room = this.rooms.get(roomId);
661
- if (!room) {
662
- room = /* @__PURE__ */ new Set();
663
- this.rooms.set(roomId, room);
664
- }
665
- room.add(connectionId);
666
- let connRooms = this.connectionRooms.get(connectionId);
667
- if (!connRooms) {
668
- connRooms = /* @__PURE__ */ new Set();
669
- this.connectionRooms.set(connectionId, connRooms);
670
- }
671
- connRooms.add(roomId);
672
- this.log.debug(`Connection ${connectionId} joined room ${roomId}`);
673
- }
674
- /**
675
- * Leave a connection from a room
676
- */
677
- leaveRoom(connectionId, roomId) {
678
- const room = this.rooms.get(roomId);
679
- if (room) {
680
- room.delete(connectionId);
681
- if (room.size === 0) this.rooms.delete(roomId);
682
- }
683
- const connRooms = this.connectionRooms.get(connectionId);
684
- if (connRooms) {
685
- connRooms.delete(roomId);
686
- if (connRooms.size === 0) this.connectionRooms.delete(connectionId);
687
- }
688
- this.log.debug(`Connection ${connectionId} left room ${roomId}`);
689
- }
690
- /**
691
- * Remove a connection from all rooms
692
- */
693
- leaveAllRooms(connectionId) {
694
- const connRooms = this.connectionRooms.get(connectionId);
695
- if (!connRooms) return;
696
- for (const roomId of connRooms) {
697
- const room = this.rooms.get(roomId);
698
- if (room) {
699
- room.delete(connectionId);
700
- if (room.size === 0) this.rooms.delete(roomId);
701
- }
702
- }
703
- this.connectionRooms.delete(connectionId);
704
- this.log.debug(`Connection ${connectionId} left all rooms`);
705
- }
706
- /**
707
- * Get all connection IDs in a room
708
- */
709
- getRoomConnections(roomId) {
710
- const room = this.rooms.get(roomId);
711
- return room ? Array.from(room) : [];
712
- }
713
- /**
714
- * Get all room IDs for a connection
715
- */
716
- getConnectionRooms(connectionId) {
717
- const connRooms = this.connectionRooms.get(connectionId);
718
- return connRooms ? Array.from(connRooms) : [];
719
- }
720
- /**
721
- * Check if a connection is in a room
722
- */
723
- isInRoom(connectionId, roomId) {
724
- const connRooms = this.connectionRooms.get(connectionId);
725
- return connRooms ? connRooms.has(roomId) : false;
726
- }
727
- /**
728
- * Get all active rooms
729
- */
730
- getAllRooms() {
731
- return Array.from(this.rooms.keys());
732
- }
733
- /**
734
- * Get total number of connections across all rooms
735
- */
736
- getTotalConnections() {
737
- return this.connectionRooms.size;
738
- }
739
- /**
740
- * Get room statistics
741
- */
742
- getStats() {
743
- const roomSizes = /* @__PURE__ */ new Map();
744
- for (const [roomId, connections] of this.rooms) roomSizes.set(roomId, connections.size);
745
- return {
746
- totalRooms: this.rooms.size,
747
- totalConnections: this.connectionRooms.size,
748
- roomSizes
749
- };
750
- }
751
- };
752
- //#endregion
753
- //#region ../../src/websocket/services/WebSocketTopicService.ts
754
- /**
755
- * WebSocket message distribution event
756
- */
757
- const webSocketMessageSchema = { payload: t.object({
758
- /**
759
- * Channel path (e.g., "/ws/chat")
760
- */
761
- channelPath: t.text(),
762
- /**
763
- * Target room ID(s)
764
- */
765
- roomIds: t.optional(t.array(t.text())),
766
- /**
767
- * Target user ID(s)
768
- */
769
- userIds: t.optional(t.array(t.text())),
770
- /**
771
- * Target connection ID(s)
772
- */
773
- connectionIds: t.optional(t.array(t.text())),
774
- /**
775
- * Exclude connection ID(s) from receiving the message
776
- */
777
- exceptConnectionIds: t.optional(t.array(t.text())),
778
- /**
779
- * Exclude user ID(s) from receiving the message
780
- */
781
- exceptUserIds: t.optional(t.array(t.text())),
782
- /**
783
- * The message payload to send
784
- */
785
- message: t.any()
786
- }) };
787
- /**
788
- * WebSocket Topic Service
789
- *
790
- * Manages pub/sub messaging for WebSocket connections across multiple server instances.
791
- * Uses alepha/topic for cross-instance message distribution, enabling horizontal scaling.
792
- *
793
- * When a WebSocket message needs to be sent:
794
- * 1. Server instance A publishes to the topic
795
- * 2. All server instances (A, B, C, etc.) receive the message
796
- * 3. Each instance sends to its local connections that match the criteria
797
- *
798
- * This enables:
799
- * - Multiple server instances handling WebSocket connections
800
- * - Redis-backed message distribution (with alepha/topic/redis)
801
- * - Horizontal scaling without losing messages
802
- */
803
- var WebSocketTopicService = class {
804
- log = $logger();
805
- /**
806
- * Handler function to be called when a message is received from the topic
807
- * This is set by the WebSocket provider during initialization
808
- */
809
- messageHandler;
810
- /**
811
- * Topic for distributing WebSocket messages across server instances
812
- */
813
- topic = $topic({
814
- name: "websocket:broadcast",
815
- description: "Distributes WebSocket messages across server instances for horizontal scaling",
816
- schema: webSocketMessageSchema,
817
- handler: async (message) => {
818
- if (this.messageHandler) await this.messageHandler(message.payload);
819
- }
820
- });
821
- /**
822
- * Publish a message to be distributed across all server instances
823
- */
824
- async publish(event) {
825
- await this.topic.publish(event);
826
- }
827
- /**
828
- * Set the handler for incoming messages
829
- */
830
- setMessageHandler(handler) {
831
- this.messageHandler = handler;
832
- }
833
- };
834
- //#endregion
835
- //#region ../../src/websocket/index.browser.ts
836
- const AlephaWebSocket = $module({
837
- name: "alepha.websocket",
838
- primitives: [$channel, $websocket],
839
- services: [WebSocketClient],
840
- register: (alepha) => {
841
- alepha.with(AlephaTopic);
842
- alepha.with(WebSocketClient);
843
- }
844
- });
845
- //#endregion
846
- export { $channel, $websocket, AlephaWebSocket, ChannelPrimitive, RoomManager, WebSocketChannelConnection, WebSocketClient, WebSocketConnectionError, WebSocketError, WebSocketPrimitive, WebSocketServerProvider, WebSocketState, WebSocketTopicService, WebSocketValidationError };
847
-
848
- //# sourceMappingURL=index.browser.js.map