@stacksjs/bun-router 0.0.17 → 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 (146) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/container/service-provider.d.ts +3 -3
  3. package/dist/development/performance-profiler.d.ts +3 -3
  4. package/dist/development/route-debugger.d.ts +1 -1
  5. package/dist/development/route-inspector.d.ts +2 -2
  6. package/dist/observability/integration.d.ts +1 -1
  7. package/dist/response/macros.d.ts +1 -1
  8. package/dist/router/index.d.ts +7 -7
  9. package/dist/router/router.d.ts +0 -32
  10. package/dist/testing/performance-testing.d.ts +2 -2
  11. package/dist/testing/test-client.d.ts +1 -1
  12. package/dist/testing/websocket-testing.d.ts +1 -1
  13. package/package.json +2 -3
  14. package/src/auth.ts +0 -489
  15. package/src/cache/lru-cache.ts +0 -457
  16. package/src/cache/middleware-memoization.ts +0 -531
  17. package/src/cache/route-cache-warmer.ts +0 -486
  18. package/src/cache/sqlite-cache.ts +0 -783
  19. package/src/cache/streaming-cache.ts +0 -572
  20. package/src/cli/colors.ts +0 -29
  21. package/src/cli/index.ts +0 -287
  22. package/src/cli/middleware.ts +0 -291
  23. package/src/cli/openapi.ts +0 -407
  24. package/src/cli/router.ts +0 -188
  25. package/src/cli/routes.ts +0 -265
  26. package/src/cli/utils.ts +0 -531
  27. package/src/cli.ts +0 -5
  28. package/src/config.ts +0 -322
  29. package/src/container/container.ts +0 -786
  30. package/src/container/contextual-binding.ts +0 -606
  31. package/src/container/decorators.ts +0 -368
  32. package/src/container/index.ts +0 -49
  33. package/src/container/service-provider.ts +0 -596
  34. package/src/development/hot-reload.ts +0 -673
  35. package/src/development/index.ts +0 -499
  36. package/src/development/performance-profiler.ts +0 -717
  37. package/src/development/route-debugger.ts +0 -527
  38. package/src/development/route-inspector.ts +0 -749
  39. package/src/development/typescript-utilities.ts +0 -682
  40. package/src/docs.ts +0 -397
  41. package/src/errors/circuit-breaker.ts +0 -733
  42. package/src/errors/error-handler.ts +0 -569
  43. package/src/errors/error-reporting.ts +0 -672
  44. package/src/errors/exceptions.ts +0 -536
  45. package/src/errors/graceful-degradation.ts +0 -623
  46. package/src/errors/index.ts +0 -21
  47. package/src/errors/router-errors.ts +0 -632
  48. package/src/file-serving/static-files.ts +0 -718
  49. package/src/index.ts +0 -66
  50. package/src/middleware/auth.ts +0 -220
  51. package/src/middleware/content_security_policy.ts +0 -215
  52. package/src/middleware/cors.ts +0 -142
  53. package/src/middleware/csrf.ts +0 -166
  54. package/src/middleware/ddos_protection.ts +0 -279
  55. package/src/middleware/file_security.ts +0 -191
  56. package/src/middleware/file_upload.ts +0 -275
  57. package/src/middleware/helmet.ts +0 -268
  58. package/src/middleware/index.ts +0 -117
  59. package/src/middleware/input_validation.ts +0 -453
  60. package/src/middleware/json_body.ts +0 -37
  61. package/src/middleware/performance_alerting.ts +0 -540
  62. package/src/middleware/performance_dashboard.ts +0 -661
  63. package/src/middleware/performance_monitor.ts +0 -945
  64. package/src/middleware/pipeline.ts +0 -489
  65. package/src/middleware/rate_limit.ts +0 -251
  66. package/src/middleware/request_id.ts +0 -36
  67. package/src/middleware/request_signing.ts +0 -638
  68. package/src/middleware/request_tracer.ts +0 -653
  69. package/src/middleware/response_cache.ts +0 -778
  70. package/src/middleware/security.ts +0 -482
  71. package/src/middleware/security_suite.ts +0 -257
  72. package/src/middleware/session.ts +0 -91
  73. package/src/model-binding/index.ts +0 -18
  74. package/src/model-binding/model-middleware.ts +0 -425
  75. package/src/model-binding/model-registry.ts +0 -550
  76. package/src/model-binding.ts +0 -370
  77. package/src/model-resolver-factory.ts +0 -106
  78. package/src/observability/correlation.ts +0 -691
  79. package/src/observability/health-checks.ts +0 -729
  80. package/src/observability/index.ts +0 -184
  81. package/src/observability/integration.ts +0 -548
  82. package/src/observability/metrics.ts +0 -753
  83. package/src/observability/tracing.ts +0 -638
  84. package/src/optimization/bun-utilities.ts +0 -778
  85. package/src/query-builder-integration.ts +0 -137
  86. package/src/request/context.ts +0 -109
  87. package/src/request/enhanced-request.ts +0 -876
  88. package/src/request/macros.ts +0 -822
  89. package/src/response/macros.ts +0 -665
  90. package/src/response/response-factory.ts +0 -596
  91. package/src/router/api-routes.ts +0 -243
  92. package/src/router/file-based-routing.ts +0 -697
  93. package/src/router/file-streaming.ts +0 -383
  94. package/src/router/fluent-routing.ts +0 -866
  95. package/src/router/group-organization.ts +0 -229
  96. package/src/router/handler-resolver.ts +0 -327
  97. package/src/router/http-methods.ts +0 -407
  98. package/src/router/index.ts +0 -193
  99. package/src/router/middleware-groups.ts +0 -222
  100. package/src/router/middleware-integration.ts +0 -399
  101. package/src/router/middleware.ts +0 -234
  102. package/src/router/model-binding.ts +0 -215
  103. package/src/router/optimized-route-matching.ts +0 -302
  104. package/src/router/route-building.ts +0 -221
  105. package/src/router/route-compiler.ts +0 -691
  106. package/src/router/route-matching.ts +0 -368
  107. package/src/router/route-trie.ts +0 -450
  108. package/src/router/router.ts +0 -1616
  109. package/src/router/server.ts +0 -684
  110. package/src/router/validation-integration.ts +0 -449
  111. package/src/router/view-rendering.ts +0 -233
  112. package/src/router/websocket.ts +0 -100
  113. package/src/routing/route-caching.ts +0 -402
  114. package/src/routing/route-throttling.ts +0 -469
  115. package/src/routing/subdomain-routing.ts +0 -492
  116. package/src/session/database-store.ts +0 -109
  117. package/src/session/file-store.ts +0 -148
  118. package/src/session/index.ts +0 -244
  119. package/src/session/memory-store.ts +0 -88
  120. package/src/session/redis-store.ts +0 -93
  121. package/src/streaming/index.ts +0 -17
  122. package/src/streaming/sse-handler.ts +0 -482
  123. package/src/streaming/stream-handler.ts +0 -552
  124. package/src/testing/auth-testing.ts +0 -448
  125. package/src/testing/file-upload-testing.ts +0 -543
  126. package/src/testing/index.ts +0 -10
  127. package/src/testing/middleware-testing.ts +0 -322
  128. package/src/testing/model-binding-testing.ts +0 -645
  129. package/src/testing/performance-testing.ts +0 -738
  130. package/src/testing/test-client.ts +0 -310
  131. package/src/testing/test-request.ts +0 -315
  132. package/src/testing/test-response.ts +0 -332
  133. package/src/testing/types.ts +0 -224
  134. package/src/testing/websocket-testing.ts +0 -590
  135. package/src/types/controller-types.ts +0 -385
  136. package/src/types/core.ts +0 -683
  137. package/src/types/middleware-types.ts +0 -422
  138. package/src/types/request-response-augmentation.ts +0 -489
  139. package/src/types/route-inference.ts +0 -372
  140. package/src/types.ts +0 -2123
  141. package/src/url.ts +0 -131
  142. package/src/utils/index.ts +0 -1
  143. package/src/utils/query-preservation.ts +0 -201
  144. package/src/utils.ts +0 -395
  145. package/src/validation/validator.ts +0 -688
  146. package/src/websocket/clustering.ts +0 -769
@@ -1,769 +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. Bounded: with worker
543
- // churn the per-worker entries would otherwise accumulate forever.
544
- if (message.data && message.workerId) {
545
- const workerIds = Object.keys(this.stats.workerStats)
546
- if (workerIds.length >= 1000 && !(message.workerId in this.stats.workerStats)) {
547
- delete this.stats.workerStats[workerIds[0]]
548
- }
549
- this.stats.workerStats[message.workerId] = message.data
550
- }
551
- break
552
- }
553
- }
554
-
555
- /**
556
- * Handle cluster messages from connections
557
- */
558
- private handleClusterMessage(connection: WebSocketConnection, message: ClusterMessage): void {
559
- switch (message.type) {
560
- case 'join':
561
- if (message.roomId) {
562
- this.joinRoom(connection.id, message.roomId, message.data || {})
563
- }
564
- break
565
-
566
- case 'leave':
567
- if (message.roomId) {
568
- this.leaveRoom(connection.id, message.roomId)
569
- }
570
- break
571
-
572
- case 'broadcast':
573
- if (message.roomId && message.data?.message) {
574
- this.broadcastToRoom(message.roomId, message.data.message, connection.id)
575
- }
576
- break
577
-
578
- case 'heartbeat':
579
- connection.lastHeartbeat = Date.now()
580
- connection.socket.send(`__cluster__${JSON.stringify({
581
- type: 'heartbeat',
582
- timestamp: Date.now(),
583
- })}`)
584
- break
585
- }
586
- }
587
-
588
- /**
589
- * Broadcast message to all workers
590
- */
591
- private broadcastToWorkers(message: ClusterMessage): void {
592
- if (this.isMainWorker) {
593
- this.workers.forEach((worker) => {
594
- worker.postMessage(message)
595
- })
596
- }
597
- else {
598
- process.send?.(message)
599
- }
600
- }
601
-
602
- /**
603
- * Start heartbeat timer
604
- */
605
- private startHeartbeat(): void {
606
- this.heartbeatTimer = setInterval(() => {
607
- const now = Date.now()
608
- const timeout = this.config.heartbeatInterval * 2
609
-
610
- // Check for stale connections
611
- this.connections.forEach((connection, connectionId) => {
612
- if (now - connection.lastHeartbeat > timeout) {
613
- console.log(`Closing stale connection: ${connectionId}`)
614
- this.closeConnection(connectionId, 1001, 'Connection timeout')
615
- }
616
- })
617
-
618
- // Send stats to other workers
619
- this.broadcastToWorkers({
620
- type: 'stats',
621
- workerId: this.workerId,
622
- data: {
623
- connections: this.connections.size,
624
- rooms: this.rooms.size,
625
- memoryUsage: process.memoryUsage().heapUsed,
626
- uptime: process.uptime(),
627
- },
628
- timestamp: now,
629
- })
630
- }, this.config.heartbeatInterval)
631
- // The heartbeat must not keep the process alive on shutdown
632
- this.heartbeatTimer.unref?.()
633
- }
634
-
635
- /**
636
- * Update worker statistics
637
- */
638
- private updateWorkerStats(): void {
639
- this.stats.workerStats[this.workerId] = {
640
- connections: this.connections.size,
641
- rooms: this.rooms.size,
642
- memoryUsage: process.memoryUsage().heapUsed,
643
- uptime: process.uptime(),
644
- }
645
- }
646
- }
647
-
648
- /**
649
- * WebSocket cluster factory
650
- */
651
- export class WebSocketClusterFactory {
652
- /**
653
- * Create WebSocket cluster with default configuration
654
- */
655
- static create(config?: WebSocketClusterConfig): WebSocketCluster {
656
- return new WebSocketCluster(config)
657
- }
658
-
659
- /**
660
- * Create high-performance cluster for production
661
- */
662
- static createHighPerformance(): WebSocketCluster {
663
- return new WebSocketCluster({
664
- workers: Math.max(2, navigator.hardwareConcurrency),
665
- maxConnections: 50000,
666
- heartbeatInterval: 60000,
667
- compression: true,
668
- backpressureLimit: 128 * 1024,
669
- closeOnBackpressureLimit: true,
670
- idleTimeout: 300000,
671
- maxPayloadLength: 32 * 1024 * 1024,
672
- })
673
- }
674
-
675
- /**
676
- * Create development cluster with debugging
677
- */
678
- static createDevelopment(): WebSocketCluster {
679
- return new WebSocketCluster({
680
- workers: 1,
681
- maxConnections: 1000,
682
- heartbeatInterval: 10000,
683
- compression: false,
684
- backpressureLimit: 16 * 1024,
685
- closeOnBackpressureLimit: false,
686
- idleTimeout: 60000,
687
- maxPayloadLength: 1024 * 1024,
688
- })
689
- }
690
- }
691
-
692
- /**
693
- * WebSocket cluster helpers
694
- */
695
- export const WebSocketClusterHelpers = {
696
- /**
697
- * Create room-based chat system
698
- */
699
- createChatSystem: (cluster: WebSocketCluster): any => ({
700
- joinRoom: (connectionId: string, roomId: string, userInfo: any) => {
701
- return cluster.joinRoom(connectionId, roomId, { userInfo, joinedAt: Date.now() })
702
- },
703
-
704
- leaveRoom: (connectionId: string, roomId: string) => {
705
- return cluster.leaveRoom(connectionId, roomId)
706
- },
707
-
708
- sendMessage: (roomId: string, message: any, fromConnectionId?: string) => {
709
- const chatMessage = JSON.stringify({
710
- type: 'chat_message',
711
- roomId,
712
- message,
713
- timestamp: Date.now(),
714
- from: fromConnectionId,
715
- })
716
- return cluster.broadcastToRoom(roomId, chatMessage, fromConnectionId)
717
- },
718
-
719
- getRoomUsers: (roomId: string) => {
720
- return cluster.getRoomConnections(roomId)
721
- },
722
- }),
723
-
724
- /**
725
- * Create real-time notifications system
726
- */
727
- createNotificationSystem: (cluster: WebSocketCluster): any => ({
728
- subscribe: (connectionId: string, topics: string[]) => {
729
- topics.forEach((topic) => {
730
- cluster.joinRoom(connectionId, `notification:${topic}`)
731
- })
732
- },
733
-
734
- unsubscribe: (connectionId: string, topics: string[]) => {
735
- topics.forEach((topic) => {
736
- cluster.leaveRoom(connectionId, `notification:${topic}`)
737
- })
738
- },
739
-
740
- notify: (topic: string, notification: any) => {
741
- const message = JSON.stringify({
742
- type: 'notification',
743
- topic,
744
- data: notification,
745
- timestamp: Date.now(),
746
- })
747
- return cluster.broadcastToRoom(`notification:${topic}`, message)
748
- },
749
- }),
750
-
751
- /**
752
- * Create presence system
753
- */
754
- createPresenceSystem: (cluster: WebSocketCluster): any => ({
755
- setPresence: (connectionId: string, status: 'online' | 'away' | 'busy' | 'offline', metadata?: any) => {
756
- cluster.broadcastToRoom('presence', JSON.stringify({
757
- type: 'presence_update',
758
- connectionId,
759
- status,
760
- metadata,
761
- timestamp: Date.now(),
762
- }), connectionId)
763
- },
764
-
765
- getOnlineUsers: () => {
766
- return cluster.getRoomConnections('presence')
767
- },
768
- }),
769
- }