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.
- package/AGENTS.md +0 -1
- package/CLAUDE.md +0 -1
- package/assets/agents-template.md +0 -1
- package/dist/api/audits/index.browser.js +1 -0
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +370 -355
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +1 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +1 -0
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +179 -170
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +1 -0
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +7 -0
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +271 -262
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +21 -3
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +198 -192
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js +1 -0
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +246 -245
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/organizations/index.d.ts +100 -97
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +323 -320
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/payments/index.d.ts +431 -376
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js +202 -87
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/subscriptions/index.d.ts +1695 -0
- package/dist/api/subscriptions/index.d.ts.map +1 -0
- package/dist/api/subscriptions/index.js +1919 -0
- package/dist/api/subscriptions/index.js.map +1 -0
- package/dist/api/users/index.d.ts +863 -847
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/verifications/index.d.ts +126 -125
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/bucket/index.d.ts +3 -2
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/cache/core/index.d.ts +114 -4
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +181 -15
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js +181 -15
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cache/database/index.d.ts +20 -19
- package/dist/cache/database/index.d.ts.map +1 -1
- package/dist/cache/redis/index.d.ts +3 -2
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cli/core/index.d.ts +113 -129
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +75 -7
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +3 -2
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/platform/index.d.ts +346 -290
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +105 -6
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +12 -11
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/command/index.d.ts +5 -4
- package/dist/command/index.d.ts.map +1 -1
- package/dist/core/index.browser.js +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +119 -118
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +1 -1
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.d.ts +3 -2
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/email/core/index.d.ts +3 -2
- package/dist/email/core/index.d.ts.map +1 -1
- package/dist/email/smtp/index.d.ts +7 -6
- package/dist/email/smtp/index.d.ts.map +1 -1
- package/dist/lock/core/index.d.ts +5 -4
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +10 -9
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +9 -8
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +9 -3
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +31 -10
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +33 -14
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +31 -10
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +6 -5
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/queue/core/index.d.ts +5 -4
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/redis/index.d.ts +3 -2
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/react/form/index.d.ts +5 -0
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +6 -4
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +2 -1
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/router/index.d.ts +206 -205
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/ui/index.d.ts +11 -11
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +3 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/security/index.browser.js +29 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +82 -35
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +56 -3
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +163 -158
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +16 -4
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/core/index.d.ts +35 -34
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/cors/index.d.ts +7 -6
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/health/index.d.ts +16 -15
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/links/index.d.ts +51 -50
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.d.ts +6 -5
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/swagger/index.d.ts +2 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/topic/redis/index.d.ts +3 -2
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/package.json +16 -32
- package/src/api/audits/entities/audits.ts +1 -0
- package/src/api/files/entities/files.ts +1 -0
- package/src/api/jobs/__tests__/$job.spec.ts +92 -40
- package/src/api/jobs/entities/jobExecutionEntity.ts +1 -0
- package/src/api/jobs/providers/JobProvider.ts +20 -5
- package/src/api/jobs/schemas/jobConfigAtom.ts +5 -0
- package/src/api/keys/entities/apiKeyEntity.ts +1 -0
- package/src/api/payments/controllers/MockCheckoutController.ts +146 -0
- package/src/api/payments/index.ts +3 -0
- package/src/api/payments/providers/MemoryPaymentProvider.ts +9 -4
- package/src/api/payments/providers/PaymentProvider.ts +25 -9
- package/src/api/payments/services/PaymentService.ts +3 -0
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
- package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
- package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
- package/src/api/subscriptions/entities/subscriptions.ts +68 -0
- package/src/api/subscriptions/index.ts +133 -0
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
- package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
- package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
- package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
- package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
- package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
- package/src/api/subscriptions/services/BillingService.ts +437 -0
- package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
- package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
- package/src/api/subscriptions/services/UsageService.ts +118 -0
- package/src/cache/core/__tests__/$cache.memory.spec.ts +450 -0
- package/src/cache/core/__tests__/$cache.swr.spec.ts +394 -0
- package/src/cache/core/index.ts +16 -0
- package/src/cache/core/primitives/$cache.ts +347 -21
- package/src/cli/core/tasks/BuildCloudflareTask.ts +16 -0
- package/src/cli/core/templates/agentMd.ts +39 -4
- package/src/cli/core/templates/biomeJson.ts +25 -1
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +2 -2
- package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +117 -0
- package/src/cli/platform/adapters/CloudflareAdapter.ts +104 -7
- package/src/cli/platform/atoms/platformOptions.ts +13 -0
- package/src/cli/platform/schemas/platform.ts +1 -0
- package/src/cli/platform/services/CloudflareApi.ts +61 -0
- package/src/cli/platform/services/PlatformOrchestrator.ts +9 -4
- package/src/core/__tests__/$module.spec.ts +2 -2
- package/src/core/primitives/$module.ts +4 -4
- package/src/mcp/providers/McpServerProvider.ts +1 -1
- package/src/orm/core/providers/DatabaseTypeProvider.ts +9 -3
- package/src/orm/core/providers/drivers/DatabaseProvider.ts +1 -1
- package/src/orm/core/schemas/insertSchema.ts +10 -2
- package/src/orm/core/services/Repository.ts +27 -7
- package/src/react/form/hooks/useFormState.ts +8 -1
- package/src/react/form/index.ts +10 -1
- package/src/react/form/services/FormModel.ts +9 -3
- package/src/security/atoms/currentTenantAtom.ts +34 -0
- package/src/security/index.browser.ts +1 -0
- package/src/security/index.ts +12 -1
- package/src/security/primitives/$issuer.ts +17 -1
- package/src/security/providers/SecurityProvider.ts +37 -0
- package/src/server/auth/__tests__/validateRedirectUri.spec.ts +78 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +21 -5
- package/tsconfig.base.json +2 -1
- package/dist/react/websocket/index.d.ts +0 -117
- package/dist/react/websocket/index.d.ts.map +0 -1
- package/dist/react/websocket/index.js +0 -108
- package/dist/react/websocket/index.js.map +0 -1
- package/dist/websocket/index.browser.js +0 -848
- package/dist/websocket/index.browser.js.map +0 -1
- package/dist/websocket/index.d.ts +0 -876
- package/dist/websocket/index.d.ts.map +0 -1
- package/dist/websocket/index.js +0 -1185
- package/dist/websocket/index.js.map +0 -1
- package/src/react/websocket/hooks/useRoom.tsx +0 -251
- package/src/react/websocket/index.ts +0 -7
- package/src/websocket/__tests__/$channel.spec.ts +0 -30
- package/src/websocket/__tests__/$websocket-new.spec.ts +0 -195
- package/src/websocket/__tests__/RoomManager.spec.ts +0 -146
- package/src/websocket/__tests__/websocket-integration.spec.ts +0 -951
- package/src/websocket/errors/WebSocketError.ts +0 -34
- package/src/websocket/index.browser.ts +0 -25
- package/src/websocket/index.shared.ts +0 -8
- package/src/websocket/index.ts +0 -85
- package/src/websocket/interfaces/WebSocketInterfaces.ts +0 -252
- package/src/websocket/primitives/$channel.ts +0 -131
- package/src/websocket/primitives/$websocket.ts +0 -107
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +0 -617
- package/src/websocket/providers/WebSocketServerProvider.ts +0 -56
- package/src/websocket/services/RoomManager.ts +0 -160
- package/src/websocket/services/WebSocketClient.ts +0 -642
- 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
|