@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.
Files changed (168) hide show
  1. package/README.md +432 -18
  2. package/README.zh-TW.md +104 -2
  3. package/dist/atlas/src/DB.d.ts +301 -0
  4. package/dist/atlas/src/OrbitAtlas.d.ts +9 -0
  5. package/dist/atlas/src/config/defineConfig.d.ts +14 -0
  6. package/dist/atlas/src/config/index.d.ts +7 -0
  7. package/dist/atlas/src/config/loadConfig.d.ts +48 -0
  8. package/dist/atlas/src/connection/Connection.d.ts +108 -0
  9. package/dist/atlas/src/connection/ConnectionManager.d.ts +111 -0
  10. package/dist/atlas/src/drivers/BunSQLDriver.d.ts +32 -0
  11. package/dist/atlas/src/drivers/BunSQLPreparedStatement.d.ts +118 -0
  12. package/dist/atlas/src/drivers/MongoDBDriver.d.ts +36 -0
  13. package/dist/atlas/src/drivers/MySQLDriver.d.ts +66 -0
  14. package/dist/atlas/src/drivers/PostgresDriver.d.ts +83 -0
  15. package/dist/atlas/src/drivers/RedisDriver.d.ts +43 -0
  16. package/dist/atlas/src/drivers/SQLiteDriver.d.ts +45 -0
  17. package/dist/atlas/src/drivers/types.d.ts +260 -0
  18. package/dist/atlas/src/errors/index.d.ts +45 -0
  19. package/dist/atlas/src/grammar/Grammar.d.ts +342 -0
  20. package/dist/atlas/src/grammar/MongoGrammar.d.ts +47 -0
  21. package/dist/atlas/src/grammar/MySQLGrammar.d.ts +54 -0
  22. package/dist/atlas/src/grammar/NullGrammar.d.ts +35 -0
  23. package/dist/atlas/src/grammar/PostgresGrammar.d.ts +62 -0
  24. package/dist/atlas/src/grammar/SQLiteGrammar.d.ts +32 -0
  25. package/dist/atlas/src/index.d.ts +67 -0
  26. package/dist/atlas/src/migration/Migration.d.ts +64 -0
  27. package/dist/atlas/src/migration/MigrationRepository.d.ts +65 -0
  28. package/dist/atlas/src/migration/Migrator.d.ts +110 -0
  29. package/dist/atlas/src/migration/index.d.ts +6 -0
  30. package/dist/atlas/src/observability/AtlasMetrics.d.ts +11 -0
  31. package/dist/atlas/src/observability/AtlasObservability.d.ts +15 -0
  32. package/dist/atlas/src/observability/AtlasTracer.d.ts +12 -0
  33. package/dist/atlas/src/observability/index.d.ts +9 -0
  34. package/dist/atlas/src/orm/index.d.ts +5 -0
  35. package/dist/atlas/src/orm/model/DirtyTracker.d.ts +121 -0
  36. package/dist/atlas/src/orm/model/Model.d.ts +449 -0
  37. package/dist/atlas/src/orm/model/ModelRegistry.d.ts +20 -0
  38. package/dist/atlas/src/orm/model/concerns/HasAttributes.d.ts +136 -0
  39. package/dist/atlas/src/orm/model/concerns/HasEvents.d.ts +36 -0
  40. package/dist/atlas/src/orm/model/concerns/HasPersistence.d.ts +87 -0
  41. package/dist/atlas/src/orm/model/concerns/HasRelationships.d.ts +117 -0
  42. package/dist/atlas/src/orm/model/concerns/HasSerialization.d.ts +64 -0
  43. package/dist/atlas/src/orm/model/concerns/applyMixins.d.ts +15 -0
  44. package/dist/atlas/src/orm/model/concerns/index.d.ts +12 -0
  45. package/dist/atlas/src/orm/model/decorators.d.ts +109 -0
  46. package/dist/atlas/src/orm/model/errors.d.ts +52 -0
  47. package/dist/atlas/src/orm/model/index.d.ts +10 -0
  48. package/dist/atlas/src/orm/model/relationships.d.ts +207 -0
  49. package/dist/atlas/src/orm/model/types.d.ts +12 -0
  50. package/dist/atlas/src/orm/schema/SchemaRegistry.d.ts +123 -0
  51. package/dist/atlas/src/orm/schema/SchemaSniffer.d.ts +54 -0
  52. package/dist/atlas/src/orm/schema/index.d.ts +6 -0
  53. package/dist/atlas/src/orm/schema/types.d.ts +85 -0
  54. package/dist/atlas/src/query/Expression.d.ts +60 -0
  55. package/dist/atlas/src/query/NPlusOneDetector.d.ts +10 -0
  56. package/dist/atlas/src/query/QueryBuilder.d.ts +573 -0
  57. package/dist/atlas/src/query/clauses/GroupByClause.d.ts +51 -0
  58. package/dist/atlas/src/query/clauses/HavingClause.d.ts +70 -0
  59. package/dist/atlas/src/query/clauses/JoinClause.d.ts +87 -0
  60. package/dist/atlas/src/query/clauses/LimitClause.d.ts +82 -0
  61. package/dist/atlas/src/query/clauses/OrderByClause.d.ts +69 -0
  62. package/dist/atlas/src/query/clauses/SelectClause.d.ts +71 -0
  63. package/dist/atlas/src/query/clauses/WhereClause.d.ts +167 -0
  64. package/dist/atlas/src/query/clauses/index.d.ts +11 -0
  65. package/dist/atlas/src/schema/Blueprint.d.ts +276 -0
  66. package/dist/atlas/src/schema/ColumnDefinition.d.ts +154 -0
  67. package/dist/atlas/src/schema/ForeignKeyDefinition.d.ts +37 -0
  68. package/dist/atlas/src/schema/Schema.d.ts +131 -0
  69. package/dist/atlas/src/schema/grammars/MySQLSchemaGrammar.d.ts +23 -0
  70. package/dist/atlas/src/schema/grammars/PostgresSchemaGrammar.d.ts +26 -0
  71. package/dist/atlas/src/schema/grammars/SQLiteSchemaGrammar.d.ts +28 -0
  72. package/dist/atlas/src/schema/grammars/SchemaGrammar.d.ts +97 -0
  73. package/dist/atlas/src/schema/grammars/index.d.ts +7 -0
  74. package/dist/atlas/src/schema/index.d.ts +8 -0
  75. package/dist/atlas/src/seed/Factory.d.ts +90 -0
  76. package/dist/atlas/src/seed/Seeder.d.ts +28 -0
  77. package/dist/atlas/src/seed/SeederRunner.d.ts +74 -0
  78. package/dist/atlas/src/seed/index.d.ts +6 -0
  79. package/dist/atlas/src/types/index.d.ts +1100 -0
  80. package/dist/atlas/src/utils/levenshtein.d.ts +9 -0
  81. package/dist/core/src/Application.d.ts +43 -17
  82. package/dist/core/src/CommandKernel.d.ts +33 -0
  83. package/dist/core/src/Container.d.ts +78 -14
  84. package/dist/core/src/HookManager.d.ts +422 -8
  85. package/dist/core/src/PlanetCore.d.ts +52 -7
  86. package/dist/core/src/Router.d.ts +41 -7
  87. package/dist/core/src/ServiceProvider.d.ts +14 -8
  88. package/dist/core/src/adapters/GravitoEngineAdapter.d.ts +1 -0
  89. package/dist/core/src/adapters/PhotonAdapter.d.ts +1 -0
  90. package/dist/core/src/adapters/bun/BunNativeAdapter.d.ts +1 -0
  91. package/dist/core/src/adapters/types.d.ts +39 -0
  92. package/dist/core/src/engine/AOTRouter.d.ts +1 -11
  93. package/dist/core/src/engine/FastContext.d.ts +4 -2
  94. package/dist/core/src/engine/Gravito.d.ts +1 -1
  95. package/dist/core/src/engine/MinimalContext.d.ts +4 -2
  96. package/dist/core/src/engine/types.d.ts +6 -1
  97. package/dist/core/src/events/CircuitBreaker.d.ts +229 -0
  98. package/dist/core/src/events/DeadLetterQueue.d.ts +145 -0
  99. package/dist/core/src/events/EventBackend.d.ts +11 -0
  100. package/dist/core/src/events/EventOptions.d.ts +109 -0
  101. package/dist/core/src/events/EventPriorityQueue.d.ts +202 -0
  102. package/dist/core/src/events/IdempotencyCache.d.ts +60 -0
  103. package/dist/core/src/events/index.d.ts +14 -0
  104. package/dist/core/src/events/observability/EventMetrics.d.ts +132 -0
  105. package/dist/core/src/events/observability/EventTracer.d.ts +68 -0
  106. package/dist/core/src/events/observability/EventTracing.d.ts +161 -0
  107. package/dist/core/src/events/observability/OTelEventMetrics.d.ts +240 -0
  108. package/dist/core/src/events/observability/ObservableHookManager.d.ts +108 -0
  109. package/dist/core/src/events/observability/index.d.ts +20 -0
  110. package/dist/core/src/events/observability/metrics-types.d.ts +16 -0
  111. package/dist/core/src/events/types.d.ts +75 -0
  112. package/dist/core/src/exceptions/CircularDependencyException.d.ts +9 -0
  113. package/dist/core/src/exceptions/index.d.ts +1 -0
  114. package/dist/core/src/http/cookie.d.ts +29 -0
  115. package/dist/core/src/http/types.d.ts +21 -0
  116. package/dist/core/src/index.d.ts +13 -3
  117. package/dist/core/src/instrumentation/index.d.ts +35 -0
  118. package/dist/core/src/instrumentation/opentelemetry.d.ts +178 -0
  119. package/dist/core/src/instrumentation/types.d.ts +182 -0
  120. package/dist/core/src/reliability/DeadLetterQueueManager.d.ts +316 -0
  121. package/dist/core/src/reliability/RetryPolicy.d.ts +217 -0
  122. package/dist/core/src/reliability/index.d.ts +6 -0
  123. package/dist/core/src/router/ControllerDispatcher.d.ts +12 -0
  124. package/dist/core/src/router/RequestValidator.d.ts +20 -0
  125. package/dist/index.js +6487 -9562
  126. package/dist/index.js.map +68 -62
  127. package/dist/photon/src/index.d.ts +69 -5
  128. package/dist/photon/src/middleware/binary.d.ts +12 -15
  129. package/dist/photon/src/middleware/htmx.d.ts +39 -0
  130. package/dist/photon/src/middleware/ratelimit.d.ts +157 -0
  131. package/dist/photon/src/openapi.d.ts +19 -0
  132. package/dist/proto/ripple.proto +120 -0
  133. package/dist/ripple/src/OrbitRipple.d.ts +34 -12
  134. package/dist/ripple/src/RippleServer.d.ts +76 -63
  135. package/dist/ripple/src/channels/ChannelManager.d.ts +132 -22
  136. package/dist/ripple/src/drivers/LocalDriver.d.ts +43 -11
  137. package/dist/ripple/src/drivers/NATSDriver.d.ts +87 -0
  138. package/dist/ripple/src/drivers/RedisDriver.d.ts +135 -28
  139. package/dist/ripple/src/drivers/index.d.ts +1 -0
  140. package/dist/ripple/src/engines/BunEngine.d.ts +98 -0
  141. package/dist/ripple/src/engines/IRippleEngine.d.ts +205 -0
  142. package/dist/ripple/src/engines/index.d.ts +11 -0
  143. package/dist/ripple/src/errors/RippleError.d.ts +48 -0
  144. package/dist/ripple/src/errors/index.d.ts +1 -0
  145. package/dist/ripple/src/events/BroadcastEvent.d.ts +78 -6
  146. package/dist/ripple/src/events/BroadcastManager.d.ts +100 -0
  147. package/dist/ripple/src/events/Broadcaster.d.ts +211 -14
  148. package/dist/ripple/src/events/index.d.ts +1 -0
  149. package/dist/ripple/src/health/HealthChecker.d.ts +93 -0
  150. package/dist/ripple/src/health/index.d.ts +1 -0
  151. package/dist/ripple/src/index.d.ts +42 -17
  152. package/dist/ripple/src/logging/Logger.d.ts +99 -0
  153. package/dist/ripple/src/logging/index.d.ts +1 -0
  154. package/dist/ripple/src/middleware/InterceptorManager.d.ts +21 -0
  155. package/dist/ripple/src/observability/RippleMetrics.d.ts +24 -0
  156. package/dist/ripple/src/reliability/AckManager.d.ts +48 -0
  157. package/dist/ripple/src/serializers/ISerializer.d.ts +39 -0
  158. package/dist/ripple/src/serializers/JsonSerializer.d.ts +19 -0
  159. package/dist/ripple/src/serializers/ProtobufSerializer.d.ts +38 -0
  160. package/dist/ripple/src/serializers/index.d.ts +3 -0
  161. package/dist/ripple/src/tracking/ConnectionTracker.d.ts +116 -0
  162. package/dist/ripple/src/tracking/SessionManager.d.ts +104 -0
  163. package/dist/ripple/src/tracking/index.d.ts +2 -0
  164. package/dist/ripple/src/types.d.ts +766 -28
  165. package/dist/ripple/src/utils/MessageSerializer.d.ts +54 -0
  166. package/dist/ripple/src/utils/TokenBucket.d.ts +25 -0
  167. package/dist/ripple/src/utils/index.d.ts +1 -0
  168. 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
- * Channel authorization callback
39
- */
40
- export type ChannelAuthorizer = (channelName: string, userId: string | number | undefined, socketId: string) => boolean | Promise<boolean> | PresenceUserInfo | Promise<PresenceUserInfo | false>;
41
- /**
42
- * User info returned for presence channels
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
- * Broadcast event interface
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
- * Client-to-server message types
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
- * Server-to-client message types
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
- * Driver interface for pub/sub backends
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 (for Redis driver)
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
- /** Redis configuration (if using redis driver) */
147
- redis?: {
148
- host?: string;
149
- port?: number;
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>;