@stacksjs/bun-router 0.0.17 → 0.0.19

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 (153) hide show
  1. package/dist/chunk-2mx7fq49.js +4 -0
  2. package/dist/chunk-a8p83068.js +103 -0
  3. package/dist/{chunk-cgptvjdf.js → chunk-y3xq6wq6.js} +1 -1
  4. package/dist/cli.js +4 -3
  5. package/dist/container/index.js +1149 -211
  6. package/dist/container/service-provider.d.ts +3 -3
  7. package/dist/development/performance-profiler.d.ts +3 -3
  8. package/dist/development/route-debugger.d.ts +1 -1
  9. package/dist/development/route-inspector.d.ts +2 -2
  10. package/dist/index.js +369 -2209
  11. package/dist/observability/integration.d.ts +1 -1
  12. package/dist/response/macros.d.ts +1 -1
  13. package/dist/router/index.d.ts +7 -7
  14. package/dist/router/router.d.ts +0 -32
  15. package/dist/testing/performance-testing.d.ts +2 -2
  16. package/dist/testing/test-client.d.ts +1 -1
  17. package/dist/testing/websocket-testing.d.ts +1 -1
  18. package/package.json +7 -7
  19. package/dist/chunk-1ahs68ys.js +0 -18
  20. package/dist/chunk-g3ybefhg.js +0 -1923
  21. package/src/auth.ts +0 -489
  22. package/src/cache/lru-cache.ts +0 -457
  23. package/src/cache/middleware-memoization.ts +0 -531
  24. package/src/cache/route-cache-warmer.ts +0 -486
  25. package/src/cache/sqlite-cache.ts +0 -783
  26. package/src/cache/streaming-cache.ts +0 -572
  27. package/src/cli/colors.ts +0 -29
  28. package/src/cli/index.ts +0 -287
  29. package/src/cli/middleware.ts +0 -291
  30. package/src/cli/openapi.ts +0 -407
  31. package/src/cli/router.ts +0 -188
  32. package/src/cli/routes.ts +0 -265
  33. package/src/cli/utils.ts +0 -531
  34. package/src/cli.ts +0 -5
  35. package/src/config.ts +0 -322
  36. package/src/container/container.ts +0 -786
  37. package/src/container/contextual-binding.ts +0 -606
  38. package/src/container/decorators.ts +0 -368
  39. package/src/container/index.ts +0 -49
  40. package/src/container/service-provider.ts +0 -596
  41. package/src/development/hot-reload.ts +0 -673
  42. package/src/development/index.ts +0 -499
  43. package/src/development/performance-profiler.ts +0 -717
  44. package/src/development/route-debugger.ts +0 -527
  45. package/src/development/route-inspector.ts +0 -749
  46. package/src/development/typescript-utilities.ts +0 -682
  47. package/src/docs.ts +0 -397
  48. package/src/errors/circuit-breaker.ts +0 -733
  49. package/src/errors/error-handler.ts +0 -569
  50. package/src/errors/error-reporting.ts +0 -672
  51. package/src/errors/exceptions.ts +0 -536
  52. package/src/errors/graceful-degradation.ts +0 -623
  53. package/src/errors/index.ts +0 -21
  54. package/src/errors/router-errors.ts +0 -632
  55. package/src/file-serving/static-files.ts +0 -718
  56. package/src/index.ts +0 -66
  57. package/src/middleware/auth.ts +0 -220
  58. package/src/middleware/content_security_policy.ts +0 -215
  59. package/src/middleware/cors.ts +0 -142
  60. package/src/middleware/csrf.ts +0 -166
  61. package/src/middleware/ddos_protection.ts +0 -279
  62. package/src/middleware/file_security.ts +0 -191
  63. package/src/middleware/file_upload.ts +0 -275
  64. package/src/middleware/helmet.ts +0 -268
  65. package/src/middleware/index.ts +0 -117
  66. package/src/middleware/input_validation.ts +0 -453
  67. package/src/middleware/json_body.ts +0 -37
  68. package/src/middleware/performance_alerting.ts +0 -540
  69. package/src/middleware/performance_dashboard.ts +0 -661
  70. package/src/middleware/performance_monitor.ts +0 -945
  71. package/src/middleware/pipeline.ts +0 -489
  72. package/src/middleware/rate_limit.ts +0 -251
  73. package/src/middleware/request_id.ts +0 -36
  74. package/src/middleware/request_signing.ts +0 -638
  75. package/src/middleware/request_tracer.ts +0 -653
  76. package/src/middleware/response_cache.ts +0 -778
  77. package/src/middleware/security.ts +0 -482
  78. package/src/middleware/security_suite.ts +0 -257
  79. package/src/middleware/session.ts +0 -91
  80. package/src/model-binding/index.ts +0 -18
  81. package/src/model-binding/model-middleware.ts +0 -425
  82. package/src/model-binding/model-registry.ts +0 -550
  83. package/src/model-binding.ts +0 -370
  84. package/src/model-resolver-factory.ts +0 -106
  85. package/src/observability/correlation.ts +0 -691
  86. package/src/observability/health-checks.ts +0 -729
  87. package/src/observability/index.ts +0 -184
  88. package/src/observability/integration.ts +0 -548
  89. package/src/observability/metrics.ts +0 -753
  90. package/src/observability/tracing.ts +0 -638
  91. package/src/optimization/bun-utilities.ts +0 -778
  92. package/src/query-builder-integration.ts +0 -137
  93. package/src/request/context.ts +0 -109
  94. package/src/request/enhanced-request.ts +0 -876
  95. package/src/request/macros.ts +0 -822
  96. package/src/response/macros.ts +0 -665
  97. package/src/response/response-factory.ts +0 -596
  98. package/src/router/api-routes.ts +0 -243
  99. package/src/router/file-based-routing.ts +0 -697
  100. package/src/router/file-streaming.ts +0 -383
  101. package/src/router/fluent-routing.ts +0 -866
  102. package/src/router/group-organization.ts +0 -229
  103. package/src/router/handler-resolver.ts +0 -327
  104. package/src/router/http-methods.ts +0 -407
  105. package/src/router/index.ts +0 -193
  106. package/src/router/middleware-groups.ts +0 -222
  107. package/src/router/middleware-integration.ts +0 -399
  108. package/src/router/middleware.ts +0 -234
  109. package/src/router/model-binding.ts +0 -215
  110. package/src/router/optimized-route-matching.ts +0 -302
  111. package/src/router/route-building.ts +0 -221
  112. package/src/router/route-compiler.ts +0 -691
  113. package/src/router/route-matching.ts +0 -368
  114. package/src/router/route-trie.ts +0 -450
  115. package/src/router/router.ts +0 -1616
  116. package/src/router/server.ts +0 -684
  117. package/src/router/validation-integration.ts +0 -449
  118. package/src/router/view-rendering.ts +0 -233
  119. package/src/router/websocket.ts +0 -100
  120. package/src/routing/route-caching.ts +0 -402
  121. package/src/routing/route-throttling.ts +0 -469
  122. package/src/routing/subdomain-routing.ts +0 -492
  123. package/src/session/database-store.ts +0 -109
  124. package/src/session/file-store.ts +0 -148
  125. package/src/session/index.ts +0 -244
  126. package/src/session/memory-store.ts +0 -88
  127. package/src/session/redis-store.ts +0 -93
  128. package/src/streaming/index.ts +0 -17
  129. package/src/streaming/sse-handler.ts +0 -482
  130. package/src/streaming/stream-handler.ts +0 -552
  131. package/src/testing/auth-testing.ts +0 -448
  132. package/src/testing/file-upload-testing.ts +0 -543
  133. package/src/testing/index.ts +0 -10
  134. package/src/testing/middleware-testing.ts +0 -322
  135. package/src/testing/model-binding-testing.ts +0 -645
  136. package/src/testing/performance-testing.ts +0 -738
  137. package/src/testing/test-client.ts +0 -310
  138. package/src/testing/test-request.ts +0 -315
  139. package/src/testing/test-response.ts +0 -332
  140. package/src/testing/types.ts +0 -224
  141. package/src/testing/websocket-testing.ts +0 -590
  142. package/src/types/controller-types.ts +0 -385
  143. package/src/types/core.ts +0 -683
  144. package/src/types/middleware-types.ts +0 -422
  145. package/src/types/request-response-augmentation.ts +0 -489
  146. package/src/types/route-inference.ts +0 -372
  147. package/src/types.ts +0 -2123
  148. package/src/url.ts +0 -131
  149. package/src/utils/index.ts +0 -1
  150. package/src/utils/query-preservation.ts +0 -201
  151. package/src/utils.ts +0 -395
  152. package/src/validation/validator.ts +0 -688
  153. 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
- }