@stacksjs/bun-router 0.0.14 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +261 -584
- package/bin/cli.ts +9 -0
- package/dist/auth.d.ts +134 -0
- package/dist/cache/lru-cache.d.ts +139 -0
- package/dist/cache/middleware-memoization.d.ts +151 -0
- package/dist/cache/route-cache-warmer.d.ts +161 -0
- package/dist/cache/sqlite-cache.d.ts +209 -0
- package/dist/cache/streaming-cache.d.ts +132 -0
- package/dist/chunk-j0e7z7hd.js +18053 -0
- package/dist/cli/colors.d.ts +15 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/middleware.d.ts +31 -0
- package/dist/cli/openapi.d.ts +17 -0
- package/dist/cli/router.d.ts +15 -0
- package/dist/cli/routes.d.ts +29 -0
- package/dist/cli/utils.d.ts +74 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +3360 -0
- package/dist/config.d.ts +7 -0
- package/dist/container/container.d.ts +273 -0
- package/dist/container/contextual-binding.d.ts +240 -0
- package/dist/container/decorators.d.ts +141 -0
- package/dist/container/service-provider.d.ts +285 -0
- package/dist/development/hot-reload.d.ts +187 -0
- package/dist/development/index.d.ts +167 -0
- package/dist/development/performance-profiler.d.ts +217 -0
- package/dist/development/route-debugger.d.ts +154 -0
- package/dist/development/route-inspector.d.ts +211 -0
- package/dist/development/typescript-utilities.d.ts +209 -0
- package/dist/docs.d.ts +11 -0
- package/dist/errors/circuit-breaker.d.ts +195 -0
- package/dist/errors/error-handler.d.ts +88 -0
- package/dist/errors/error-reporting.d.ts +144 -0
- package/dist/errors/exceptions.d.ts +184 -0
- package/dist/errors/graceful-degradation.d.ts +154 -0
- package/dist/errors/index.d.ts +9 -0
- package/dist/errors/router-errors.d.ts +110 -0
- package/dist/file-serving/static-files.d.ts +143 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13968 -0
- package/dist/middleware/auth.d.ts +63 -0
- package/dist/middleware/content_security_policy.d.ts +53 -0
- package/dist/middleware/cors.d.ts +5 -0
- package/dist/middleware/csrf.d.ts +7 -0
- package/dist/middleware/ddos_protection.d.ts +39 -0
- package/dist/middleware/file_security.d.ts +26 -0
- package/dist/middleware/file_upload.d.ts +38 -0
- package/dist/middleware/helmet.d.ts +54 -0
- package/dist/middleware/index.d.ts +44 -0
- package/dist/middleware/input_validation.d.ts +45 -0
- package/dist/middleware/json_body.d.ts +4 -0
- package/dist/middleware/performance_alerting.d.ts +87 -0
- package/dist/middleware/performance_dashboard.d.ts +87 -0
- package/dist/middleware/performance_monitor.d.ts +209 -0
- package/dist/middleware/pipeline.d.ts +131 -0
- package/dist/middleware/rate_limit.d.ts +36 -0
- package/dist/middleware/request_id.d.ts +4 -0
- package/dist/middleware/request_signing.d.ts +153 -0
- package/dist/middleware/request_tracer.d.ts +72 -0
- package/dist/middleware/response_cache.d.ts +97 -0
- package/dist/middleware/security.d.ts +69 -0
- package/dist/middleware/security_suite.d.ts +46 -0
- package/dist/middleware/session.d.ts +23 -0
- package/dist/model-binding/index.d.ts +2 -0
- package/dist/model-binding/model-middleware.d.ts +118 -0
- package/dist/model-binding/model-registry.d.ts +164 -0
- package/dist/model-binding.d.ts +185 -0
- package/dist/model-resolver-factory.d.ts +31 -0
- package/dist/observability/correlation.d.ts +187 -0
- package/dist/observability/health-checks.d.ts +186 -0
- package/dist/observability/index.d.ts +60 -0
- package/dist/observability/integration.d.ts +147 -0
- package/dist/observability/metrics.d.ts +184 -0
- package/dist/observability/tracing.d.ts +187 -0
- package/dist/optimization/bun-utilities.d.ts +224 -0
- package/dist/query-builder-integration.d.ts +36 -0
- package/dist/request/context.d.ts +34 -0
- package/dist/request/enhanced-request.d.ts +213 -0
- package/dist/request/macros.d.ts +300 -0
- package/dist/response/macros.d.ts +256 -0
- package/dist/response/response-factory.d.ts +133 -0
- package/dist/router/api-routes.d.ts +5 -0
- package/dist/router/file-based-routing.d.ts +118 -0
- package/dist/router/file-streaming.d.ts +5 -0
- package/dist/router/fluent-router.d.ts +315 -0
- package/dist/router/fluent-routing.d.ts +271 -0
- package/dist/router/group-organization.d.ts +5 -0
- package/dist/router/handler-resolver.d.ts +24 -0
- package/dist/router/http-methods.d.ts +10 -0
- package/dist/router/index.d.ts +114 -0
- package/dist/router/middleware-groups.d.ts +94 -0
- package/dist/router/middleware-integration.d.ts +112 -0
- package/dist/router/middleware.d.ts +5 -0
- package/dist/router/model-binding.d.ts +5 -0
- package/dist/router/optimized-route-matching.d.ts +6 -0
- package/dist/router/route-building.d.ts +5 -0
- package/dist/router/route-compiler.d.ts +153 -0
- package/dist/router/route-matching.d.ts +5 -0
- package/dist/router/route-trie.d.ts +114 -0
- package/dist/router/router.d.ts +317 -0
- package/dist/router/server.d.ts +5 -0
- package/dist/router/validation-integration.d.ts +170 -0
- package/dist/router/view-rendering.d.ts +5 -0
- package/dist/router/websocket.d.ts +5 -0
- package/dist/routing/route-caching.d.ts +129 -0
- package/dist/routing/route-throttling.d.ts +150 -0
- package/dist/routing/subdomain-routing.d.ts +206 -0
- package/dist/session/database-store.d.ts +51 -0
- package/dist/session/file-store.d.ts +22 -0
- package/dist/session/index.d.ts +106 -0
- package/dist/session/memory-store.d.ts +21 -0
- package/dist/session/redis-store.d.ts +31 -0
- package/dist/streaming/index.d.ts +3 -0
- package/dist/streaming/sse-handler.d.ts +138 -0
- package/dist/streaming/stream-handler.d.ts +114 -0
- package/dist/testing/auth-testing.d.ts +156 -0
- package/dist/testing/file-upload-testing.d.ts +190 -0
- package/dist/testing/index.d.ts +10 -0
- package/dist/testing/middleware-testing.d.ts +138 -0
- package/dist/testing/model-binding-testing.d.ts +186 -0
- package/dist/testing/performance-testing.d.ts +235 -0
- package/dist/testing/test-client.d.ts +117 -0
- package/dist/testing/test-request.d.ts +85 -0
- package/dist/testing/test-response.d.ts +90 -0
- package/dist/testing/types.d.ts +207 -0
- package/dist/testing/websocket-testing.d.ts +227 -0
- package/dist/types/controller-types.d.ts +208 -0
- package/dist/types/core.d.ts +543 -0
- package/dist/types/middleware-types.d.ts +227 -0
- package/dist/types/request-response-augmentation.d.ts +261 -0
- package/dist/types/route-inference.d.ts +168 -0
- package/dist/types.d.ts +1794 -0
- package/dist/url.d.ts +56 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/query-preservation.d.ts +48 -0
- package/dist/utils.d.ts +69 -0
- package/dist/validation/validator.d.ts +140 -0
- package/dist/websocket/clustering.d.ts +185 -0
- package/package.json +45 -44
- package/src/auth.ts +469 -0
- package/src/cache/lru-cache.ts +457 -0
- package/src/cache/middleware-memoization.ts +531 -0
- package/src/cache/route-cache-warmer.ts +486 -0
- package/src/cache/sqlite-cache.ts +783 -0
- package/src/cache/streaming-cache.ts +572 -0
- package/src/cli/colors.ts +29 -0
- package/src/cli/index.ts +287 -0
- package/src/cli/middleware.ts +291 -0
- package/src/cli/openapi.ts +407 -0
- package/src/cli/router.ts +188 -0
- package/src/cli/routes.ts +265 -0
- package/src/cli/utils.ts +531 -0
- package/src/cli.ts +5 -0
- package/src/config.ts +322 -0
- package/src/container/container.ts +740 -0
- package/src/container/contextual-binding.ts +603 -0
- package/src/container/decorators.ts +359 -0
- package/src/container/service-provider.ts +596 -0
- package/src/development/hot-reload.ts +673 -0
- package/src/development/index.ts +499 -0
- package/src/development/performance-profiler.ts +717 -0
- package/src/development/route-debugger.ts +527 -0
- package/src/development/route-inspector.ts +749 -0
- package/src/development/typescript-utilities.ts +682 -0
- package/src/docs.ts +397 -0
- package/src/errors/circuit-breaker.ts +732 -0
- package/src/errors/error-handler.ts +569 -0
- package/src/errors/error-reporting.ts +672 -0
- package/src/errors/exceptions.ts +536 -0
- package/src/errors/graceful-degradation.ts +621 -0
- package/src/errors/index.ts +21 -0
- package/src/errors/router-errors.ts +632 -0
- package/src/file-serving/static-files.ts +581 -0
- package/src/index.ts +14 -0
- package/src/middleware/auth.ts +220 -0
- package/src/middleware/content_security_policy.ts +215 -0
- package/src/middleware/cors.ts +74 -0
- package/src/middleware/csrf.ts +108 -0
- package/src/middleware/ddos_protection.ts +255 -0
- package/src/middleware/file_security.ts +191 -0
- package/src/middleware/file_upload.ts +275 -0
- package/src/middleware/helmet.ts +268 -0
- package/src/middleware/index.ts +117 -0
- package/src/middleware/input_validation.ts +449 -0
- package/src/middleware/json_body.ts +37 -0
- package/src/middleware/performance_alerting.ts +538 -0
- package/src/middleware/performance_dashboard.ts +661 -0
- package/src/middleware/performance_monitor.ts +943 -0
- package/src/middleware/pipeline.ts +489 -0
- package/src/middleware/rate_limit.ts +245 -0
- package/src/middleware/request_id.ts +36 -0
- package/src/middleware/request_signing.ts +636 -0
- package/src/middleware/request_tracer.ts +636 -0
- package/src/middleware/response_cache.ts +743 -0
- package/src/middleware/security.ts +482 -0
- package/src/middleware/security_suite.ts +257 -0
- package/src/middleware/session.ts +91 -0
- package/src/model-binding/index.ts +18 -0
- package/src/model-binding/model-middleware.ts +425 -0
- package/src/model-binding/model-registry.ts +550 -0
- package/src/model-binding.ts +370 -0
- package/src/model-resolver-factory.ts +106 -0
- package/src/observability/correlation.ts +691 -0
- package/src/observability/health-checks.ts +729 -0
- package/src/observability/index.ts +184 -0
- package/src/observability/integration.ts +548 -0
- package/src/observability/metrics.ts +753 -0
- package/src/observability/tracing.ts +638 -0
- package/src/optimization/bun-utilities.ts +778 -0
- package/src/query-builder-integration.ts +137 -0
- package/src/request/context.ts +62 -0
- package/src/request/enhanced-request.ts +857 -0
- package/src/request/macros.ts +688 -0
- package/src/response/macros.ts +665 -0
- package/src/response/response-factory.ts +596 -0
- package/src/router/api-routes.ts +243 -0
- package/src/router/file-based-routing.ts +690 -0
- package/src/router/file-streaming.ts +383 -0
- package/src/router/fluent-router.ts +927 -0
- package/src/router/fluent-routing.ts +797 -0
- package/src/router/group-organization.ts +213 -0
- package/src/router/handler-resolver.ts +291 -0
- package/src/router/http-methods.ts +377 -0
- package/src/router/index.ts +187 -0
- package/src/router/middleware-groups.ts +222 -0
- package/src/router/middleware-integration.ts +399 -0
- package/src/router/middleware.ts +231 -0
- package/src/router/model-binding.ts +215 -0
- package/src/router/optimized-route-matching.ts +253 -0
- package/src/router/route-building.ts +221 -0
- package/src/router/route-compiler.ts +702 -0
- package/src/router/route-matching.ts +349 -0
- package/src/router/route-trie.ts +464 -0
- package/src/router/router.ts +1604 -0
- package/src/router/server.ts +462 -0
- package/src/router/validation-integration.ts +443 -0
- package/src/router/view-rendering.ts +233 -0
- package/src/router/websocket.ts +100 -0
- package/src/routing/route-caching.ts +402 -0
- package/src/routing/route-throttling.ts +469 -0
- package/src/routing/subdomain-routing.ts +492 -0
- package/src/session/database-store.ts +109 -0
- package/src/session/file-store.ts +148 -0
- package/src/session/index.ts +244 -0
- package/src/session/memory-store.ts +88 -0
- package/src/session/redis-store.ts +93 -0
- package/src/streaming/index.ts +17 -0
- package/src/streaming/sse-handler.ts +482 -0
- package/src/streaming/stream-handler.ts +552 -0
- package/src/testing/auth-testing.ts +446 -0
- package/src/testing/file-upload-testing.ts +543 -0
- package/src/testing/index.ts +10 -0
- package/src/testing/middleware-testing.ts +322 -0
- package/src/testing/model-binding-testing.ts +645 -0
- package/src/testing/performance-testing.ts +738 -0
- package/src/testing/test-client.ts +310 -0
- package/src/testing/test-request.ts +315 -0
- package/src/testing/test-response.ts +332 -0
- package/src/testing/types.ts +224 -0
- package/src/testing/websocket-testing.ts +590 -0
- package/src/types/controller-types.ts +385 -0
- package/src/types/core.ts +683 -0
- package/src/types/middleware-types.ts +419 -0
- package/src/types/request-response-augmentation.ts +489 -0
- package/src/types/route-inference.ts +357 -0
- package/src/types.ts +2067 -0
- package/src/url.ts +126 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/query-preservation.ts +201 -0
- package/src/utils.ts +327 -0
- package/src/validation/validator.ts +685 -0
- package/src/websocket/clustering.ts +762 -0
- package/CHANGELOG.md +0 -770
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EnhancedRequest,
|
|
3
|
+
ExtractRouteParams,
|
|
4
|
+
SSEConfig,
|
|
5
|
+
SSEEvent,
|
|
6
|
+
SSERouteHandler,
|
|
7
|
+
} from '../types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Server-Sent Events handler
|
|
11
|
+
*/
|
|
12
|
+
export class SSEHandler {
|
|
13
|
+
private config: Required<SSEConfig>
|
|
14
|
+
private controller?: ReadableStreamDefaultController<Uint8Array>
|
|
15
|
+
private encoder = new TextEncoder()
|
|
16
|
+
private isConnected = false
|
|
17
|
+
private heartbeatInterval?: ReturnType<typeof setInterval>
|
|
18
|
+
|
|
19
|
+
constructor(config: SSEConfig = {}) {
|
|
20
|
+
this.config = {
|
|
21
|
+
heartbeatInterval: config.heartbeatInterval ?? 30000,
|
|
22
|
+
enableHeartbeat: config.enableHeartbeat ?? true,
|
|
23
|
+
maxConnections: config.maxConnections ?? 1000,
|
|
24
|
+
connectionTimeout: config.connectionTimeout ?? 300000,
|
|
25
|
+
retryDelay: config.retryDelay ?? 3000,
|
|
26
|
+
retryInterval: config.retryInterval ?? 3000,
|
|
27
|
+
keepAlive: config.keepAlive ?? 30000,
|
|
28
|
+
compression: config.compression ?? 'none',
|
|
29
|
+
headers: config.headers ?? {},
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create SSE response stream
|
|
35
|
+
*/
|
|
36
|
+
createStream(): Response {
|
|
37
|
+
const stream = new ReadableStream({
|
|
38
|
+
start: (controller) => {
|
|
39
|
+
this.controller = controller
|
|
40
|
+
this.isConnected = true
|
|
41
|
+
this.setupHeartbeat()
|
|
42
|
+
this.sendRetryDelay()
|
|
43
|
+
},
|
|
44
|
+
cancel: () => {
|
|
45
|
+
this.disconnect()
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const headers = new Headers({
|
|
50
|
+
'Content-Type': 'text/event-stream',
|
|
51
|
+
'Cache-Control': 'no-cache',
|
|
52
|
+
'Connection': 'keep-alive',
|
|
53
|
+
'Access-Control-Allow-Origin': '*',
|
|
54
|
+
'Access-Control-Allow-Headers': 'Cache-Control',
|
|
55
|
+
...this.config.headers,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
return new Response(stream, {
|
|
59
|
+
status: 200,
|
|
60
|
+
headers,
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Send an SSE event
|
|
66
|
+
*/
|
|
67
|
+
send(event: SSEEvent): boolean {
|
|
68
|
+
if (!this.isConnected || !this.controller) {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const eventData = this.formatEvent(event)
|
|
74
|
+
this.controller.enqueue(this.encoder.encode(eventData))
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('Failed to send SSE event:', error)
|
|
79
|
+
this.disconnect()
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Send multiple events
|
|
86
|
+
*/
|
|
87
|
+
sendBatch(events: SSEEvent[]): number {
|
|
88
|
+
let sentCount = 0
|
|
89
|
+
for (const event of events) {
|
|
90
|
+
if (this.send(event)) {
|
|
91
|
+
sentCount++
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return sentCount
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Send a simple message
|
|
102
|
+
*/
|
|
103
|
+
message(data: any, id?: string): boolean {
|
|
104
|
+
return this.send({
|
|
105
|
+
data,
|
|
106
|
+
id,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Send an event with type
|
|
112
|
+
*/
|
|
113
|
+
event(type: string, data: any, id?: string): boolean {
|
|
114
|
+
return this.send({
|
|
115
|
+
event: type,
|
|
116
|
+
data,
|
|
117
|
+
id,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Send a comment (for debugging)
|
|
123
|
+
*/
|
|
124
|
+
comment(text: string): boolean {
|
|
125
|
+
if (!this.isConnected || !this.controller) {
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const commentData = `: ${text}\n\n`
|
|
131
|
+
this.controller.enqueue(this.encoder.encode(commentData))
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.error('Failed to send SSE comment:', error)
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Close the connection
|
|
142
|
+
*/
|
|
143
|
+
close(): void {
|
|
144
|
+
this.disconnect()
|
|
145
|
+
if (this.controller) {
|
|
146
|
+
try {
|
|
147
|
+
this.controller.close()
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error('Error closing SSE connection:', error)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if connection is active
|
|
157
|
+
*/
|
|
158
|
+
get connected(): boolean {
|
|
159
|
+
return this.isConnected
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Format SSE event according to specification
|
|
164
|
+
*/
|
|
165
|
+
private formatEvent(event: SSEEvent): string {
|
|
166
|
+
let formatted = ''
|
|
167
|
+
|
|
168
|
+
if (event.id !== undefined) {
|
|
169
|
+
formatted += `id: ${event.id}\n`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (event.event) {
|
|
173
|
+
formatted += `event: ${event.event}\n`
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (event.retry !== undefined) {
|
|
177
|
+
formatted += `retry: ${event.retry}\n`
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (event.data !== undefined) {
|
|
181
|
+
const dataStr = typeof event.data === 'string'
|
|
182
|
+
? event.data
|
|
183
|
+
: JSON.stringify(event.data)
|
|
184
|
+
|
|
185
|
+
// Handle multi-line data
|
|
186
|
+
const lines = dataStr.split('\n')
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
formatted += `data: ${line}\n`
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
formatted += '\n'
|
|
193
|
+
return formatted
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Setup heartbeat to keep connection alive
|
|
198
|
+
*/
|
|
199
|
+
private setupHeartbeat(): void {
|
|
200
|
+
if (!this.config.enableHeartbeat)
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
this.heartbeatInterval = setInterval(() => {
|
|
204
|
+
if (this.isConnected) {
|
|
205
|
+
this.comment('heartbeat')
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
this.clearHeartbeat()
|
|
209
|
+
}
|
|
210
|
+
}, this.config.heartbeatInterval)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Clear heartbeat interval
|
|
215
|
+
*/
|
|
216
|
+
private clearHeartbeat(): void {
|
|
217
|
+
if (this.heartbeatInterval) {
|
|
218
|
+
clearInterval(this.heartbeatInterval)
|
|
219
|
+
this.heartbeatInterval = undefined
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Send retry delay instruction
|
|
225
|
+
*/
|
|
226
|
+
private sendRetryDelay(): void {
|
|
227
|
+
if (this.config.retryDelay > 0) {
|
|
228
|
+
this.send({
|
|
229
|
+
data: '',
|
|
230
|
+
retry: this.config.retryDelay,
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Disconnect and cleanup
|
|
237
|
+
*/
|
|
238
|
+
private disconnect(): void {
|
|
239
|
+
this.isConnected = false
|
|
240
|
+
this.clearHeartbeat()
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* SSE connection manager
|
|
246
|
+
*/
|
|
247
|
+
export class SSEConnectionManager {
|
|
248
|
+
private connections = new Map<string, SSEHandler>()
|
|
249
|
+
private connectionCounts = new Map<string, number>()
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Create or get SSE connection
|
|
253
|
+
*/
|
|
254
|
+
createConnection(
|
|
255
|
+
connectionId: string,
|
|
256
|
+
config: SSEConfig = {},
|
|
257
|
+
): SSEHandler {
|
|
258
|
+
// Check max connections per ID
|
|
259
|
+
const currentCount = this.connectionCounts.get(connectionId) || 0
|
|
260
|
+
if (config.maxConnections && currentCount >= config.maxConnections) {
|
|
261
|
+
throw new Error(`Maximum connections exceeded for ${connectionId}`)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const handler = new SSEHandler(config)
|
|
265
|
+
this.connections.set(connectionId, handler)
|
|
266
|
+
this.connectionCounts.set(connectionId, currentCount + 1)
|
|
267
|
+
|
|
268
|
+
return handler
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get existing connection
|
|
273
|
+
*/
|
|
274
|
+
getConnection(connectionId: string): SSEHandler | undefined {
|
|
275
|
+
return this.connections.get(connectionId)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Close connection
|
|
280
|
+
*/
|
|
281
|
+
closeConnection(connectionId: string): boolean {
|
|
282
|
+
const handler = this.connections.get(connectionId)
|
|
283
|
+
if (handler) {
|
|
284
|
+
handler.close()
|
|
285
|
+
this.connections.delete(connectionId)
|
|
286
|
+
|
|
287
|
+
const currentCount = this.connectionCounts.get(connectionId) || 0
|
|
288
|
+
if (currentCount <= 1) {
|
|
289
|
+
this.connectionCounts.delete(connectionId)
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
this.connectionCounts.set(connectionId, currentCount - 1)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return true
|
|
296
|
+
}
|
|
297
|
+
return false
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Broadcast to all connections
|
|
302
|
+
*/
|
|
303
|
+
broadcast(event: SSEEvent): number {
|
|
304
|
+
let sentCount = 0
|
|
305
|
+
for (const handler of this.connections.values()) {
|
|
306
|
+
if (handler.send(event)) {
|
|
307
|
+
sentCount++
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return sentCount
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Broadcast to specific connections
|
|
315
|
+
*/
|
|
316
|
+
broadcastToConnections(connectionIds: string[], event: SSEEvent): number {
|
|
317
|
+
let sentCount = 0
|
|
318
|
+
for (const id of connectionIds) {
|
|
319
|
+
const handler = this.connections.get(id)
|
|
320
|
+
if (handler && handler.send(event)) {
|
|
321
|
+
sentCount++
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return sentCount
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get connection statistics
|
|
329
|
+
*/
|
|
330
|
+
getStats(): { totalConnections: number, connectionsByUser: Record<string, number>, activeConnections: string[] } {
|
|
331
|
+
return {
|
|
332
|
+
totalConnections: this.connections.size,
|
|
333
|
+
connectionsByUser: Object.fromEntries(this.connectionCounts),
|
|
334
|
+
activeConnections: Array.from(this.connections.keys()),
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Cleanup dead connections
|
|
340
|
+
*/
|
|
341
|
+
cleanup(): void {
|
|
342
|
+
for (const [id, handler] of this.connections.entries()) {
|
|
343
|
+
if (!handler.connected) {
|
|
344
|
+
this.closeConnection(id)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Global SSE connection manager instance
|
|
352
|
+
*/
|
|
353
|
+
export const sseManager: SSEConnectionManager = new SSEConnectionManager()
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* SSE middleware for route handlers
|
|
357
|
+
*/
|
|
358
|
+
export function createSSEMiddleware(config: SSEConfig = {}) {
|
|
359
|
+
return async (req: EnhancedRequest, next: () => Promise<Response>): Promise<Response> => {
|
|
360
|
+
// Check if client accepts SSE
|
|
361
|
+
const accept = req.headers.get('accept')
|
|
362
|
+
if (!accept?.includes('text/event-stream')) {
|
|
363
|
+
return new Response('SSE not supported', { status: 406 })
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Add SSE helper to request
|
|
367
|
+
const handler = new SSEHandler(config)
|
|
368
|
+
req.sse = {
|
|
369
|
+
id: Math.random().toString(36).substring(7),
|
|
370
|
+
eventSource: handler,
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return next()
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* SSE utility functions
|
|
379
|
+
*/
|
|
380
|
+
export const SSEUtils = {
|
|
381
|
+
/**
|
|
382
|
+
* Create a simple SSE route handler
|
|
383
|
+
*/
|
|
384
|
+
createHandler<TPath extends string>(
|
|
385
|
+
handler: SSERouteHandler<TPath>,
|
|
386
|
+
): (req: EnhancedRequest & { params: ExtractRouteParams<TPath> }) => Promise<Response> {
|
|
387
|
+
return async (req) => {
|
|
388
|
+
const sse = new SSEHandler()
|
|
389
|
+
const response = sse.createStream()
|
|
390
|
+
|
|
391
|
+
// Call the handler in the background
|
|
392
|
+
const result = handler(req)
|
|
393
|
+
|
|
394
|
+
// Consume the generator
|
|
395
|
+
;(async () => {
|
|
396
|
+
try {
|
|
397
|
+
// Check if result is a function (SSEGenerator) or already an AsyncGenerator
|
|
398
|
+
const generator = typeof result === 'function' ? result() : result
|
|
399
|
+
for await (const event of generator) {
|
|
400
|
+
if (!sse.send(event)) {
|
|
401
|
+
break
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
console.error('SSE handler error:', error)
|
|
407
|
+
}
|
|
408
|
+
finally {
|
|
409
|
+
sse.close()
|
|
410
|
+
}
|
|
411
|
+
})()
|
|
412
|
+
|
|
413
|
+
return response
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Create SSE event from object
|
|
419
|
+
*/
|
|
420
|
+
createEvent(data: any, options: Partial<SSEEvent> = {}): SSEEvent {
|
|
421
|
+
return {
|
|
422
|
+
data,
|
|
423
|
+
...options,
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Validate SSE event
|
|
429
|
+
*/
|
|
430
|
+
validateEvent(event: SSEEvent): boolean {
|
|
431
|
+
return event.data !== undefined || event.event !== undefined
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Create periodic SSE sender
|
|
436
|
+
*/
|
|
437
|
+
createPeriodicSender(
|
|
438
|
+
sse: SSEHandler,
|
|
439
|
+
dataFn: () => any | Promise<any>,
|
|
440
|
+
intervalMs: number,
|
|
441
|
+
): () => void {
|
|
442
|
+
const interval: ReturnType<typeof setInterval> = setInterval(async () => {
|
|
443
|
+
try {
|
|
444
|
+
const data = await dataFn()
|
|
445
|
+
if (!sse.send({ data })) {
|
|
446
|
+
clearInterval(interval)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
console.error('Periodic SSE sender error:', error)
|
|
451
|
+
sse.close()
|
|
452
|
+
clearInterval(interval)
|
|
453
|
+
}
|
|
454
|
+
}, intervalMs)
|
|
455
|
+
|
|
456
|
+
return () => clearInterval(interval)
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Create SSE from async iterable
|
|
461
|
+
*/
|
|
462
|
+
async fromAsyncIterable<T>(
|
|
463
|
+
sse: SSEHandler,
|
|
464
|
+
iterable: AsyncIterable<T>,
|
|
465
|
+
transform?: (item: T) => SSEEvent,
|
|
466
|
+
): Promise<void> {
|
|
467
|
+
try {
|
|
468
|
+
for await (const item of iterable) {
|
|
469
|
+
const event = transform ? transform(item) : { data: item }
|
|
470
|
+
if (!sse.send(event)) {
|
|
471
|
+
break
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
console.error('SSE from async iterable error:', error)
|
|
477
|
+
}
|
|
478
|
+
finally {
|
|
479
|
+
sse.close()
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
}
|