@stacksjs/bun-router 0.0.16 → 0.0.18

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 (176) hide show
  1. package/dist/auth.d.ts +22 -8
  2. package/dist/chunk-1ahs68ys.js +18 -0
  3. package/dist/{chunk-0145g1me.js → chunk-cgptvjdf.js} +147 -75
  4. package/dist/chunk-g3ybefhg.js +1923 -0
  5. package/dist/cli.js +3 -2
  6. package/dist/container/container.d.ts +1 -0
  7. package/dist/container/contextual-binding.d.ts +0 -1
  8. package/dist/container/decorators.d.ts +2 -5
  9. package/dist/container/index.d.ts +16 -0
  10. package/dist/container/index.js +322 -0
  11. package/dist/container/service-provider.d.ts +3 -3
  12. package/dist/development/performance-profiler.d.ts +3 -3
  13. package/dist/development/route-debugger.d.ts +1 -1
  14. package/dist/development/route-inspector.d.ts +2 -2
  15. package/dist/file-serving/static-files.d.ts +16 -0
  16. package/dist/index.d.ts +17 -1
  17. package/dist/index.js +1159 -1380
  18. package/dist/middleware/cors.d.ts +9 -0
  19. package/dist/middleware/csrf.d.ts +7 -0
  20. package/dist/middleware/ddos_protection.d.ts +16 -1
  21. package/dist/middleware/rate_limit.d.ts +7 -1
  22. package/dist/middleware/request_tracer.d.ts +15 -9
  23. package/dist/middleware/response_cache.d.ts +2 -1
  24. package/dist/observability/integration.d.ts +1 -1
  25. package/dist/request/context.d.ts +12 -1
  26. package/dist/request/macros.d.ts +40 -2
  27. package/dist/response/macros.d.ts +4 -4
  28. package/dist/router/fluent-routing.d.ts +50 -13
  29. package/dist/router/handler-resolver.d.ts +8 -0
  30. package/dist/router/index.d.ts +9 -7
  31. package/dist/router/route-compiler.d.ts +8 -10
  32. package/dist/router/route-trie.d.ts +7 -1
  33. package/dist/router/router.d.ts +13 -34
  34. package/dist/router/validation-integration.d.ts +10 -4
  35. package/dist/testing/performance-testing.d.ts +2 -2
  36. package/dist/testing/test-client.d.ts +1 -1
  37. package/dist/testing/websocket-testing.d.ts +1 -1
  38. package/dist/types/middleware-types.d.ts +1 -1
  39. package/dist/types/route-inference.d.ts +9 -2
  40. package/dist/types.d.ts +62 -9
  41. package/dist/utils.d.ts +1 -1
  42. package/package.json +12 -3
  43. package/dist/router/fluent-router.d.ts +0 -315
  44. package/src/auth.ts +0 -469
  45. package/src/cache/lru-cache.ts +0 -457
  46. package/src/cache/middleware-memoization.ts +0 -531
  47. package/src/cache/route-cache-warmer.ts +0 -486
  48. package/src/cache/sqlite-cache.ts +0 -783
  49. package/src/cache/streaming-cache.ts +0 -572
  50. package/src/cli/colors.ts +0 -29
  51. package/src/cli/index.ts +0 -287
  52. package/src/cli/middleware.ts +0 -291
  53. package/src/cli/openapi.ts +0 -407
  54. package/src/cli/router.ts +0 -188
  55. package/src/cli/routes.ts +0 -265
  56. package/src/cli/utils.ts +0 -531
  57. package/src/cli.ts +0 -5
  58. package/src/config.ts +0 -322
  59. package/src/container/container.ts +0 -740
  60. package/src/container/contextual-binding.ts +0 -603
  61. package/src/container/decorators.ts +0 -359
  62. package/src/container/service-provider.ts +0 -596
  63. package/src/development/hot-reload.ts +0 -673
  64. package/src/development/index.ts +0 -499
  65. package/src/development/performance-profiler.ts +0 -717
  66. package/src/development/route-debugger.ts +0 -527
  67. package/src/development/route-inspector.ts +0 -749
  68. package/src/development/typescript-utilities.ts +0 -682
  69. package/src/docs.ts +0 -397
  70. package/src/errors/circuit-breaker.ts +0 -732
  71. package/src/errors/error-handler.ts +0 -569
  72. package/src/errors/error-reporting.ts +0 -672
  73. package/src/errors/exceptions.ts +0 -536
  74. package/src/errors/graceful-degradation.ts +0 -621
  75. package/src/errors/index.ts +0 -21
  76. package/src/errors/router-errors.ts +0 -632
  77. package/src/file-serving/static-files.ts +0 -581
  78. package/src/index.ts +0 -14
  79. package/src/middleware/auth.ts +0 -220
  80. package/src/middleware/content_security_policy.ts +0 -215
  81. package/src/middleware/cors.ts +0 -74
  82. package/src/middleware/csrf.ts +0 -118
  83. package/src/middleware/ddos_protection.ts +0 -255
  84. package/src/middleware/file_security.ts +0 -191
  85. package/src/middleware/file_upload.ts +0 -275
  86. package/src/middleware/helmet.ts +0 -268
  87. package/src/middleware/index.ts +0 -117
  88. package/src/middleware/input_validation.ts +0 -449
  89. package/src/middleware/json_body.ts +0 -37
  90. package/src/middleware/performance_alerting.ts +0 -538
  91. package/src/middleware/performance_dashboard.ts +0 -661
  92. package/src/middleware/performance_monitor.ts +0 -943
  93. package/src/middleware/pipeline.ts +0 -489
  94. package/src/middleware/rate_limit.ts +0 -245
  95. package/src/middleware/request_id.ts +0 -36
  96. package/src/middleware/request_signing.ts +0 -636
  97. package/src/middleware/request_tracer.ts +0 -636
  98. package/src/middleware/response_cache.ts +0 -743
  99. package/src/middleware/security.ts +0 -482
  100. package/src/middleware/security_suite.ts +0 -257
  101. package/src/middleware/session.ts +0 -91
  102. package/src/model-binding/index.ts +0 -18
  103. package/src/model-binding/model-middleware.ts +0 -425
  104. package/src/model-binding/model-registry.ts +0 -550
  105. package/src/model-binding.ts +0 -370
  106. package/src/model-resolver-factory.ts +0 -106
  107. package/src/observability/correlation.ts +0 -691
  108. package/src/observability/health-checks.ts +0 -729
  109. package/src/observability/index.ts +0 -184
  110. package/src/observability/integration.ts +0 -548
  111. package/src/observability/metrics.ts +0 -753
  112. package/src/observability/tracing.ts +0 -638
  113. package/src/optimization/bun-utilities.ts +0 -778
  114. package/src/query-builder-integration.ts +0 -137
  115. package/src/request/context.ts +0 -62
  116. package/src/request/enhanced-request.ts +0 -857
  117. package/src/request/macros.ts +0 -688
  118. package/src/response/macros.ts +0 -665
  119. package/src/response/response-factory.ts +0 -596
  120. package/src/router/api-routes.ts +0 -243
  121. package/src/router/file-based-routing.ts +0 -690
  122. package/src/router/file-streaming.ts +0 -383
  123. package/src/router/fluent-router.ts +0 -927
  124. package/src/router/fluent-routing.ts +0 -797
  125. package/src/router/group-organization.ts +0 -213
  126. package/src/router/handler-resolver.ts +0 -291
  127. package/src/router/http-methods.ts +0 -377
  128. package/src/router/index.ts +0 -187
  129. package/src/router/middleware-groups.ts +0 -222
  130. package/src/router/middleware-integration.ts +0 -399
  131. package/src/router/middleware.ts +0 -231
  132. package/src/router/model-binding.ts +0 -215
  133. package/src/router/optimized-route-matching.ts +0 -253
  134. package/src/router/route-building.ts +0 -221
  135. package/src/router/route-compiler.ts +0 -702
  136. package/src/router/route-matching.ts +0 -349
  137. package/src/router/route-trie.ts +0 -464
  138. package/src/router/router.ts +0 -1604
  139. package/src/router/server.ts +0 -462
  140. package/src/router/validation-integration.ts +0 -443
  141. package/src/router/view-rendering.ts +0 -233
  142. package/src/router/websocket.ts +0 -100
  143. package/src/routing/route-caching.ts +0 -402
  144. package/src/routing/route-throttling.ts +0 -469
  145. package/src/routing/subdomain-routing.ts +0 -492
  146. package/src/session/database-store.ts +0 -109
  147. package/src/session/file-store.ts +0 -148
  148. package/src/session/index.ts +0 -244
  149. package/src/session/memory-store.ts +0 -88
  150. package/src/session/redis-store.ts +0 -93
  151. package/src/streaming/index.ts +0 -17
  152. package/src/streaming/sse-handler.ts +0 -482
  153. package/src/streaming/stream-handler.ts +0 -552
  154. package/src/testing/auth-testing.ts +0 -446
  155. package/src/testing/file-upload-testing.ts +0 -543
  156. package/src/testing/index.ts +0 -10
  157. package/src/testing/middleware-testing.ts +0 -322
  158. package/src/testing/model-binding-testing.ts +0 -645
  159. package/src/testing/performance-testing.ts +0 -738
  160. package/src/testing/test-client.ts +0 -310
  161. package/src/testing/test-request.ts +0 -315
  162. package/src/testing/test-response.ts +0 -332
  163. package/src/testing/types.ts +0 -224
  164. package/src/testing/websocket-testing.ts +0 -590
  165. package/src/types/controller-types.ts +0 -385
  166. package/src/types/core.ts +0 -683
  167. package/src/types/middleware-types.ts +0 -419
  168. package/src/types/request-response-augmentation.ts +0 -489
  169. package/src/types/route-inference.ts +0 -357
  170. package/src/types.ts +0 -2067
  171. package/src/url.ts +0 -126
  172. package/src/utils/index.ts +0 -1
  173. package/src/utils/query-preservation.ts +0 -201
  174. package/src/utils.ts +0 -392
  175. package/src/validation/validator.ts +0 -685
  176. package/src/websocket/clustering.ts +0 -762
@@ -1,762 +0,0 @@
1
- /**
2
- * WebSocket Clustering
3
- *
4
- * Native WebSocket clustering across workers with high-performance optimizations
5
- */
6
-
7
- import type { ServerWebSocket } from 'bun'
8
- import type { Buffer } from 'node:buffer'
9
- import process from 'node:process'
10
-
11
- export interface WebSocketClusterConfig {
12
- workers?: number
13
- maxConnections?: number
14
- heartbeatInterval?: number
15
- compression?: boolean
16
- backpressureLimit?: number
17
- closeOnBackpressureLimit?: boolean
18
- idleTimeout?: number
19
- maxPayloadLength?: number
20
- }
21
-
22
- export interface ClusterMessage {
23
- type: 'broadcast' | 'direct' | 'join' | 'leave' | 'heartbeat' | 'stats'
24
- workerId?: string
25
- targetId?: string
26
- roomId?: string
27
- data?: any
28
- timestamp: number
29
- }
30
-
31
- export interface WebSocketConnection {
32
- id: string
33
- workerId: string
34
- socket: ServerWebSocket<any>
35
- rooms: Set<string>
36
- metadata: Record<string, any>
37
- lastHeartbeat: number
38
- connected: boolean
39
- }
40
-
41
- export interface WebSocketRoom {
42
- id: string
43
- connections: Set<string>
44
- metadata: Record<string, any>
45
- created: number
46
- }
47
-
48
- export interface ClusterStats {
49
- totalConnections: number
50
- totalRooms: number
51
- workerStats: Record<string, {
52
- connections: number
53
- rooms: number
54
- memoryUsage: number
55
- uptime: number
56
- }>
57
- messagesSent: number
58
- messagesReceived: number
59
- bytesTransferred: number
60
- }
61
-
62
- /**
63
- * WebSocket cluster manager for distributing connections across workers
64
- */
65
- export class WebSocketCluster {
66
- private config: Required<WebSocketClusterConfig>
67
- private workers = new Map<string, Worker>()
68
- private connections = new Map<string, WebSocketConnection>()
69
- private rooms = new Map<string, WebSocketRoom>()
70
- private messageHandlers = new Map<string, (...args: any[]) => any>()
71
- private stats: ClusterStats
72
- private heartbeatTimer?: ReturnType<typeof setInterval>
73
- private isMainWorker: boolean
74
- private workerId: string
75
-
76
- constructor(config: WebSocketClusterConfig = {}) {
77
- this.config = {
78
- workers: config.workers || Math.max(1, Math.floor(navigator.hardwareConcurrency / 2)),
79
- maxConnections: config.maxConnections || 10000,
80
- heartbeatInterval: config.heartbeatInterval || 30000,
81
- compression: config.compression ?? true,
82
- backpressureLimit: config.backpressureLimit || 64 * 1024,
83
- closeOnBackpressureLimit: config.closeOnBackpressureLimit ?? false,
84
- idleTimeout: config.idleTimeout || 120000,
85
- maxPayloadLength: config.maxPayloadLength || 16 * 1024 * 1024,
86
- ...config,
87
- }
88
-
89
- this.workerId = this.generateWorkerId()
90
- this.isMainWorker = !process.env.BUN_WORKER_ID
91
- this.stats = this.initializeStats()
92
-
93
- this.setupWorkerCommunication()
94
- this.startHeartbeat()
95
- }
96
-
97
- /**
98
- * Create WebSocket server with clustering support
99
- */
100
- createServer(options: {
101
- port?: number
102
- hostname?: string
103
- onConnection?: (ws: ServerWebSocket<any>, request: Request) => void
104
- onMessage?: (ws: ServerWebSocket<any>, message: string | Buffer) => void
105
- onClose?: (ws: ServerWebSocket<any>, code?: number, reason?: string) => void
106
- onError?: (ws: ServerWebSocket<any>, error: Error) => void
107
- } = {}): any {
108
- const server = Bun.serve({
109
- port: options.port || 3000,
110
- hostname: options.hostname || 'localhost',
111
-
112
- fetch: (req, server) => {
113
- const url = new URL(req.url)
114
-
115
- if (url.pathname === '/ws') {
116
- const success = server.upgrade(req, {
117
- data: {
118
- connectionId: this.generateConnectionId(),
119
- workerId: this.workerId,
120
- connectedAt: Date.now(),
121
- rooms: new Set<string>(),
122
- metadata: {},
123
- } as any,
124
- })
125
-
126
- if (!success) {
127
- return new Response('WebSocket upgrade failed', { status: 400 })
128
- }
129
-
130
- return undefined
131
- }
132
-
133
- if (url.pathname === '/ws/stats') {
134
- return Response.json(this.getStats())
135
- }
136
-
137
- return new Response('Not Found', { status: 404 })
138
- },
139
-
140
- websocket: {
141
-
142
- open: (ws) => {
143
- const connectionId = (ws.data as any).connectionId
144
- const connection: WebSocketConnection = {
145
- id: connectionId,
146
- workerId: this.workerId,
147
- socket: ws,
148
- rooms: new Set(),
149
- metadata: (ws.data as any).metadata || {},
150
- lastHeartbeat: Date.now(),
151
- connected: true,
152
- }
153
-
154
- this.connections.set(connectionId, connection)
155
- this.stats.totalConnections++
156
- this.updateWorkerStats()
157
-
158
- // Notify other workers about new connection
159
- this.broadcastToWorkers({
160
- type: 'join',
161
- workerId: this.workerId,
162
- data: { connectionId, metadata: connection.metadata },
163
- timestamp: Date.now(),
164
- })
165
-
166
- options.onConnection?.(ws, (ws.data as any).request)
167
- },
168
-
169
- message: (ws, message) => {
170
- const connectionId = (ws.data as any).connectionId
171
- const connection = this.connections.get(connectionId)
172
-
173
- if (!connection)
174
- return
175
-
176
- connection.lastHeartbeat = Date.now()
177
- this.stats.messagesReceived++
178
- this.stats.bytesTransferred += typeof message === 'string' ? message.length : message.byteLength
179
-
180
- // Handle internal cluster messages
181
- if (typeof message === 'string' && message.startsWith('__cluster__')) {
182
- this.handleClusterMessage(connection, JSON.parse(message.slice(11)))
183
- return
184
- }
185
-
186
- options.onMessage?.(ws, message)
187
- },
188
-
189
- close: (ws, code, reason) => {
190
- const connectionId = (ws.data as any).connectionId
191
- const connection = this.connections.get(connectionId)
192
-
193
- if (connection) {
194
- connection.connected = false
195
-
196
- // Remove from all rooms
197
- connection.rooms.forEach((roomId) => {
198
- this.leaveRoom(connectionId, roomId)
199
- })
200
-
201
- this.connections.delete(connectionId)
202
- this.stats.totalConnections--
203
- this.updateWorkerStats()
204
-
205
- // Notify other workers about disconnection
206
- this.broadcastToWorkers({
207
- type: 'leave',
208
- workerId: this.workerId,
209
- data: { connectionId },
210
- timestamp: Date.now(),
211
- })
212
- }
213
-
214
- options.onClose?.(ws, code, reason)
215
- },
216
-
217
- },
218
- })
219
-
220
- console.log(`WebSocket cluster worker ${this.workerId} listening on ${options.hostname || 'localhost'}:${options.port || 3000}`)
221
- return server
222
- }
223
-
224
- /**
225
- * Join a connection to a room
226
- */
227
- joinRoom(connectionId: string, roomId: string, metadata: Record<string, any> = {}): boolean {
228
- const connection = this.connections.get(connectionId)
229
- if (!connection || !connection.connected)
230
- return false
231
-
232
- connection.rooms.add(roomId)
233
-
234
- if (!this.rooms.has(roomId)) {
235
- this.rooms.set(roomId, {
236
- id: roomId,
237
- connections: new Set(),
238
- metadata,
239
- created: Date.now(),
240
- })
241
- this.stats.totalRooms++
242
- }
243
-
244
- const room = this.rooms.get(roomId)!
245
- room.connections.add(connectionId)
246
-
247
- // Notify other workers
248
- this.broadcastToWorkers({
249
- type: 'join',
250
- workerId: this.workerId,
251
- roomId,
252
- data: { connectionId, metadata },
253
- timestamp: Date.now(),
254
- })
255
-
256
- return true
257
- }
258
-
259
- /**
260
- * Remove a connection from a room
261
- */
262
- leaveRoom(connectionId: string, roomId: string): boolean {
263
- const connection = this.connections.get(connectionId)
264
- if (!connection)
265
- return false
266
-
267
- connection.rooms.delete(roomId)
268
-
269
- const room = this.rooms.get(roomId)
270
- if (room) {
271
- room.connections.delete(connectionId)
272
-
273
- if (room.connections.size === 0) {
274
- this.rooms.delete(roomId)
275
- this.stats.totalRooms--
276
- }
277
- }
278
-
279
- // Notify other workers
280
- this.broadcastToWorkers({
281
- type: 'leave',
282
- workerId: this.workerId,
283
- roomId,
284
- data: { connectionId },
285
- timestamp: Date.now(),
286
- })
287
-
288
- return true
289
- }
290
-
291
- /**
292
- * Broadcast message to all connections in a room
293
- */
294
- broadcastToRoom(roomId: string, message: string | Buffer, excludeConnectionId?: string): number {
295
- const room = this.rooms.get(roomId)
296
- if (!room)
297
- return 0
298
-
299
- let sentCount = 0
300
-
301
- // Send to local connections
302
- room.connections.forEach((connectionId) => {
303
- if (connectionId === excludeConnectionId)
304
- return
305
-
306
- const connection = this.connections.get(connectionId)
307
- if (connection && connection.connected) {
308
- try {
309
- connection.socket.send(message)
310
- sentCount++
311
- this.stats.messagesSent++
312
- this.stats.bytesTransferred += typeof message === 'string' ? message.length : message.byteLength
313
- }
314
- catch (error) {
315
- console.error('Failed to send message to connection:', error)
316
- }
317
- }
318
- })
319
-
320
- // Broadcast to other workers
321
- this.broadcastToWorkers({
322
- type: 'broadcast',
323
- workerId: this.workerId,
324
- roomId,
325
- data: { message: message.toString(), excludeConnectionId },
326
- timestamp: Date.now(),
327
- })
328
-
329
- return sentCount
330
- }
331
-
332
- /**
333
- * Send message to specific connection
334
- */
335
- sendToConnection(connectionId: string, message: string | Buffer): boolean {
336
- const connection = this.connections.get(connectionId)
337
-
338
- if (connection && connection.connected) {
339
- try {
340
- connection.socket.send(message)
341
- this.stats.messagesSent++
342
- this.stats.bytesTransferred += typeof message === 'string' ? message.length : message.byteLength
343
- return true
344
- }
345
- catch (error) {
346
- console.error('Failed to send message to connection:', error)
347
- return false
348
- }
349
- }
350
-
351
- // Try to send via other workers
352
- this.broadcastToWorkers({
353
- type: 'direct',
354
- workerId: this.workerId,
355
- targetId: connectionId,
356
- data: { message: message.toString() },
357
- timestamp: Date.now(),
358
- })
359
-
360
- return false
361
- }
362
-
363
- /**
364
- * Get cluster statistics
365
- */
366
- getStats(): ClusterStats {
367
- this.updateWorkerStats()
368
- return { ...this.stats }
369
- }
370
-
371
- /**
372
- * Get connections in a room
373
- */
374
- getRoomConnections(roomId: string): string[] {
375
- const room = this.rooms.get(roomId)
376
- return room ? Array.from(room.connections) : []
377
- }
378
-
379
- /**
380
- * Get rooms for a connection
381
- */
382
- getConnectionRooms(connectionId: string): string[] {
383
- const connection = this.connections.get(connectionId)
384
- return connection ? Array.from(connection.rooms) : []
385
- }
386
-
387
- /**
388
- * Close connection
389
- */
390
- closeConnection(connectionId: string, code?: number, reason?: string): boolean {
391
- const connection = this.connections.get(connectionId)
392
- if (!connection || !connection.connected)
393
- return false
394
-
395
- try {
396
- connection.socket.close(code, reason)
397
- return true
398
- }
399
- catch (error) {
400
- console.error('Failed to close connection:', error)
401
- return false
402
- }
403
- }
404
-
405
- /**
406
- * Shutdown cluster
407
- */
408
- shutdown(): void {
409
- if (this.heartbeatTimer) {
410
- clearInterval(this.heartbeatTimer)
411
- }
412
-
413
- // Close all connections
414
- this.connections.forEach((connection) => {
415
- if (connection.connected) {
416
- connection.socket.close(1001, 'Server shutdown')
417
- }
418
- })
419
-
420
- // Terminate workers
421
- this.workers.forEach((worker) => {
422
- worker.terminate()
423
- })
424
-
425
- this.connections.clear()
426
- this.rooms.clear()
427
- this.workers.clear()
428
- }
429
-
430
- /**
431
- * Generate unique connection ID
432
- */
433
- private generateConnectionId(): string {
434
- return `conn_${this.workerId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
435
- }
436
-
437
- /**
438
- * Generate unique worker ID
439
- */
440
- private generateWorkerId(): string {
441
- return process.env.BUN_WORKER_ID || `worker_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
442
- }
443
-
444
- /**
445
- * Initialize statistics
446
- */
447
- private initializeStats(): ClusterStats {
448
- return {
449
- totalConnections: 0,
450
- totalRooms: 0,
451
- workerStats: {},
452
- messagesSent: 0,
453
- messagesReceived: 0,
454
- bytesTransferred: 0,
455
- }
456
- }
457
-
458
- /**
459
- * Setup worker communication
460
- */
461
- private setupWorkerCommunication(): void {
462
- if (this.isMainWorker) {
463
- // Main worker spawns additional workers
464
- for (let i = 1; i < this.config.workers; i++) {
465
- const worker = new Worker(new URL(import.meta.url), {
466
- env: { ...process.env, BUN_WORKER_ID: `worker_${i}` },
467
- })
468
-
469
- worker.onmessage = (event) => {
470
- this.handleWorkerMessage(event.data)
471
- }
472
-
473
- this.workers.set(`worker_${i}`, worker)
474
- }
475
- }
476
- else {
477
- // Worker listens for messages from main worker
478
- process.on('message', (message) => {
479
- this.handleWorkerMessage(message as ClusterMessage)
480
- })
481
- }
482
- }
483
-
484
- /**
485
- * Handle messages from other workers
486
- */
487
- private handleWorkerMessage(message: ClusterMessage): void {
488
- switch (message.type) {
489
- case 'broadcast':
490
- if (message.roomId && message.data?.message) {
491
- const room = this.rooms.get(message.roomId)
492
- if (room) {
493
- room.connections.forEach((connectionId) => {
494
- if (connectionId === message.data.excludeConnectionId)
495
- return
496
-
497
- const connection = this.connections.get(connectionId)
498
- if (connection && connection.connected) {
499
- connection.socket.send(message.data.message)
500
- }
501
- })
502
- }
503
- }
504
- break
505
-
506
- case 'direct':
507
- if (message.targetId && message.data?.message) {
508
- const connection = this.connections.get(message.targetId)
509
- if (connection && connection.connected) {
510
- connection.socket.send(message.data.message)
511
- }
512
- }
513
- break
514
-
515
- case 'join':
516
- if (message.roomId && message.data?.connectionId) {
517
- // Update room information from other workers
518
- if (!this.rooms.has(message.roomId)) {
519
- this.rooms.set(message.roomId, {
520
- id: message.roomId,
521
- connections: new Set(),
522
- metadata: message.data.metadata || {},
523
- created: Date.now(),
524
- })
525
- }
526
- }
527
- break
528
-
529
- case 'leave':
530
- if (message.roomId && message.data?.connectionId) {
531
- const room = this.rooms.get(message.roomId)
532
- if (room) {
533
- room.connections.delete(message.data.connectionId)
534
- if (room.connections.size === 0) {
535
- this.rooms.delete(message.roomId)
536
- }
537
- }
538
- }
539
- break
540
-
541
- case 'stats':
542
- // Handle stats updates from other workers
543
- if (message.data && message.workerId) {
544
- this.stats.workerStats[message.workerId] = message.data
545
- }
546
- break
547
- }
548
- }
549
-
550
- /**
551
- * Handle cluster messages from connections
552
- */
553
- private handleClusterMessage(connection: WebSocketConnection, message: ClusterMessage): void {
554
- switch (message.type) {
555
- case 'join':
556
- if (message.roomId) {
557
- this.joinRoom(connection.id, message.roomId, message.data || {})
558
- }
559
- break
560
-
561
- case 'leave':
562
- if (message.roomId) {
563
- this.leaveRoom(connection.id, message.roomId)
564
- }
565
- break
566
-
567
- case 'broadcast':
568
- if (message.roomId && message.data?.message) {
569
- this.broadcastToRoom(message.roomId, message.data.message, connection.id)
570
- }
571
- break
572
-
573
- case 'heartbeat':
574
- connection.lastHeartbeat = Date.now()
575
- connection.socket.send(`__cluster__${JSON.stringify({
576
- type: 'heartbeat',
577
- timestamp: Date.now(),
578
- })}`)
579
- break
580
- }
581
- }
582
-
583
- /**
584
- * Broadcast message to all workers
585
- */
586
- private broadcastToWorkers(message: ClusterMessage): void {
587
- if (this.isMainWorker) {
588
- this.workers.forEach((worker) => {
589
- worker.postMessage(message)
590
- })
591
- }
592
- else {
593
- process.send?.(message)
594
- }
595
- }
596
-
597
- /**
598
- * Start heartbeat timer
599
- */
600
- private startHeartbeat(): void {
601
- this.heartbeatTimer = setInterval(() => {
602
- const now = Date.now()
603
- const timeout = this.config.heartbeatInterval * 2
604
-
605
- // Check for stale connections
606
- this.connections.forEach((connection, connectionId) => {
607
- if (now - connection.lastHeartbeat > timeout) {
608
- console.log(`Closing stale connection: ${connectionId}`)
609
- this.closeConnection(connectionId, 1001, 'Connection timeout')
610
- }
611
- })
612
-
613
- // Send stats to other workers
614
- this.broadcastToWorkers({
615
- type: 'stats',
616
- workerId: this.workerId,
617
- data: {
618
- connections: this.connections.size,
619
- rooms: this.rooms.size,
620
- memoryUsage: process.memoryUsage().heapUsed,
621
- uptime: process.uptime(),
622
- },
623
- timestamp: now,
624
- })
625
- }, this.config.heartbeatInterval)
626
- }
627
-
628
- /**
629
- * Update worker statistics
630
- */
631
- private updateWorkerStats(): void {
632
- this.stats.workerStats[this.workerId] = {
633
- connections: this.connections.size,
634
- rooms: this.rooms.size,
635
- memoryUsage: process.memoryUsage().heapUsed,
636
- uptime: process.uptime(),
637
- }
638
- }
639
- }
640
-
641
- /**
642
- * WebSocket cluster factory
643
- */
644
- export class WebSocketClusterFactory {
645
- /**
646
- * Create WebSocket cluster with default configuration
647
- */
648
- static create(config?: WebSocketClusterConfig): WebSocketCluster {
649
- return new WebSocketCluster(config)
650
- }
651
-
652
- /**
653
- * Create high-performance cluster for production
654
- */
655
- static createHighPerformance(): WebSocketCluster {
656
- return new WebSocketCluster({
657
- workers: Math.max(2, navigator.hardwareConcurrency),
658
- maxConnections: 50000,
659
- heartbeatInterval: 60000,
660
- compression: true,
661
- backpressureLimit: 128 * 1024,
662
- closeOnBackpressureLimit: true,
663
- idleTimeout: 300000,
664
- maxPayloadLength: 32 * 1024 * 1024,
665
- })
666
- }
667
-
668
- /**
669
- * Create development cluster with debugging
670
- */
671
- static createDevelopment(): WebSocketCluster {
672
- return new WebSocketCluster({
673
- workers: 1,
674
- maxConnections: 1000,
675
- heartbeatInterval: 10000,
676
- compression: false,
677
- backpressureLimit: 16 * 1024,
678
- closeOnBackpressureLimit: false,
679
- idleTimeout: 60000,
680
- maxPayloadLength: 1024 * 1024,
681
- })
682
- }
683
- }
684
-
685
- /**
686
- * WebSocket cluster helpers
687
- */
688
- export const WebSocketClusterHelpers = {
689
- /**
690
- * Create room-based chat system
691
- */
692
- createChatSystem: (cluster: WebSocketCluster): any => ({
693
- joinRoom: (connectionId: string, roomId: string, userInfo: any) => {
694
- return cluster.joinRoom(connectionId, roomId, { userInfo, joinedAt: Date.now() })
695
- },
696
-
697
- leaveRoom: (connectionId: string, roomId: string) => {
698
- return cluster.leaveRoom(connectionId, roomId)
699
- },
700
-
701
- sendMessage: (roomId: string, message: any, fromConnectionId?: string) => {
702
- const chatMessage = JSON.stringify({
703
- type: 'chat_message',
704
- roomId,
705
- message,
706
- timestamp: Date.now(),
707
- from: fromConnectionId,
708
- })
709
- return cluster.broadcastToRoom(roomId, chatMessage, fromConnectionId)
710
- },
711
-
712
- getRoomUsers: (roomId: string) => {
713
- return cluster.getRoomConnections(roomId)
714
- },
715
- }),
716
-
717
- /**
718
- * Create real-time notifications system
719
- */
720
- createNotificationSystem: (cluster: WebSocketCluster): any => ({
721
- subscribe: (connectionId: string, topics: string[]) => {
722
- topics.forEach((topic) => {
723
- cluster.joinRoom(connectionId, `notification:${topic}`)
724
- })
725
- },
726
-
727
- unsubscribe: (connectionId: string, topics: string[]) => {
728
- topics.forEach((topic) => {
729
- cluster.leaveRoom(connectionId, `notification:${topic}`)
730
- })
731
- },
732
-
733
- notify: (topic: string, notification: any) => {
734
- const message = JSON.stringify({
735
- type: 'notification',
736
- topic,
737
- data: notification,
738
- timestamp: Date.now(),
739
- })
740
- return cluster.broadcastToRoom(`notification:${topic}`, message)
741
- },
742
- }),
743
-
744
- /**
745
- * Create presence system
746
- */
747
- createPresenceSystem: (cluster: WebSocketCluster): any => ({
748
- setPresence: (connectionId: string, status: 'online' | 'away' | 'busy' | 'offline', metadata?: any) => {
749
- cluster.broadcastToRoom('presence', JSON.stringify({
750
- type: 'presence_update',
751
- connectionId,
752
- status,
753
- metadata,
754
- timestamp: Date.now(),
755
- }), connectionId)
756
- },
757
-
758
- getOnlineUsers: () => {
759
- return cluster.getRoomConnections('presence')
760
- },
761
- }),
762
- }