@gravito/ripple 3.0.1 → 4.0.0
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/README.md +432 -18
- package/README.zh-TW.md +104 -2
- package/dist/atlas/src/DB.d.ts +301 -0
- package/dist/atlas/src/OrbitAtlas.d.ts +9 -0
- package/dist/atlas/src/config/defineConfig.d.ts +14 -0
- package/dist/atlas/src/config/index.d.ts +7 -0
- package/dist/atlas/src/config/loadConfig.d.ts +48 -0
- package/dist/atlas/src/connection/Connection.d.ts +108 -0
- package/dist/atlas/src/connection/ConnectionManager.d.ts +111 -0
- package/dist/atlas/src/drivers/BunSQLDriver.d.ts +32 -0
- package/dist/atlas/src/drivers/BunSQLPreparedStatement.d.ts +118 -0
- package/dist/atlas/src/drivers/MongoDBDriver.d.ts +36 -0
- package/dist/atlas/src/drivers/MySQLDriver.d.ts +66 -0
- package/dist/atlas/src/drivers/PostgresDriver.d.ts +83 -0
- package/dist/atlas/src/drivers/RedisDriver.d.ts +43 -0
- package/dist/atlas/src/drivers/SQLiteDriver.d.ts +45 -0
- package/dist/atlas/src/drivers/types.d.ts +260 -0
- package/dist/atlas/src/errors/index.d.ts +45 -0
- package/dist/atlas/src/grammar/Grammar.d.ts +342 -0
- package/dist/atlas/src/grammar/MongoGrammar.d.ts +47 -0
- package/dist/atlas/src/grammar/MySQLGrammar.d.ts +54 -0
- package/dist/atlas/src/grammar/NullGrammar.d.ts +35 -0
- package/dist/atlas/src/grammar/PostgresGrammar.d.ts +62 -0
- package/dist/atlas/src/grammar/SQLiteGrammar.d.ts +32 -0
- package/dist/atlas/src/index.d.ts +67 -0
- package/dist/atlas/src/migration/Migration.d.ts +64 -0
- package/dist/atlas/src/migration/MigrationRepository.d.ts +65 -0
- package/dist/atlas/src/migration/Migrator.d.ts +110 -0
- package/dist/atlas/src/migration/index.d.ts +6 -0
- package/dist/atlas/src/observability/AtlasMetrics.d.ts +11 -0
- package/dist/atlas/src/observability/AtlasObservability.d.ts +15 -0
- package/dist/atlas/src/observability/AtlasTracer.d.ts +12 -0
- package/dist/atlas/src/observability/index.d.ts +9 -0
- package/dist/atlas/src/orm/index.d.ts +5 -0
- package/dist/atlas/src/orm/model/DirtyTracker.d.ts +121 -0
- package/dist/atlas/src/orm/model/Model.d.ts +449 -0
- package/dist/atlas/src/orm/model/ModelRegistry.d.ts +20 -0
- package/dist/atlas/src/orm/model/concerns/HasAttributes.d.ts +136 -0
- package/dist/atlas/src/orm/model/concerns/HasEvents.d.ts +36 -0
- package/dist/atlas/src/orm/model/concerns/HasPersistence.d.ts +87 -0
- package/dist/atlas/src/orm/model/concerns/HasRelationships.d.ts +117 -0
- package/dist/atlas/src/orm/model/concerns/HasSerialization.d.ts +64 -0
- package/dist/atlas/src/orm/model/concerns/applyMixins.d.ts +15 -0
- package/dist/atlas/src/orm/model/concerns/index.d.ts +12 -0
- package/dist/atlas/src/orm/model/decorators.d.ts +109 -0
- package/dist/atlas/src/orm/model/errors.d.ts +52 -0
- package/dist/atlas/src/orm/model/index.d.ts +10 -0
- package/dist/atlas/src/orm/model/relationships.d.ts +207 -0
- package/dist/atlas/src/orm/model/types.d.ts +12 -0
- package/dist/atlas/src/orm/schema/SchemaRegistry.d.ts +123 -0
- package/dist/atlas/src/orm/schema/SchemaSniffer.d.ts +54 -0
- package/dist/atlas/src/orm/schema/index.d.ts +6 -0
- package/dist/atlas/src/orm/schema/types.d.ts +85 -0
- package/dist/atlas/src/query/Expression.d.ts +60 -0
- package/dist/atlas/src/query/NPlusOneDetector.d.ts +10 -0
- package/dist/atlas/src/query/QueryBuilder.d.ts +573 -0
- package/dist/atlas/src/query/clauses/GroupByClause.d.ts +51 -0
- package/dist/atlas/src/query/clauses/HavingClause.d.ts +70 -0
- package/dist/atlas/src/query/clauses/JoinClause.d.ts +87 -0
- package/dist/atlas/src/query/clauses/LimitClause.d.ts +82 -0
- package/dist/atlas/src/query/clauses/OrderByClause.d.ts +69 -0
- package/dist/atlas/src/query/clauses/SelectClause.d.ts +71 -0
- package/dist/atlas/src/query/clauses/WhereClause.d.ts +167 -0
- package/dist/atlas/src/query/clauses/index.d.ts +11 -0
- package/dist/atlas/src/schema/Blueprint.d.ts +276 -0
- package/dist/atlas/src/schema/ColumnDefinition.d.ts +154 -0
- package/dist/atlas/src/schema/ForeignKeyDefinition.d.ts +37 -0
- package/dist/atlas/src/schema/Schema.d.ts +131 -0
- package/dist/atlas/src/schema/grammars/MySQLSchemaGrammar.d.ts +23 -0
- package/dist/atlas/src/schema/grammars/PostgresSchemaGrammar.d.ts +26 -0
- package/dist/atlas/src/schema/grammars/SQLiteSchemaGrammar.d.ts +28 -0
- package/dist/atlas/src/schema/grammars/SchemaGrammar.d.ts +97 -0
- package/dist/atlas/src/schema/grammars/index.d.ts +7 -0
- package/dist/atlas/src/schema/index.d.ts +8 -0
- package/dist/atlas/src/seed/Factory.d.ts +90 -0
- package/dist/atlas/src/seed/Seeder.d.ts +28 -0
- package/dist/atlas/src/seed/SeederRunner.d.ts +74 -0
- package/dist/atlas/src/seed/index.d.ts +6 -0
- package/dist/atlas/src/types/index.d.ts +1100 -0
- package/dist/atlas/src/utils/levenshtein.d.ts +9 -0
- package/dist/core/src/Application.d.ts +43 -17
- package/dist/core/src/CommandKernel.d.ts +33 -0
- package/dist/core/src/Container.d.ts +78 -14
- package/dist/core/src/HookManager.d.ts +422 -8
- package/dist/core/src/PlanetCore.d.ts +52 -7
- package/dist/core/src/Router.d.ts +41 -7
- package/dist/core/src/ServiceProvider.d.ts +14 -8
- package/dist/core/src/adapters/GravitoEngineAdapter.d.ts +1 -0
- package/dist/core/src/adapters/PhotonAdapter.d.ts +1 -0
- package/dist/core/src/adapters/bun/BunNativeAdapter.d.ts +1 -0
- package/dist/core/src/adapters/types.d.ts +39 -0
- package/dist/core/src/engine/AOTRouter.d.ts +1 -11
- package/dist/core/src/engine/FastContext.d.ts +4 -2
- package/dist/core/src/engine/Gravito.d.ts +1 -1
- package/dist/core/src/engine/MinimalContext.d.ts +4 -2
- package/dist/core/src/engine/types.d.ts +6 -1
- package/dist/core/src/events/CircuitBreaker.d.ts +229 -0
- package/dist/core/src/events/DeadLetterQueue.d.ts +145 -0
- package/dist/core/src/events/EventBackend.d.ts +11 -0
- package/dist/core/src/events/EventOptions.d.ts +109 -0
- package/dist/core/src/events/EventPriorityQueue.d.ts +202 -0
- package/dist/core/src/events/IdempotencyCache.d.ts +60 -0
- package/dist/core/src/events/index.d.ts +14 -0
- package/dist/core/src/events/observability/EventMetrics.d.ts +132 -0
- package/dist/core/src/events/observability/EventTracer.d.ts +68 -0
- package/dist/core/src/events/observability/EventTracing.d.ts +161 -0
- package/dist/core/src/events/observability/OTelEventMetrics.d.ts +240 -0
- package/dist/core/src/events/observability/ObservableHookManager.d.ts +108 -0
- package/dist/core/src/events/observability/index.d.ts +20 -0
- package/dist/core/src/events/observability/metrics-types.d.ts +16 -0
- package/dist/core/src/events/types.d.ts +75 -0
- package/dist/core/src/exceptions/CircularDependencyException.d.ts +9 -0
- package/dist/core/src/exceptions/index.d.ts +1 -0
- package/dist/core/src/http/cookie.d.ts +29 -0
- package/dist/core/src/http/types.d.ts +21 -0
- package/dist/core/src/index.d.ts +13 -3
- package/dist/core/src/instrumentation/index.d.ts +35 -0
- package/dist/core/src/instrumentation/opentelemetry.d.ts +178 -0
- package/dist/core/src/instrumentation/types.d.ts +182 -0
- package/dist/core/src/reliability/DeadLetterQueueManager.d.ts +316 -0
- package/dist/core/src/reliability/RetryPolicy.d.ts +217 -0
- package/dist/core/src/reliability/index.d.ts +6 -0
- package/dist/core/src/router/ControllerDispatcher.d.ts +12 -0
- package/dist/core/src/router/RequestValidator.d.ts +20 -0
- package/dist/index.js +6487 -9562
- package/dist/index.js.map +68 -62
- package/dist/photon/src/index.d.ts +69 -5
- package/dist/photon/src/middleware/binary.d.ts +12 -15
- package/dist/photon/src/middleware/htmx.d.ts +39 -0
- package/dist/photon/src/middleware/ratelimit.d.ts +157 -0
- package/dist/photon/src/openapi.d.ts +19 -0
- package/dist/proto/ripple.proto +120 -0
- package/dist/ripple/src/OrbitRipple.d.ts +34 -12
- package/dist/ripple/src/RippleServer.d.ts +76 -63
- package/dist/ripple/src/channels/ChannelManager.d.ts +132 -22
- package/dist/ripple/src/drivers/LocalDriver.d.ts +43 -11
- package/dist/ripple/src/drivers/NATSDriver.d.ts +87 -0
- package/dist/ripple/src/drivers/RedisDriver.d.ts +135 -28
- package/dist/ripple/src/drivers/index.d.ts +1 -0
- package/dist/ripple/src/engines/BunEngine.d.ts +98 -0
- package/dist/ripple/src/engines/IRippleEngine.d.ts +205 -0
- package/dist/ripple/src/engines/index.d.ts +11 -0
- package/dist/ripple/src/errors/RippleError.d.ts +48 -0
- package/dist/ripple/src/errors/index.d.ts +1 -0
- package/dist/ripple/src/events/BroadcastEvent.d.ts +78 -6
- package/dist/ripple/src/events/BroadcastManager.d.ts +100 -0
- package/dist/ripple/src/events/Broadcaster.d.ts +211 -14
- package/dist/ripple/src/events/index.d.ts +1 -0
- package/dist/ripple/src/health/HealthChecker.d.ts +93 -0
- package/dist/ripple/src/health/index.d.ts +1 -0
- package/dist/ripple/src/index.d.ts +42 -17
- package/dist/ripple/src/logging/Logger.d.ts +99 -0
- package/dist/ripple/src/logging/index.d.ts +1 -0
- package/dist/ripple/src/middleware/InterceptorManager.d.ts +21 -0
- package/dist/ripple/src/observability/RippleMetrics.d.ts +24 -0
- package/dist/ripple/src/reliability/AckManager.d.ts +48 -0
- package/dist/ripple/src/serializers/ISerializer.d.ts +39 -0
- package/dist/ripple/src/serializers/JsonSerializer.d.ts +19 -0
- package/dist/ripple/src/serializers/ProtobufSerializer.d.ts +38 -0
- package/dist/ripple/src/serializers/index.d.ts +3 -0
- package/dist/ripple/src/tracking/ConnectionTracker.d.ts +116 -0
- package/dist/ripple/src/tracking/SessionManager.d.ts +104 -0
- package/dist/ripple/src/tracking/index.d.ts +2 -0
- package/dist/ripple/src/types.d.ts +766 -28
- package/dist/ripple/src/utils/MessageSerializer.d.ts +54 -0
- package/dist/ripple/src/utils/TokenBucket.d.ts +25 -0
- package/dist/ripple/src/utils/index.d.ts +1 -0
- package/package.json +25 -7
|
@@ -3,8 +3,23 @@
|
|
|
3
3
|
* @module @gravito/ripple
|
|
4
4
|
*/
|
|
5
5
|
import type { Server, ServerWebSocket } from 'bun';
|
|
6
|
+
import type { NATSDriverConfig } from './drivers/NATSDriver';
|
|
7
|
+
import type { RedisDriverConfig } from './drivers/RedisDriver';
|
|
6
8
|
/**
|
|
7
|
-
* Data attached to each WebSocket connection
|
|
9
|
+
* Data attached to each WebSocket connection.
|
|
10
|
+
*
|
|
11
|
+
* Contains client identification, authentication state, channel subscriptions,
|
|
12
|
+
* and presence information. This data is accessed via `ws.data` on any RippleWebSocket.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Access client data in event handlers
|
|
17
|
+
* const handleMessage = (ws: RippleWebSocket, message: string) => {
|
|
18
|
+
* console.log('Client ID:', ws.data.id)
|
|
19
|
+
* console.log('User ID:', ws.data.userId)
|
|
20
|
+
* console.log('Channels:', Array.from(ws.data.channels))
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
8
23
|
*/
|
|
9
24
|
export interface ClientData {
|
|
10
25
|
/** Unique client identifier */
|
|
@@ -15,16 +30,47 @@ export interface ClientData {
|
|
|
15
30
|
channels: Set<string>;
|
|
16
31
|
/** Additional user info for presence channels */
|
|
17
32
|
userInfo?: Record<string, unknown>;
|
|
33
|
+
/** Reconnection token for session recovery (v3.6+) */
|
|
34
|
+
reconnectionToken?: string;
|
|
35
|
+
/** Session expiry timestamp (v3.6+) */
|
|
36
|
+
sessionExpiry?: number;
|
|
18
37
|
}
|
|
19
38
|
/**
|
|
20
39
|
* Supported WebSocket channel types.
|
|
21
40
|
*
|
|
41
|
+
* - `public`: Open channels accessible to all clients without authentication
|
|
42
|
+
* - `private`: Channels requiring authorization via the authorizer callback
|
|
43
|
+
* - `presence`: Private channels that track online users and their metadata
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* // Channel type determines the authorization flow
|
|
48
|
+
* const publicChannel: ChannelType = 'public' // No auth needed
|
|
49
|
+
* const privateChannel: ChannelType = 'private' // Boolean auth result
|
|
50
|
+
* const presenceChannel: ChannelType = 'presence' // PresenceUserInfo required
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
22
53
|
* @public
|
|
23
54
|
* @since 3.0.0
|
|
24
55
|
*/
|
|
25
56
|
export type ChannelType = 'public' | 'private' | 'presence';
|
|
26
57
|
/**
|
|
27
|
-
* Base channel interface
|
|
58
|
+
* Base channel interface representing a WebSocket channel.
|
|
59
|
+
*
|
|
60
|
+
* Channels are the core abstraction for organizing WebSocket communications.
|
|
61
|
+
* Each channel has a type (public/private/presence) and manages subscriptions.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* import { PublicChannel, PrivateChannel } from '@gravito/ripple'
|
|
66
|
+
*
|
|
67
|
+
* // Create different channel types
|
|
68
|
+
* const news = new PublicChannel('news')
|
|
69
|
+
* console.log(news.fullName) // 'news'
|
|
70
|
+
*
|
|
71
|
+
* const orders = new PrivateChannel('orders.123')
|
|
72
|
+
* console.log(orders.fullName) // 'private-orders.123'
|
|
73
|
+
* ```
|
|
28
74
|
*/
|
|
29
75
|
export interface Channel {
|
|
30
76
|
/** Channel name (without prefix) */
|
|
@@ -35,18 +81,132 @@ export interface Channel {
|
|
|
35
81
|
readonly fullName: string;
|
|
36
82
|
}
|
|
37
83
|
/**
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
84
|
+
* User information for presence channels.
|
|
85
|
+
*
|
|
86
|
+
* Defines the shape of user data returned by the authorizer for presence channels.
|
|
87
|
+
* This data is shared with all members of the presence channel.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* // Return presence user info from authorizer
|
|
92
|
+
* const authorizer: ChannelAuthorizer = async (channel, userId, socketId) => {
|
|
93
|
+
* if (channel.startsWith('presence-chat.')) {
|
|
94
|
+
* const user = await db.users.findById(userId)
|
|
95
|
+
* return {
|
|
96
|
+
* id: user.id,
|
|
97
|
+
* info: {
|
|
98
|
+
* name: user.name,
|
|
99
|
+
* avatar: user.avatarUrl,
|
|
100
|
+
* status: 'online'
|
|
101
|
+
* }
|
|
102
|
+
* }
|
|
103
|
+
* }
|
|
104
|
+
* return true
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
43
107
|
*/
|
|
44
108
|
export interface PresenceUserInfo {
|
|
109
|
+
/** Unique user identifier */
|
|
45
110
|
id: string | number;
|
|
111
|
+
/** Additional user metadata shared with channel members */
|
|
46
112
|
info: Record<string, unknown>;
|
|
47
113
|
}
|
|
48
114
|
/**
|
|
49
|
-
*
|
|
115
|
+
* Authorization callback for channel subscriptions.
|
|
116
|
+
*
|
|
117
|
+
* Determines whether a client can subscribe to a given channel. The return value
|
|
118
|
+
* depends on the channel type:
|
|
119
|
+
*
|
|
120
|
+
* - **Public channels**: Always return `true` or omit authorization
|
|
121
|
+
* - **Private channels**: Return `boolean` or `Promise<boolean>`
|
|
122
|
+
* - **Presence channels**: Return `PresenceUserInfo` or `Promise<PresenceUserInfo | false>`
|
|
123
|
+
*
|
|
124
|
+
* @param channelName - Full channel name including prefix (e.g., 'private-orders.123')
|
|
125
|
+
* @param userId - User ID from authenticated session (undefined for unauthenticated)
|
|
126
|
+
* @param socketId - Unique WebSocket connection ID
|
|
127
|
+
* @returns Authorization result:
|
|
128
|
+
* - `true` = authorized
|
|
129
|
+
* - `false` = denied
|
|
130
|
+
* - `PresenceUserInfo` = authorized with user data (presence channels only)
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* // Example 1: Public channel (no authorization)
|
|
135
|
+
* const publicAuthorizer: ChannelAuthorizer = (channel, userId, socketId) => {
|
|
136
|
+
* return true // Allow all
|
|
137
|
+
* }
|
|
138
|
+
*
|
|
139
|
+
* // Example 2: Private channel with simple auth
|
|
140
|
+
* const privateAuthorizer: ChannelAuthorizer = (channel, userId, socketId) => {
|
|
141
|
+
* if (channel === 'private-admin') {
|
|
142
|
+
* return userId === 'admin' // Only admin user
|
|
143
|
+
* }
|
|
144
|
+
* return userId !== undefined // Require authentication
|
|
145
|
+
* }
|
|
146
|
+
*
|
|
147
|
+
* // Example 3: Presence channel with async database lookup
|
|
148
|
+
* const presenceAuthorizer: ChannelAuthorizer = async (channel, userId, socketId) => {
|
|
149
|
+
* if (channel.startsWith('presence-chat.')) {
|
|
150
|
+
* if (!userId) return false
|
|
151
|
+
*
|
|
152
|
+
* const user = await db.users.findById(userId)
|
|
153
|
+
* if (!user) return false
|
|
154
|
+
*
|
|
155
|
+
* return {
|
|
156
|
+
* id: user.id,
|
|
157
|
+
* info: {
|
|
158
|
+
* name: user.name,
|
|
159
|
+
* avatar: user.avatarUrl,
|
|
160
|
+
* role: user.role
|
|
161
|
+
* }
|
|
162
|
+
* }
|
|
163
|
+
* }
|
|
164
|
+
*
|
|
165
|
+
* // Default: require authentication
|
|
166
|
+
* return userId !== undefined
|
|
167
|
+
* }
|
|
168
|
+
*
|
|
169
|
+
* // Example 4: Resource ownership check
|
|
170
|
+
* const ownershipAuthorizer: ChannelAuthorizer = async (channel, userId, socketId) => {
|
|
171
|
+
* const match = channel.match(/^private-orders\.(\d+)$/)
|
|
172
|
+
* if (match) {
|
|
173
|
+
* const orderId = match[1]
|
|
174
|
+
* const order = await db.orders.findById(orderId)
|
|
175
|
+
* return order.userId === userId // Only order owner
|
|
176
|
+
* }
|
|
177
|
+
* return false
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export type ChannelAuthorizer = (channelName: string, userId: string | number | undefined, socketId: string) => boolean | PresenceUserInfo | Promise<boolean | PresenceUserInfo>;
|
|
182
|
+
/**
|
|
183
|
+
* Interface for broadcast event classes.
|
|
184
|
+
*
|
|
185
|
+
* Implement this interface to create type-safe broadcast events that can be
|
|
186
|
+
* dispatched using the `broadcast()` function or `BroadcastManager`.
|
|
187
|
+
*
|
|
188
|
+
* @see {@link BroadcastEvent} - Abstract base class implementation
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* import { BroadcastEventInterface, Channel, PrivateChannel } from '@gravito/ripple'
|
|
193
|
+
*
|
|
194
|
+
* class OrderShipped implements BroadcastEventInterface {
|
|
195
|
+
* constructor(public order: { id: number; userId: number }) {}
|
|
196
|
+
*
|
|
197
|
+
* broadcastOn(): Channel {
|
|
198
|
+
* return new PrivateChannel(`orders.${this.order.userId}`)
|
|
199
|
+
* }
|
|
200
|
+
*
|
|
201
|
+
* broadcastAs(): string {
|
|
202
|
+
* return 'OrderShipped'
|
|
203
|
+
* }
|
|
204
|
+
*
|
|
205
|
+
* broadcastExcept(): string[] {
|
|
206
|
+
* return [] // Don't exclude anyone
|
|
207
|
+
* }
|
|
208
|
+
* }
|
|
209
|
+
* ```
|
|
50
210
|
*/
|
|
51
211
|
export interface BroadcastEventInterface {
|
|
52
212
|
/** Channels to broadcast to */
|
|
@@ -57,7 +217,37 @@ export interface BroadcastEventInterface {
|
|
|
57
217
|
broadcastExcept?(): string[];
|
|
58
218
|
}
|
|
59
219
|
/**
|
|
60
|
-
*
|
|
220
|
+
* Messages sent from client to server over WebSocket.
|
|
221
|
+
*
|
|
222
|
+
* This discriminated union defines the protocol for client-to-server communication.
|
|
223
|
+
* All messages must have a `type` field for proper routing.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```typescript
|
|
227
|
+
* // Subscribe to a channel
|
|
228
|
+
* const subscribeMsg: ClientMessage = {
|
|
229
|
+
* type: 'subscribe',
|
|
230
|
+
* channel: 'private-orders.123',
|
|
231
|
+
* auth: { socketId: 'abc', signature: 'xyz' }
|
|
232
|
+
* }
|
|
233
|
+
*
|
|
234
|
+
* // Unsubscribe from a channel
|
|
235
|
+
* const unsubscribeMsg: ClientMessage = {
|
|
236
|
+
* type: 'unsubscribe',
|
|
237
|
+
* channel: 'news'
|
|
238
|
+
* }
|
|
239
|
+
*
|
|
240
|
+
* // Send a whisper (client-to-client event)
|
|
241
|
+
* const whisperMsg: ClientMessage = {
|
|
242
|
+
* type: 'whisper',
|
|
243
|
+
* channel: 'presence-chat.lobby',
|
|
244
|
+
* event: 'typing',
|
|
245
|
+
* data: { userId: 123, isTyping: true }
|
|
246
|
+
* }
|
|
247
|
+
*
|
|
248
|
+
* // Ping for connection health check
|
|
249
|
+
* const pingMsg: ClientMessage = { type: 'ping' }
|
|
250
|
+
* ```
|
|
61
251
|
*/
|
|
62
252
|
export type ClientMessage = {
|
|
63
253
|
type: 'subscribe';
|
|
@@ -76,9 +266,86 @@ export type ClientMessage = {
|
|
|
76
266
|
data: unknown;
|
|
77
267
|
} | {
|
|
78
268
|
type: 'ping';
|
|
269
|
+
} | {
|
|
270
|
+
type: 'binary';
|
|
271
|
+
channel: string;
|
|
272
|
+
event: string;
|
|
273
|
+
data: ArrayBuffer;
|
|
274
|
+
} | {
|
|
275
|
+
type: 'ack';
|
|
276
|
+
seq: number;
|
|
79
277
|
};
|
|
80
278
|
/**
|
|
81
|
-
*
|
|
279
|
+
* Error codes for Ripple WebSocket protocol.
|
|
280
|
+
*
|
|
281
|
+
* Used in `ErrorServerMessage` to communicate specific failure reasons to clients.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* // Send authorization error to client
|
|
286
|
+
* const error: ErrorServerMessage = {
|
|
287
|
+
* type: 'error',
|
|
288
|
+
* code: 'UNAUTHORIZED',
|
|
289
|
+
* message: 'You are not authorized to join this channel',
|
|
290
|
+
* channel: 'private-orders.123'
|
|
291
|
+
* }
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
export type RippleErrorCode = 'UNAUTHORIZED' | 'NOT_SUBSCRIBED' | 'INVALID_FORMAT' | 'DRIVER_NOT_INITIALIZED' | 'REDIS_NOT_INSTALLED' | 'REDIS_CONNECTION_FAILED';
|
|
295
|
+
/**
|
|
296
|
+
* Error message sent from server to client.
|
|
297
|
+
*
|
|
298
|
+
* Contains a typed error code and human-readable message.
|
|
299
|
+
*
|
|
300
|
+
* @deprecated Use the `ServerMessage` discriminated union instead
|
|
301
|
+
*/
|
|
302
|
+
export interface ErrorServerMessage {
|
|
303
|
+
type: 'error';
|
|
304
|
+
code: RippleErrorCode;
|
|
305
|
+
message: string;
|
|
306
|
+
channel?: string;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Messages sent from server to client over WebSocket.
|
|
310
|
+
*
|
|
311
|
+
* This discriminated union defines the protocol for server-to-client communication.
|
|
312
|
+
* Clients should handle messages based on the `type` field.
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* // Handle incoming server messages
|
|
317
|
+
* ws.onmessage = (event) => {
|
|
318
|
+
* const message: ServerMessage = JSON.parse(event.data)
|
|
319
|
+
*
|
|
320
|
+
* switch (message.type) {
|
|
321
|
+
* case 'connected':
|
|
322
|
+
* console.log('Connected with socket ID:', message.socketId)
|
|
323
|
+
* break
|
|
324
|
+
*
|
|
325
|
+
* case 'subscribed':
|
|
326
|
+
* console.log('Subscribed to:', message.channel)
|
|
327
|
+
* break
|
|
328
|
+
*
|
|
329
|
+
* case 'event':
|
|
330
|
+
* console.log(`Event "${message.event}" on ${message.channel}:`, message.data)
|
|
331
|
+
* break
|
|
332
|
+
*
|
|
333
|
+
* case 'presence':
|
|
334
|
+
* if (message.event === 'join') {
|
|
335
|
+
* console.log('User joined:', message.data)
|
|
336
|
+
* }
|
|
337
|
+
* break
|
|
338
|
+
*
|
|
339
|
+
* case 'error':
|
|
340
|
+
* console.error('Error:', message.message, message.code)
|
|
341
|
+
* break
|
|
342
|
+
*
|
|
343
|
+
* case 'pong':
|
|
344
|
+
* // Heartbeat response
|
|
345
|
+
* break
|
|
346
|
+
* }
|
|
347
|
+
* }
|
|
348
|
+
* ```
|
|
82
349
|
*/
|
|
83
350
|
export type ServerMessage = {
|
|
84
351
|
type: 'subscribed';
|
|
@@ -90,74 +357,492 @@ export type ServerMessage = {
|
|
|
90
357
|
type: 'error';
|
|
91
358
|
message: string;
|
|
92
359
|
channel?: string;
|
|
360
|
+
code?: RippleErrorCode;
|
|
93
361
|
} | {
|
|
94
362
|
type: 'event';
|
|
95
363
|
channel: string;
|
|
96
364
|
event: string;
|
|
97
365
|
data: unknown;
|
|
366
|
+
seq?: number;
|
|
367
|
+
needAck?: boolean;
|
|
98
368
|
} | {
|
|
99
369
|
type: 'presence';
|
|
100
370
|
channel: string;
|
|
101
371
|
event: 'join' | 'leave' | 'members';
|
|
102
372
|
data: unknown;
|
|
373
|
+
seq?: number;
|
|
374
|
+
needAck?: boolean;
|
|
103
375
|
} | {
|
|
104
376
|
type: 'pong';
|
|
105
377
|
} | {
|
|
106
378
|
type: 'connected';
|
|
107
379
|
socketId: string;
|
|
380
|
+
} | {
|
|
381
|
+
type: 'binary';
|
|
382
|
+
channel: string;
|
|
383
|
+
event: string;
|
|
384
|
+
data: ArrayBuffer;
|
|
385
|
+
seq?: number;
|
|
386
|
+
needAck?: boolean;
|
|
387
|
+
} | {
|
|
388
|
+
type: 'ack_received';
|
|
389
|
+
seq: number;
|
|
390
|
+
} | {
|
|
391
|
+
type: 'reconnection_token';
|
|
392
|
+
token: string;
|
|
108
393
|
};
|
|
109
394
|
/**
|
|
110
|
-
*
|
|
395
|
+
* Context for Ripple Message Interceptors (v4.0+).
|
|
396
|
+
*/
|
|
397
|
+
export interface RippleContext {
|
|
398
|
+
/** The WebSocket client connection */
|
|
399
|
+
ws: import('./engines/IRippleEngine').RippleSocket;
|
|
400
|
+
/** The message being processed */
|
|
401
|
+
message: ClientMessage | ServerMessage;
|
|
402
|
+
/** Direction of the message flow */
|
|
403
|
+
direction: 'incoming' | 'outgoing';
|
|
404
|
+
/** Channel name (optional) */
|
|
405
|
+
channel?: string;
|
|
406
|
+
/** Event name (optional) */
|
|
407
|
+
event?: string;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Middleware function for intercepting Ripple messages.
|
|
411
|
+
* Next function must be called to continue the pipeline.
|
|
412
|
+
*/
|
|
413
|
+
export type RippleInterceptor = (ctx: RippleContext, next: () => Promise<void>) => Promise<void> | void;
|
|
414
|
+
/**
|
|
415
|
+
* Driver health status information.
|
|
416
|
+
*
|
|
417
|
+
* Returned by `RippleDriver.getStatus()` to report the current state of the driver.
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```typescript
|
|
421
|
+
* const driver = new RedisDriver(config)
|
|
422
|
+
* const status = driver.getStatus()
|
|
423
|
+
*
|
|
424
|
+
* console.log(status.name) // 'redis'
|
|
425
|
+
* console.log(status.initialized) // true
|
|
426
|
+
* console.log(status.connected) // true
|
|
427
|
+
* console.log(status.lastError) // undefined (or error message if failed)
|
|
428
|
+
* ```
|
|
429
|
+
*/
|
|
430
|
+
export interface DriverStatus {
|
|
431
|
+
/** Driver name (e.g., 'local', 'redis') */
|
|
432
|
+
name: string;
|
|
433
|
+
/** Whether the driver has been initialized */
|
|
434
|
+
initialized: boolean;
|
|
435
|
+
/** Whether the driver is currently connected */
|
|
436
|
+
connected: boolean;
|
|
437
|
+
/** Last error message if connection failed */
|
|
438
|
+
lastError?: string;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Server message type constants.
|
|
442
|
+
*
|
|
443
|
+
* Use these constants instead of string literals for type safety.
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```typescript
|
|
447
|
+
* import { SERVER_MESSAGE_TYPES } from '@gravito/ripple'
|
|
448
|
+
*
|
|
449
|
+
* // Type-safe message creation
|
|
450
|
+
* const message = {
|
|
451
|
+
* type: SERVER_MESSAGE_TYPES.SUBSCRIBED,
|
|
452
|
+
* channel: 'news'
|
|
453
|
+
* }
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
456
|
+
export declare const SERVER_MESSAGE_TYPES: {
|
|
457
|
+
readonly SUBSCRIBED: "subscribed";
|
|
458
|
+
readonly UNSUBSCRIBED: "unsubscribed";
|
|
459
|
+
readonly ERROR: "error";
|
|
460
|
+
readonly EVENT: "event";
|
|
461
|
+
readonly PRESENCE: "presence";
|
|
462
|
+
readonly PONG: "pong";
|
|
463
|
+
readonly CONNECTED: "connected";
|
|
464
|
+
readonly BINARY: "binary";
|
|
465
|
+
readonly RECONNECTION_TOKEN: "reconnection_token";
|
|
466
|
+
};
|
|
467
|
+
/**
|
|
468
|
+
* Client message type constants.
|
|
469
|
+
*
|
|
470
|
+
* Use these constants instead of string literals for type safety.
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* ```typescript
|
|
474
|
+
* import { CLIENT_MESSAGE_TYPES } from '@gravito/ripple'
|
|
475
|
+
*
|
|
476
|
+
* // Type-safe message creation
|
|
477
|
+
* const message = {
|
|
478
|
+
* type: CLIENT_MESSAGE_TYPES.SUBSCRIBE,
|
|
479
|
+
* channel: 'private-orders.123'
|
|
480
|
+
* }
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
483
|
+
export declare const CLIENT_MESSAGE_TYPES: {
|
|
484
|
+
readonly SUBSCRIBE: "subscribe";
|
|
485
|
+
readonly UNSUBSCRIBE: "unsubscribe";
|
|
486
|
+
readonly WHISPER: "whisper";
|
|
487
|
+
readonly PING: "ping";
|
|
488
|
+
readonly BINARY: "binary";
|
|
489
|
+
readonly ACK: "ack";
|
|
490
|
+
};
|
|
491
|
+
/**
|
|
492
|
+
* Interface for implementing custom Ripple drivers.
|
|
493
|
+
*
|
|
494
|
+
* Drivers handle message distribution across server instances. The `local` driver
|
|
495
|
+
* keeps messages in-memory (single server), while the `redis` driver enables
|
|
496
|
+
* horizontal scaling across multiple servers.
|
|
497
|
+
*
|
|
498
|
+
* Implement this interface to create custom drivers (e.g., NATS, RabbitMQ, Kafka).
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* ```typescript
|
|
502
|
+
* // Example: Custom NATS driver implementation
|
|
503
|
+
* import type { RedisDriverConfig } from './drivers/RedisDriver'
|
|
504
|
+
import type { NATSDriverConfig } from './drivers/NATSDriver'
|
|
505
|
+
import type { ComponentHealth } from './health/HealthChecker'
|
|
506
|
+
import { connect, NatsConnection, Subscription } from 'nats'
|
|
507
|
+
*
|
|
508
|
+
* export class NatsDriver implements RippleDriver {
|
|
509
|
+
* readonly name = 'nats'
|
|
510
|
+
* private connection?: NatsConnection
|
|
511
|
+
* private subscriptions = new Map<string, Subscription>()
|
|
512
|
+
* private status: DriverStatus = {
|
|
513
|
+
* name: 'nats',
|
|
514
|
+
* initialized: false,
|
|
515
|
+
* connected: false
|
|
516
|
+
* }
|
|
517
|
+
*
|
|
518
|
+
* async init(): Promise<void> {
|
|
519
|
+
* try {
|
|
520
|
+
* this.connection = await connect({ servers: 'nats://localhost:4222' })
|
|
521
|
+
* this.status.initialized = true
|
|
522
|
+
* this.status.connected = true
|
|
523
|
+
* } catch (error) {
|
|
524
|
+
* this.status.lastError = error.message
|
|
525
|
+
* throw error
|
|
526
|
+
* }
|
|
527
|
+
* }
|
|
528
|
+
*
|
|
529
|
+
* async publish(channel: string, event: string, data: unknown): Promise<void> {
|
|
530
|
+
* const payload = JSON.stringify({ event, data })
|
|
531
|
+
* await this.connection?.publish(`ripple.${channel}`, payload)
|
|
532
|
+
* }
|
|
533
|
+
*
|
|
534
|
+
* async subscribe(channel: string, callback: (event: string, data: unknown) => void): Promise<void> {
|
|
535
|
+
* const sub = this.connection?.subscribe(`ripple.${channel}`)
|
|
536
|
+
* this.subscriptions.set(channel, sub!)
|
|
537
|
+
*
|
|
538
|
+
* for await (const msg of sub!) {
|
|
539
|
+
* const { event, data } = JSON.parse(msg.data.toString())
|
|
540
|
+
* callback(event, data)
|
|
541
|
+
* }
|
|
542
|
+
* }
|
|
543
|
+
*
|
|
544
|
+
* async unsubscribe(channel: string): Promise<void> {
|
|
545
|
+
* const sub = this.subscriptions.get(channel)
|
|
546
|
+
* if (sub) {
|
|
547
|
+
* await sub.unsubscribe()
|
|
548
|
+
* this.subscriptions.delete(channel)
|
|
549
|
+
* }
|
|
550
|
+
* }
|
|
551
|
+
*
|
|
552
|
+
* async shutdown(): Promise<void> {
|
|
553
|
+
* await this.connection?.close()
|
|
554
|
+
* this.status.connected = false
|
|
555
|
+
* }
|
|
556
|
+
*
|
|
557
|
+
* getStatus(): DriverStatus {
|
|
558
|
+
* return this.status
|
|
559
|
+
* }
|
|
560
|
+
* }
|
|
561
|
+
* ```
|
|
111
562
|
*/
|
|
112
563
|
export interface RippleDriver {
|
|
113
|
-
/** Driver name */
|
|
564
|
+
/** Driver name (e.g., 'local', 'redis', 'nats') */
|
|
114
565
|
readonly name: string;
|
|
115
566
|
/**
|
|
116
|
-
* Publish a message to a channel
|
|
567
|
+
* Publish a message to a channel.
|
|
568
|
+
*
|
|
569
|
+
* @param channel - Channel name to publish to
|
|
570
|
+
* @param event - Event name
|
|
571
|
+
* @param data - Event payload
|
|
117
572
|
*/
|
|
118
573
|
publish(channel: string, event: string, data: unknown): Promise<void>;
|
|
119
574
|
/**
|
|
120
|
-
* Subscribe to channel messages (
|
|
575
|
+
* Subscribe to a channel for incoming messages (optional).
|
|
576
|
+
*
|
|
577
|
+
* For multi-server setups, implement this to receive messages from other servers.
|
|
578
|
+
* The local driver doesn't need this since messages are in-memory.
|
|
579
|
+
*
|
|
580
|
+
* @param channel - Channel name to subscribe to
|
|
581
|
+
* @param callback - Called when a message is received
|
|
121
582
|
*/
|
|
122
583
|
subscribe?(channel: string, callback: (event: string, data: unknown) => void): Promise<void>;
|
|
123
584
|
/**
|
|
124
|
-
* Unsubscribe from a channel
|
|
585
|
+
* Unsubscribe from a channel (optional).
|
|
586
|
+
*
|
|
587
|
+
* @param channel - Channel name to unsubscribe from
|
|
125
588
|
*/
|
|
126
589
|
unsubscribe?(channel: string): Promise<void>;
|
|
127
590
|
/**
|
|
128
|
-
* Initialize the driver
|
|
591
|
+
* Initialize the driver (optional).
|
|
592
|
+
*
|
|
593
|
+
* Called when RippleServer starts. Use this to establish connections,
|
|
594
|
+
* initialize resources, etc.
|
|
129
595
|
*/
|
|
130
596
|
init?(): Promise<void>;
|
|
131
597
|
/**
|
|
132
|
-
* Shutdown the driver
|
|
598
|
+
* Shutdown the driver (optional).
|
|
599
|
+
*
|
|
600
|
+
* Called when RippleServer shuts down. Clean up connections and resources here.
|
|
133
601
|
*/
|
|
134
602
|
shutdown?(): Promise<void>;
|
|
603
|
+
/**
|
|
604
|
+
* Get current driver status (optional).
|
|
605
|
+
*
|
|
606
|
+
* @returns Current driver status
|
|
607
|
+
*/
|
|
608
|
+
getStatus?(): DriverStatus;
|
|
609
|
+
/**
|
|
610
|
+
* Track a presence member in a channel (optional).
|
|
611
|
+
*
|
|
612
|
+
* For distributed drivers like Redis, this stores presence information
|
|
613
|
+
* in a shared backend so all server instances can see the same members.
|
|
614
|
+
*
|
|
615
|
+
* @param channel - Presence channel name
|
|
616
|
+
* @param userInfo - User information to track
|
|
617
|
+
* @since 3.6.0
|
|
618
|
+
*/
|
|
619
|
+
trackPresence?(channel: string, userInfo: PresenceUserInfo): Promise<void>;
|
|
620
|
+
/**
|
|
621
|
+
* Remove a presence member from a channel (optional).
|
|
622
|
+
*
|
|
623
|
+
* @param channel - Presence channel name
|
|
624
|
+
* @param userId - User ID to remove
|
|
625
|
+
* @since 3.6.0
|
|
626
|
+
*/
|
|
627
|
+
untrackPresence?(channel: string, userId: string | number): Promise<void>;
|
|
628
|
+
/**
|
|
629
|
+
* Get all presence members for a channel (optional).
|
|
630
|
+
*
|
|
631
|
+
* For distributed drivers, this retrieves members from the shared backend.
|
|
632
|
+
*
|
|
633
|
+
* @param channel - Presence channel name
|
|
634
|
+
* @returns Array of presence user information
|
|
635
|
+
* @since 3.6.0
|
|
636
|
+
*/
|
|
637
|
+
getPresenceMembers?(channel: string): Promise<PresenceUserInfo[]>;
|
|
135
638
|
}
|
|
136
639
|
/**
|
|
137
|
-
* Ripple server configuration
|
|
640
|
+
* Ripple server configuration.
|
|
641
|
+
*
|
|
642
|
+
* Configures WebSocket behavior, authentication, drivers, and observability.
|
|
643
|
+
*
|
|
644
|
+
* @example
|
|
645
|
+
* ```typescript
|
|
646
|
+
* // Example 1: Basic setup with local driver (single server)
|
|
647
|
+
* const config: RippleConfig = {
|
|
648
|
+
* path: '/ws',
|
|
649
|
+
* authorizer: async (channel, userId, socketId) => {
|
|
650
|
+
* if (channel.startsWith('private-')) {
|
|
651
|
+
* return userId !== undefined
|
|
652
|
+
* }
|
|
653
|
+
* return true
|
|
654
|
+
* }
|
|
655
|
+
* }
|
|
656
|
+
*
|
|
657
|
+
* // Example 2: Production setup with Redis driver (multi-server)
|
|
658
|
+
* const config: RippleConfig = {
|
|
659
|
+
* path: '/realtime',
|
|
660
|
+
* driver: 'redis',
|
|
661
|
+
* redis: {
|
|
662
|
+
* host: process.env.REDIS_HOST || 'localhost',
|
|
663
|
+
* port: 6379,
|
|
664
|
+
* password: process.env.REDIS_PASSWORD,
|
|
665
|
+
* db: 0
|
|
666
|
+
* },
|
|
667
|
+
* authorizer: async (channel, userId, socketId) => {
|
|
668
|
+
* if (channel.startsWith('presence-')) {
|
|
669
|
+
* if (!userId) return false
|
|
670
|
+
* const user = await db.users.findById(userId)
|
|
671
|
+
* return {
|
|
672
|
+
* id: user.id,
|
|
673
|
+
* info: { name: user.name, avatar: user.avatarUrl }
|
|
674
|
+
* }
|
|
675
|
+
* }
|
|
676
|
+
* return userId !== undefined
|
|
677
|
+
* },
|
|
678
|
+
* pingInterval: 30000,
|
|
679
|
+
* logLevel: 'info'
|
|
680
|
+
* }
|
|
681
|
+
*
|
|
682
|
+
* // Example 3: Custom logger and health check
|
|
683
|
+
* import { RippleLogger } from '@gravito/ripple'
|
|
684
|
+
*
|
|
685
|
+
* const customLogger: RippleLogger = {
|
|
686
|
+
* debug: (message, context) => console.debug(message, context),
|
|
687
|
+
* info: (message, context) => console.info(message, context),
|
|
688
|
+
* warn: (message, context) => console.warn(message, context),
|
|
689
|
+
* error: (message, context) => console.error(message, context)
|
|
690
|
+
* }
|
|
691
|
+
*
|
|
692
|
+
* const config: RippleConfig = {
|
|
693
|
+
* logger: customLogger,
|
|
694
|
+
* logLevel: 'debug',
|
|
695
|
+
* healthCheck: {
|
|
696
|
+
* enabled: true,
|
|
697
|
+
* path: '/health'
|
|
698
|
+
* }
|
|
699
|
+
* }
|
|
700
|
+
*
|
|
701
|
+
* // Example 4: Connection tracking
|
|
702
|
+
* import { ConnectionTracker } from '@gravito/ripple'
|
|
703
|
+
*
|
|
704
|
+
* const tracker = new ConnectionTracker()
|
|
705
|
+
* const config: RippleConfig = {
|
|
706
|
+
* connectionTracker: tracker
|
|
707
|
+
* }
|
|
708
|
+
*
|
|
709
|
+
* // Later: query connection stats
|
|
710
|
+
* const stats = tracker.getStats()
|
|
711
|
+
* console.log('Total connections:', stats.totalConnections)
|
|
712
|
+
* console.log('Active connections:', stats.activeConnections)
|
|
713
|
+
* ```
|
|
138
714
|
*/
|
|
139
715
|
export interface RippleConfig {
|
|
140
716
|
/** WebSocket endpoint path (default: '/ws') */
|
|
141
717
|
path?: string;
|
|
142
718
|
/** Authentication endpoint for private/presence channels */
|
|
143
719
|
authEndpoint?: string;
|
|
144
|
-
/** Driver to use ('local' | 'redis') */
|
|
145
|
-
driver?: 'local' | 'redis';
|
|
146
|
-
/**
|
|
147
|
-
redis?:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
password?: string;
|
|
151
|
-
db?: number;
|
|
152
|
-
};
|
|
720
|
+
/** Driver to use for scaling ('local' | 'redis' | 'nats') */
|
|
721
|
+
driver?: 'local' | 'redis' | 'nats';
|
|
722
|
+
/** Configuration for Redis driver */
|
|
723
|
+
redis?: RedisDriverConfig;
|
|
724
|
+
/** Configuration for NATS driver (v4.0+) */
|
|
725
|
+
nats?: NATSDriverConfig;
|
|
153
726
|
/** Channel authorizer function */
|
|
154
727
|
authorizer?: ChannelAuthorizer;
|
|
155
728
|
/** Ping interval in milliseconds (default: 30000) */
|
|
156
729
|
pingInterval?: number;
|
|
730
|
+
/** Custom logger */
|
|
731
|
+
logger?: import('./logging/Logger').RippleLogger;
|
|
732
|
+
/** Log level */
|
|
733
|
+
logLevel?: import('./logging/Logger').LogLevel;
|
|
734
|
+
/** Connection tracker */
|
|
735
|
+
connectionTracker?: import('./tracking/ConnectionTracker').ConnectionTracker;
|
|
736
|
+
/** Health check configuration */
|
|
737
|
+
healthCheck?: {
|
|
738
|
+
enabled: boolean;
|
|
739
|
+
path?: string;
|
|
740
|
+
};
|
|
741
|
+
/**
|
|
742
|
+
* Rate limiting configuration.
|
|
743
|
+
*/
|
|
744
|
+
rateLimit?: {
|
|
745
|
+
/** Max whispers per interval */
|
|
746
|
+
whisperMax?: number;
|
|
747
|
+
/** Interval in milliseconds for whisper limit (default: 1000) */
|
|
748
|
+
whisperInterval?: number;
|
|
749
|
+
};
|
|
750
|
+
/**
|
|
751
|
+
* Reconnection configuration (v3.6+).
|
|
752
|
+
*/
|
|
753
|
+
reconnection?: {
|
|
754
|
+
/** Enable server-assisted reconnection (default: false) */
|
|
755
|
+
enabled?: boolean;
|
|
756
|
+
/** Session TTL in milliseconds (default: 60000 = 1 minute) */
|
|
757
|
+
sessionTTL?: number;
|
|
758
|
+
/** Maximum number of stored sessions (default: 10000) */
|
|
759
|
+
maxSessions?: number;
|
|
760
|
+
};
|
|
761
|
+
/**
|
|
762
|
+
* Serializer to use for messages (default: 'json').
|
|
763
|
+
*
|
|
764
|
+
* 'json': Recommended for most use cases. Zero configuration,
|
|
765
|
+
* browser-native, easy debugging. No additional dependencies.
|
|
766
|
+
*
|
|
767
|
+
* 'protobuf': For specialized use cases (IoT, bandwidth-constrained networks).
|
|
768
|
+
* Requires 'protobufjs' peer dependency to be installed.
|
|
769
|
+
* Install with: npm install protobufjs
|
|
770
|
+
*/
|
|
771
|
+
serializer?: 'json' | 'protobuf';
|
|
772
|
+
/**
|
|
773
|
+
* Performance & Backpressure (v3.7+).
|
|
774
|
+
*/
|
|
775
|
+
backpressure?: {
|
|
776
|
+
/** High Water Mark in bytes. Force disconnect if reached. (default: 5,242,880 = 5MB) */
|
|
777
|
+
hwmHigh?: number;
|
|
778
|
+
/** Low Water Mark in bytes. Skip non-critical events if reached. (default: 1,048,576 = 1MB) */
|
|
779
|
+
hwmLow?: number;
|
|
780
|
+
/** Whether to enable automatic slow client isolation (default: false) */
|
|
781
|
+
enabled?: boolean;
|
|
782
|
+
};
|
|
783
|
+
/**
|
|
784
|
+
* Prometheus Metrics (v3.7+).
|
|
785
|
+
*/
|
|
786
|
+
metrics?: {
|
|
787
|
+
/** Enable Prometheus metrics (default: false) */
|
|
788
|
+
enabled?: boolean;
|
|
789
|
+
/** Metric name prefix (default: 'ripple') */
|
|
790
|
+
prefix?: string;
|
|
791
|
+
};
|
|
792
|
+
/**
|
|
793
|
+
* Message Interceptors (v4.0+).
|
|
794
|
+
*/
|
|
795
|
+
interceptors?: RippleInterceptor[];
|
|
796
|
+
/**
|
|
797
|
+
* WebSocket runtime/engine to use (v5.0+).
|
|
798
|
+
*
|
|
799
|
+
* - 'bun': Bun native WebSocket (default, highest performance)
|
|
800
|
+
* - 'node-uws': uWebSockets.js for Node.js (high performance, requires peer dep)
|
|
801
|
+
* - 'node-ws': ws package for Node.js (standard, best compatibility)
|
|
802
|
+
*
|
|
803
|
+
* If not specified, Ripple will auto-detect the runtime.
|
|
804
|
+
*
|
|
805
|
+
* @since 5.0.0
|
|
806
|
+
*/
|
|
807
|
+
runtime?: 'bun' | 'node-uws' | 'node-ws';
|
|
808
|
+
/**
|
|
809
|
+
* Server port to listen on (v5.0+).
|
|
810
|
+
*
|
|
811
|
+
* Required when using the engine-based architecture.
|
|
812
|
+
*
|
|
813
|
+
* @since 5.0.0
|
|
814
|
+
*/
|
|
815
|
+
port?: number;
|
|
816
|
+
/**
|
|
817
|
+
* Hostname to bind to (v5.0+).
|
|
818
|
+
*
|
|
819
|
+
* @default '0.0.0.0'
|
|
820
|
+
* @since 5.0.0
|
|
821
|
+
*/
|
|
822
|
+
hostname?: string;
|
|
157
823
|
}
|
|
158
824
|
/**
|
|
159
825
|
* Strongly-typed Bun ServerWebSocket for Ripple.
|
|
160
826
|
*
|
|
827
|
+
* This type adds `ClientData` to Bun's native `ServerWebSocket`, providing
|
|
828
|
+
* type-safe access to client ID, user ID, and channel subscriptions.
|
|
829
|
+
*
|
|
830
|
+
* @example
|
|
831
|
+
* ```typescript
|
|
832
|
+
* import { RippleWebSocket } from '@gravito/ripple'
|
|
833
|
+
*
|
|
834
|
+
* const handleMessage = (ws: RippleWebSocket, message: string) => {
|
|
835
|
+
* // Type-safe access to client data
|
|
836
|
+
* console.log('Client ID:', ws.data.id)
|
|
837
|
+
* console.log('User ID:', ws.data.userId)
|
|
838
|
+
* console.log('Channels:', Array.from(ws.data.channels))
|
|
839
|
+
*
|
|
840
|
+
* // Standard Bun WebSocket methods
|
|
841
|
+
* ws.send('Message received')
|
|
842
|
+
* ws.close()
|
|
843
|
+
* }
|
|
844
|
+
* ```
|
|
845
|
+
*
|
|
161
846
|
* @public
|
|
162
847
|
* @since 3.0.0
|
|
163
848
|
*/
|
|
@@ -165,12 +850,65 @@ export type RippleWebSocket = ServerWebSocket<ClientData>;
|
|
|
165
850
|
/**
|
|
166
851
|
* Strongly-typed Bun Server for Ripple.
|
|
167
852
|
*
|
|
853
|
+
* This type ensures the Bun server is configured with `ClientData` for WebSocket handling.
|
|
854
|
+
*
|
|
855
|
+
* @example
|
|
856
|
+
* ```typescript
|
|
857
|
+
* import { RippleBunServer } from '@gravito/ripple'
|
|
858
|
+
*
|
|
859
|
+
* let server: RippleBunServer
|
|
860
|
+
*
|
|
861
|
+
* server = Bun.serve({
|
|
862
|
+
* port: 3000,
|
|
863
|
+
* fetch: (req, server) => {
|
|
864
|
+
* if (ripple.upgrade(req, server)) return
|
|
865
|
+
* return new Response('Not found', { status: 404 })
|
|
866
|
+
* },
|
|
867
|
+
* websocket: ripple.getHandler()
|
|
868
|
+
* })
|
|
869
|
+
*
|
|
870
|
+
* // Later: graceful shutdown
|
|
871
|
+
* server.stop()
|
|
872
|
+
* ```
|
|
873
|
+
*
|
|
168
874
|
* @public
|
|
169
875
|
* @since 3.0.0
|
|
170
876
|
*/
|
|
171
877
|
export type RippleBunServer = Server<ClientData>;
|
|
172
878
|
/**
|
|
173
|
-
* WebSocket handler configuration for Bun.serve
|
|
879
|
+
* WebSocket handler configuration for Bun.serve.
|
|
880
|
+
*
|
|
881
|
+
* Defines the WebSocket lifecycle event handlers that Bun expects.
|
|
882
|
+
* RippleServer implements this interface internally.
|
|
883
|
+
*
|
|
884
|
+
* @example
|
|
885
|
+
* ```typescript
|
|
886
|
+
* import { WebSocketHandlerConfig, RippleWebSocket } from '@gravito/ripple'
|
|
887
|
+
*
|
|
888
|
+
* // Custom WebSocket handler (usually handled by RippleServer)
|
|
889
|
+
* const handler: WebSocketHandlerConfig = {
|
|
890
|
+
* open: (ws: RippleWebSocket) => {
|
|
891
|
+
* console.log('Client connected:', ws.data.id)
|
|
892
|
+
* },
|
|
893
|
+
*
|
|
894
|
+
* message: (ws: RippleWebSocket, message: string | Buffer) => {
|
|
895
|
+
* console.log('Received:', message)
|
|
896
|
+
* },
|
|
897
|
+
*
|
|
898
|
+
* close: (ws: RippleWebSocket, code: number, reason: string) => {
|
|
899
|
+
* console.log('Client disconnected:', code, reason)
|
|
900
|
+
* },
|
|
901
|
+
*
|
|
902
|
+
* drain: (ws: RippleWebSocket) => {
|
|
903
|
+
* console.log('Backpressure drained')
|
|
904
|
+
* }
|
|
905
|
+
* }
|
|
906
|
+
*
|
|
907
|
+
* // Pass to Bun.serve
|
|
908
|
+
* Bun.serve({
|
|
909
|
+
* websocket: handler
|
|
910
|
+
* })
|
|
911
|
+
* ```
|
|
174
912
|
*/
|
|
175
913
|
export interface WebSocketHandlerConfig {
|
|
176
914
|
open: (ws: RippleWebSocket) => void | Promise<void>;
|