@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.
- package/dist/cli.js +1 -1
- package/dist/container/service-provider.d.ts +3 -3
- package/dist/development/performance-profiler.d.ts +3 -3
- package/dist/development/route-debugger.d.ts +1 -1
- package/dist/development/route-inspector.d.ts +2 -2
- package/dist/observability/integration.d.ts +1 -1
- package/dist/response/macros.d.ts +1 -1
- package/dist/router/index.d.ts +7 -7
- package/dist/router/router.d.ts +0 -32
- package/dist/testing/performance-testing.d.ts +2 -2
- package/dist/testing/test-client.d.ts +1 -1
- package/dist/testing/websocket-testing.d.ts +1 -1
- package/package.json +2 -3
- package/src/auth.ts +0 -489
- package/src/cache/lru-cache.ts +0 -457
- package/src/cache/middleware-memoization.ts +0 -531
- package/src/cache/route-cache-warmer.ts +0 -486
- package/src/cache/sqlite-cache.ts +0 -783
- package/src/cache/streaming-cache.ts +0 -572
- package/src/cli/colors.ts +0 -29
- package/src/cli/index.ts +0 -287
- package/src/cli/middleware.ts +0 -291
- package/src/cli/openapi.ts +0 -407
- package/src/cli/router.ts +0 -188
- package/src/cli/routes.ts +0 -265
- package/src/cli/utils.ts +0 -531
- package/src/cli.ts +0 -5
- package/src/config.ts +0 -322
- package/src/container/container.ts +0 -786
- package/src/container/contextual-binding.ts +0 -606
- package/src/container/decorators.ts +0 -368
- package/src/container/index.ts +0 -49
- package/src/container/service-provider.ts +0 -596
- package/src/development/hot-reload.ts +0 -673
- package/src/development/index.ts +0 -499
- package/src/development/performance-profiler.ts +0 -717
- package/src/development/route-debugger.ts +0 -527
- package/src/development/route-inspector.ts +0 -749
- package/src/development/typescript-utilities.ts +0 -682
- package/src/docs.ts +0 -397
- package/src/errors/circuit-breaker.ts +0 -733
- package/src/errors/error-handler.ts +0 -569
- package/src/errors/error-reporting.ts +0 -672
- package/src/errors/exceptions.ts +0 -536
- package/src/errors/graceful-degradation.ts +0 -623
- package/src/errors/index.ts +0 -21
- package/src/errors/router-errors.ts +0 -632
- package/src/file-serving/static-files.ts +0 -718
- package/src/index.ts +0 -66
- package/src/middleware/auth.ts +0 -220
- package/src/middleware/content_security_policy.ts +0 -215
- package/src/middleware/cors.ts +0 -142
- package/src/middleware/csrf.ts +0 -166
- package/src/middleware/ddos_protection.ts +0 -279
- package/src/middleware/file_security.ts +0 -191
- package/src/middleware/file_upload.ts +0 -275
- package/src/middleware/helmet.ts +0 -268
- package/src/middleware/index.ts +0 -117
- package/src/middleware/input_validation.ts +0 -453
- package/src/middleware/json_body.ts +0 -37
- package/src/middleware/performance_alerting.ts +0 -540
- package/src/middleware/performance_dashboard.ts +0 -661
- package/src/middleware/performance_monitor.ts +0 -945
- package/src/middleware/pipeline.ts +0 -489
- package/src/middleware/rate_limit.ts +0 -251
- package/src/middleware/request_id.ts +0 -36
- package/src/middleware/request_signing.ts +0 -638
- package/src/middleware/request_tracer.ts +0 -653
- package/src/middleware/response_cache.ts +0 -778
- package/src/middleware/security.ts +0 -482
- package/src/middleware/security_suite.ts +0 -257
- package/src/middleware/session.ts +0 -91
- package/src/model-binding/index.ts +0 -18
- package/src/model-binding/model-middleware.ts +0 -425
- package/src/model-binding/model-registry.ts +0 -550
- package/src/model-binding.ts +0 -370
- package/src/model-resolver-factory.ts +0 -106
- package/src/observability/correlation.ts +0 -691
- package/src/observability/health-checks.ts +0 -729
- package/src/observability/index.ts +0 -184
- package/src/observability/integration.ts +0 -548
- package/src/observability/metrics.ts +0 -753
- package/src/observability/tracing.ts +0 -638
- package/src/optimization/bun-utilities.ts +0 -778
- package/src/query-builder-integration.ts +0 -137
- package/src/request/context.ts +0 -109
- package/src/request/enhanced-request.ts +0 -876
- package/src/request/macros.ts +0 -822
- package/src/response/macros.ts +0 -665
- package/src/response/response-factory.ts +0 -596
- package/src/router/api-routes.ts +0 -243
- package/src/router/file-based-routing.ts +0 -697
- package/src/router/file-streaming.ts +0 -383
- package/src/router/fluent-routing.ts +0 -866
- package/src/router/group-organization.ts +0 -229
- package/src/router/handler-resolver.ts +0 -327
- package/src/router/http-methods.ts +0 -407
- package/src/router/index.ts +0 -193
- package/src/router/middleware-groups.ts +0 -222
- package/src/router/middleware-integration.ts +0 -399
- package/src/router/middleware.ts +0 -234
- package/src/router/model-binding.ts +0 -215
- package/src/router/optimized-route-matching.ts +0 -302
- package/src/router/route-building.ts +0 -221
- package/src/router/route-compiler.ts +0 -691
- package/src/router/route-matching.ts +0 -368
- package/src/router/route-trie.ts +0 -450
- package/src/router/router.ts +0 -1616
- package/src/router/server.ts +0 -684
- package/src/router/validation-integration.ts +0 -449
- package/src/router/view-rendering.ts +0 -233
- package/src/router/websocket.ts +0 -100
- package/src/routing/route-caching.ts +0 -402
- package/src/routing/route-throttling.ts +0 -469
- package/src/routing/subdomain-routing.ts +0 -492
- package/src/session/database-store.ts +0 -109
- package/src/session/file-store.ts +0 -148
- package/src/session/index.ts +0 -244
- package/src/session/memory-store.ts +0 -88
- package/src/session/redis-store.ts +0 -93
- package/src/streaming/index.ts +0 -17
- package/src/streaming/sse-handler.ts +0 -482
- package/src/streaming/stream-handler.ts +0 -552
- package/src/testing/auth-testing.ts +0 -448
- package/src/testing/file-upload-testing.ts +0 -543
- package/src/testing/index.ts +0 -10
- package/src/testing/middleware-testing.ts +0 -322
- package/src/testing/model-binding-testing.ts +0 -645
- package/src/testing/performance-testing.ts +0 -738
- package/src/testing/test-client.ts +0 -310
- package/src/testing/test-request.ts +0 -315
- package/src/testing/test-response.ts +0 -332
- package/src/testing/types.ts +0 -224
- package/src/testing/websocket-testing.ts +0 -590
- package/src/types/controller-types.ts +0 -385
- package/src/types/core.ts +0 -683
- package/src/types/middleware-types.ts +0 -422
- package/src/types/request-response-augmentation.ts +0 -489
- package/src/types/route-inference.ts +0 -372
- package/src/types.ts +0 -2123
- package/src/url.ts +0 -131
- package/src/utils/index.ts +0 -1
- package/src/utils/query-preservation.ts +0 -201
- package/src/utils.ts +0 -395
- package/src/validation/validator.ts +0 -688
- package/src/websocket/clustering.ts +0 -769
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
import type { EnhancedRequest, NextFunction } from '../types'
|
|
2
|
-
import type { PerformanceMetrics } from './performance_monitor'
|
|
3
|
-
|
|
4
|
-
export interface AlertRule {
|
|
5
|
-
id: string
|
|
6
|
-
name: string
|
|
7
|
-
description?: string
|
|
8
|
-
enabled: boolean
|
|
9
|
-
metric: 'responseTime' | 'errorRate' | 'memoryUsage' | 'cpuUsage' | 'requestRate' | 'custom'
|
|
10
|
-
condition: 'gt' | 'lt' | 'eq' | 'gte' | 'lte'
|
|
11
|
-
threshold: number
|
|
12
|
-
duration?: number // Time window in ms to evaluate the condition
|
|
13
|
-
severity: 'info' | 'warning' | 'critical'
|
|
14
|
-
cooldown?: number // Minimum time between alerts in ms
|
|
15
|
-
customEvaluator?: (metrics: PerformanceMetrics[]) => boolean
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface AlertNotification {
|
|
19
|
-
id: string
|
|
20
|
-
ruleId: string
|
|
21
|
-
ruleName: string
|
|
22
|
-
severity: 'info' | 'warning' | 'critical'
|
|
23
|
-
message: string
|
|
24
|
-
timestamp: number
|
|
25
|
-
resolved: boolean
|
|
26
|
-
resolvedAt?: number
|
|
27
|
-
metadata: Record<string, any>
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface AlertChannel {
|
|
31
|
-
type: 'webhook' | 'slack' | 'email' | 'console' | 'custom'
|
|
32
|
-
enabled: boolean
|
|
33
|
-
config: {
|
|
34
|
-
url?: string
|
|
35
|
-
token?: string
|
|
36
|
-
channel?: string
|
|
37
|
-
email?: string
|
|
38
|
-
customHandler?: (notification: AlertNotification) => Promise<void>
|
|
39
|
-
}
|
|
40
|
-
filters?: {
|
|
41
|
-
severities?: Array<'info' | 'warning' | 'critical'>
|
|
42
|
-
rules?: string[]
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface AlertingOptions {
|
|
47
|
-
enabled?: boolean
|
|
48
|
-
rules?: AlertRule[]
|
|
49
|
-
channels?: AlertChannel[]
|
|
50
|
-
defaultCooldown?: number
|
|
51
|
-
maxAlerts?: number
|
|
52
|
-
autoResolve?: boolean
|
|
53
|
-
autoResolveTimeout?: number
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default class PerformanceAlerting {
|
|
57
|
-
private options: AlertingOptions
|
|
58
|
-
private rules: Map<string, AlertRule> = new Map()
|
|
59
|
-
private channels: AlertChannel[] = []
|
|
60
|
-
private notifications: AlertNotification[] = []
|
|
61
|
-
private lastAlertTimes: Map<string, number> = new Map()
|
|
62
|
-
private metricsBuffer: PerformanceMetrics[] = []
|
|
63
|
-
private checkInterval?: ReturnType<typeof setInterval>
|
|
64
|
-
|
|
65
|
-
constructor(options: AlertingOptions = {}) {
|
|
66
|
-
this.options = {
|
|
67
|
-
enabled: options.enabled ?? true,
|
|
68
|
-
defaultCooldown: options.defaultCooldown ?? 300000, // 5 minutes
|
|
69
|
-
maxAlerts: options.maxAlerts ?? 1000,
|
|
70
|
-
autoResolve: options.autoResolve ?? true,
|
|
71
|
-
autoResolveTimeout: options.autoResolveTimeout ?? 600000, // 10 minutes
|
|
72
|
-
...options,
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Initialize rules
|
|
76
|
-
if (options.rules) {
|
|
77
|
-
for (const rule of options.rules) {
|
|
78
|
-
this.rules.set(rule.id, rule)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Initialize channels
|
|
83
|
-
this.channels = options.channels || []
|
|
84
|
-
|
|
85
|
-
// Add default rules if none provided
|
|
86
|
-
if (this.rules.size === 0) {
|
|
87
|
-
this.addDefaultRules()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Add default console channel if none provided
|
|
91
|
-
if (this.channels.length === 0) {
|
|
92
|
-
this.channels.push({
|
|
93
|
-
type: 'console',
|
|
94
|
-
enabled: true,
|
|
95
|
-
config: {},
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (this.options.enabled) {
|
|
100
|
-
this.startAlertingEngine()
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private addDefaultRules(): void {
|
|
105
|
-
const defaultRules: AlertRule[] = [
|
|
106
|
-
{
|
|
107
|
-
id: 'high-response-time',
|
|
108
|
-
name: 'High Response Time',
|
|
109
|
-
description: 'Alert when average response time exceeds threshold',
|
|
110
|
-
enabled: true,
|
|
111
|
-
metric: 'responseTime',
|
|
112
|
-
condition: 'gt',
|
|
113
|
-
threshold: 1000, // 1 second
|
|
114
|
-
duration: 60000, // 1 minute
|
|
115
|
-
severity: 'warning',
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
id: 'critical-response-time',
|
|
119
|
-
name: 'Critical Response Time',
|
|
120
|
-
description: 'Alert when response time is critically high',
|
|
121
|
-
enabled: true,
|
|
122
|
-
metric: 'responseTime',
|
|
123
|
-
condition: 'gt',
|
|
124
|
-
threshold: 5000, // 5 seconds
|
|
125
|
-
duration: 30000, // 30 seconds
|
|
126
|
-
severity: 'critical',
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
id: 'high-error-rate',
|
|
130
|
-
name: 'High Error Rate',
|
|
131
|
-
description: 'Alert when error rate exceeds threshold',
|
|
132
|
-
enabled: true,
|
|
133
|
-
metric: 'errorRate',
|
|
134
|
-
condition: 'gt',
|
|
135
|
-
threshold: 0.05, // 5%
|
|
136
|
-
duration: 120000, // 2 minutes
|
|
137
|
-
severity: 'warning',
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
id: 'critical-error-rate',
|
|
141
|
-
name: 'Critical Error Rate',
|
|
142
|
-
description: 'Alert when error rate is critically high',
|
|
143
|
-
enabled: true,
|
|
144
|
-
metric: 'errorRate',
|
|
145
|
-
condition: 'gt',
|
|
146
|
-
threshold: 0.1, // 10%
|
|
147
|
-
duration: 60000, // 1 minute
|
|
148
|
-
severity: 'critical',
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
id: 'high-memory-usage',
|
|
152
|
-
name: 'High Memory Usage',
|
|
153
|
-
description: 'Alert when memory usage exceeds threshold',
|
|
154
|
-
enabled: true,
|
|
155
|
-
metric: 'memoryUsage',
|
|
156
|
-
condition: 'gt',
|
|
157
|
-
threshold: 512 * 1024 * 1024, // 512MB
|
|
158
|
-
duration: 300000, // 5 minutes
|
|
159
|
-
severity: 'warning',
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
id: 'low-request-rate',
|
|
163
|
-
name: 'Low Request Rate',
|
|
164
|
-
description: 'Alert when request rate drops significantly',
|
|
165
|
-
enabled: false,
|
|
166
|
-
metric: 'requestRate',
|
|
167
|
-
condition: 'lt',
|
|
168
|
-
threshold: 1, // 1 request per second
|
|
169
|
-
duration: 300000, // 5 minutes
|
|
170
|
-
severity: 'info',
|
|
171
|
-
},
|
|
172
|
-
]
|
|
173
|
-
|
|
174
|
-
for (const rule of defaultRules) {
|
|
175
|
-
this.rules.set(rule.id, rule)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private startAlertingEngine(): void {
|
|
180
|
-
this.checkInterval = setInterval(() => {
|
|
181
|
-
this.evaluateRules()
|
|
182
|
-
this.cleanupOldNotifications()
|
|
183
|
-
this.autoResolveAlerts()
|
|
184
|
-
}, 10000) // Check every 10 seconds
|
|
185
|
-
// Background checks must not keep the process alive
|
|
186
|
-
this.checkInterval.unref?.()
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
private evaluateRules(): void {
|
|
190
|
-
const now = Date.now()
|
|
191
|
-
|
|
192
|
-
for (const rule of this.rules.values()) {
|
|
193
|
-
if (!rule.enabled)
|
|
194
|
-
continue
|
|
195
|
-
|
|
196
|
-
// Check cooldown
|
|
197
|
-
const lastAlert = this.lastAlertTimes.get(rule.id)
|
|
198
|
-
const cooldown = rule.cooldown || this.options.defaultCooldown || 300000
|
|
199
|
-
if (lastAlert && now - lastAlert < cooldown)
|
|
200
|
-
continue
|
|
201
|
-
|
|
202
|
-
// Get relevant metrics for evaluation
|
|
203
|
-
const duration = rule.duration || 60000
|
|
204
|
-
const relevantMetrics = this.metricsBuffer.filter(
|
|
205
|
-
m => now - m.timestamp <= duration,
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
if (relevantMetrics.length === 0)
|
|
209
|
-
continue
|
|
210
|
-
|
|
211
|
-
let shouldAlert = false
|
|
212
|
-
let alertValue: any
|
|
213
|
-
|
|
214
|
-
if (rule.metric === 'custom' && rule.customEvaluator) {
|
|
215
|
-
shouldAlert = rule.customEvaluator(relevantMetrics)
|
|
216
|
-
alertValue = 'custom condition'
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
const values = this.extractMetricValues(relevantMetrics, rule.metric)
|
|
220
|
-
if (values.length === 0)
|
|
221
|
-
continue
|
|
222
|
-
|
|
223
|
-
const avgValue = values.reduce((sum, val) => sum + val, 0) / values.length
|
|
224
|
-
alertValue = avgValue
|
|
225
|
-
|
|
226
|
-
shouldAlert = this.evaluateCondition(avgValue, rule.condition, rule.threshold)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (shouldAlert) {
|
|
230
|
-
this.triggerAlert(rule, alertValue, relevantMetrics)
|
|
231
|
-
this.lastAlertTimes.set(rule.id, now)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
private extractMetricValues(metrics: PerformanceMetrics[], metricType: string): number[] {
|
|
237
|
-
switch (metricType) {
|
|
238
|
-
case 'responseTime':
|
|
239
|
-
return metrics.map(m => m.responseTime)
|
|
240
|
-
case 'errorRate':
|
|
241
|
-
return metrics.map(m => m.statusCode >= 400 ? 1 : 0)
|
|
242
|
-
case 'memoryUsage':
|
|
243
|
-
return metrics.map(m => m.memoryUsage.heapUsed)
|
|
244
|
-
case 'cpuUsage':
|
|
245
|
-
return metrics.map(m => m.cpuUsage.user + m.cpuUsage.system)
|
|
246
|
-
case 'requestRate': {
|
|
247
|
-
// Calculate requests per second over the time window
|
|
248
|
-
if (metrics.length < 2)
|
|
249
|
-
return []
|
|
250
|
-
const timeSpan = (metrics[metrics.length - 1].timestamp - metrics[0].timestamp) / 1000
|
|
251
|
-
return [metrics.length / Math.max(timeSpan, 1)]
|
|
252
|
-
}
|
|
253
|
-
default:
|
|
254
|
-
return []
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private evaluateCondition(value: number, condition: string, threshold: number): boolean {
|
|
259
|
-
switch (condition) {
|
|
260
|
-
case 'gt': return value > threshold
|
|
261
|
-
case 'gte': return value >= threshold
|
|
262
|
-
case 'lt': return value < threshold
|
|
263
|
-
case 'lte': return value <= threshold
|
|
264
|
-
case 'eq': return Math.abs(value - threshold) < 0.001
|
|
265
|
-
default: return false
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private async triggerAlert(rule: AlertRule, value: any, metrics: PerformanceMetrics[]): Promise<void> {
|
|
270
|
-
const notification: AlertNotification = {
|
|
271
|
-
id: this.generateId(),
|
|
272
|
-
ruleId: rule.id,
|
|
273
|
-
ruleName: rule.name,
|
|
274
|
-
severity: rule.severity,
|
|
275
|
-
message: this.formatAlertMessage(rule, value, metrics),
|
|
276
|
-
timestamp: Date.now(),
|
|
277
|
-
resolved: false,
|
|
278
|
-
metadata: {
|
|
279
|
-
rule,
|
|
280
|
-
value,
|
|
281
|
-
threshold: rule.threshold,
|
|
282
|
-
condition: rule.condition,
|
|
283
|
-
metricsCount: metrics.length,
|
|
284
|
-
},
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
this.notifications.push(notification)
|
|
288
|
-
|
|
289
|
-
// Send to channels
|
|
290
|
-
for (const channel of this.channels) {
|
|
291
|
-
if (!channel.enabled)
|
|
292
|
-
continue
|
|
293
|
-
if (channel.filters?.severities && !channel.filters.severities.includes(rule.severity))
|
|
294
|
-
continue
|
|
295
|
-
if (channel.filters?.rules && !channel.filters.rules.includes(rule.id))
|
|
296
|
-
continue
|
|
297
|
-
|
|
298
|
-
try {
|
|
299
|
-
await this.sendNotification(channel, notification)
|
|
300
|
-
}
|
|
301
|
-
catch (error) {
|
|
302
|
-
console.error(`Failed to send alert to ${channel.type}:`, error)
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Cleanup old notifications
|
|
307
|
-
if (this.notifications.length > (this.options.maxAlerts || 1000)) {
|
|
308
|
-
this.notifications = this.notifications.slice(-(this.options.maxAlerts || 1000))
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
private formatAlertMessage(rule: AlertRule, value: any, metrics: PerformanceMetrics[]): string {
|
|
313
|
-
const formatValue = (val: number, metric: string): string => {
|
|
314
|
-
switch (metric) {
|
|
315
|
-
case 'responseTime':
|
|
316
|
-
return `${Math.round(val)}ms`
|
|
317
|
-
case 'errorRate':
|
|
318
|
-
return `${Math.round(val * 100)}%`
|
|
319
|
-
case 'memoryUsage':
|
|
320
|
-
return `${Math.round(val / 1024 / 1024)}MB`
|
|
321
|
-
case 'cpuUsage':
|
|
322
|
-
return `${Math.round(val)}%`
|
|
323
|
-
case 'requestRate':
|
|
324
|
-
return `${Math.round(val)}/s`
|
|
325
|
-
default:
|
|
326
|
-
return String(val)
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const valueStr = typeof value === 'number' ? formatValue(value, rule.metric) : String(value)
|
|
331
|
-
const thresholdStr = formatValue(rule.threshold, rule.metric)
|
|
332
|
-
|
|
333
|
-
return `${rule.name}: ${valueStr} ${rule.condition} ${thresholdStr} (${metrics.length} samples)`
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private async sendNotification(channel: AlertChannel, notification: AlertNotification): Promise<void> {
|
|
337
|
-
switch (channel.type) {
|
|
338
|
-
case 'console':
|
|
339
|
-
this.sendConsoleNotification(notification)
|
|
340
|
-
break
|
|
341
|
-
case 'webhook':
|
|
342
|
-
await this.sendWebhookNotification(channel, notification)
|
|
343
|
-
break
|
|
344
|
-
case 'slack':
|
|
345
|
-
await this.sendSlackNotification(channel, notification)
|
|
346
|
-
break
|
|
347
|
-
case 'email':
|
|
348
|
-
await this.sendEmailNotification(channel, notification)
|
|
349
|
-
break
|
|
350
|
-
case 'custom':
|
|
351
|
-
if (channel.config.customHandler) {
|
|
352
|
-
await channel.config.customHandler(notification)
|
|
353
|
-
}
|
|
354
|
-
break
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
private sendConsoleNotification(notification: AlertNotification): void {
|
|
359
|
-
const emoji = notification.severity === 'critical'
|
|
360
|
-
? '🚨'
|
|
361
|
-
: notification.severity === 'warning' ? '⚠️' : 'ℹ️'
|
|
362
|
-
|
|
363
|
-
console.log(`${emoji} ALERT [${notification.severity.toUpperCase()}]: ${notification.message}`)
|
|
364
|
-
console.log(` Rule: ${notification.ruleName} (${notification.ruleId})`)
|
|
365
|
-
console.log(` Time: ${new Date(notification.timestamp).toISOString()}`)
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
private async sendWebhookNotification(channel: AlertChannel, notification: AlertNotification): Promise<void> {
|
|
369
|
-
if (!channel.config.url)
|
|
370
|
-
return
|
|
371
|
-
|
|
372
|
-
const payload = {
|
|
373
|
-
alert: notification,
|
|
374
|
-
timestamp: new Date(notification.timestamp).toISOString(),
|
|
375
|
-
service: 'bun-router',
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
await fetch(channel.config.url, {
|
|
379
|
-
method: 'POST',
|
|
380
|
-
headers: {
|
|
381
|
-
'Content-Type': 'application/json',
|
|
382
|
-
...(channel.config.token && { Authorization: `Bearer ${channel.config.token}` }),
|
|
383
|
-
},
|
|
384
|
-
body: JSON.stringify(payload),
|
|
385
|
-
})
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
private async sendSlackNotification(channel: AlertChannel, notification: AlertNotification): Promise<void> {
|
|
389
|
-
if (!channel.config.url)
|
|
390
|
-
return
|
|
391
|
-
|
|
392
|
-
const color = notification.severity === 'critical'
|
|
393
|
-
? 'danger'
|
|
394
|
-
: notification.severity === 'warning' ? 'warning' : 'good'
|
|
395
|
-
|
|
396
|
-
const emoji = notification.severity === 'critical'
|
|
397
|
-
? ':rotating_light:'
|
|
398
|
-
: notification.severity === 'warning' ? ':warning:' : ':information_source:'
|
|
399
|
-
|
|
400
|
-
const payload = (() => {
|
|
401
|
-
const base = {
|
|
402
|
-
channel: channel.config.channel,
|
|
403
|
-
username: 'Performance Monitor',
|
|
404
|
-
icon_emoji: ':chart_with_upwards_trend:',
|
|
405
|
-
attachments: [{
|
|
406
|
-
color,
|
|
407
|
-
title: `${emoji} ${notification.ruleName}`,
|
|
408
|
-
text: notification.message,
|
|
409
|
-
fields: [
|
|
410
|
-
{
|
|
411
|
-
title: 'Severity',
|
|
412
|
-
value: notification.severity.toUpperCase(),
|
|
413
|
-
short: true,
|
|
414
|
-
},
|
|
415
|
-
{
|
|
416
|
-
title: 'Time',
|
|
417
|
-
value: new Date(notification.timestamp).toISOString(),
|
|
418
|
-
short: true,
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
footer: 'bun-router Performance Monitor',
|
|
422
|
-
ts: Math.floor(notification.timestamp / 1000),
|
|
423
|
-
}],
|
|
424
|
-
}
|
|
425
|
-
return base
|
|
426
|
-
})()
|
|
427
|
-
|
|
428
|
-
await fetch(channel.config.url, {
|
|
429
|
-
method: 'POST',
|
|
430
|
-
headers: { 'Content-Type': 'application/json' },
|
|
431
|
-
body: JSON.stringify(payload),
|
|
432
|
-
})
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
private async sendEmailNotification(channel: AlertChannel, notification: AlertNotification): Promise<void> {
|
|
436
|
-
// Email implementation would require an email service integration
|
|
437
|
-
// This is a placeholder for the interface
|
|
438
|
-
console.log(`Email notification would be sent to ${channel.config.email}:`, notification.message)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
private cleanupOldNotifications(): void {
|
|
442
|
-
const maxAge = 24 * 60 * 60 * 1000 // 24 hours
|
|
443
|
-
const cutoff = Date.now() - maxAge
|
|
444
|
-
|
|
445
|
-
this.notifications = this.notifications.filter(n => n.timestamp > cutoff)
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
private autoResolveAlerts(): void {
|
|
449
|
-
if (!this.options.autoResolve)
|
|
450
|
-
return
|
|
451
|
-
|
|
452
|
-
const timeout = this.options.autoResolveTimeout || 600000
|
|
453
|
-
const cutoff = Date.now() - timeout
|
|
454
|
-
|
|
455
|
-
for (const notification of this.notifications) {
|
|
456
|
-
if (!notification.resolved && notification.timestamp < cutoff) {
|
|
457
|
-
notification.resolved = true
|
|
458
|
-
notification.resolvedAt = Date.now()
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
private generateId(): string {
|
|
464
|
-
return Math.random().toString(36).substring(2, 15)
|
|
465
|
-
+ Math.random().toString(36).substring(2, 15)
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Public API methods
|
|
469
|
-
addMetrics(metrics: PerformanceMetrics): void {
|
|
470
|
-
this.metricsBuffer.push(metrics)
|
|
471
|
-
|
|
472
|
-
// Keep only recent metrics (last hour)
|
|
473
|
-
const maxAge = 60 * 60 * 1000 // 1 hour
|
|
474
|
-
const cutoff = Date.now() - maxAge
|
|
475
|
-
this.metricsBuffer = this.metricsBuffer.filter(m => m.timestamp > cutoff)
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
addRule(rule: AlertRule): void {
|
|
479
|
-
this.rules.set(rule.id, rule)
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
removeRule(ruleId: string): void {
|
|
483
|
-
this.rules.delete(ruleId)
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
updateRule(ruleId: string, updates: Partial<AlertRule>): void {
|
|
487
|
-
const rule = this.rules.get(ruleId)
|
|
488
|
-
if (rule) {
|
|
489
|
-
this.rules.set(ruleId, { ...rule, ...updates })
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
addChannel(channel: AlertChannel): void {
|
|
494
|
-
this.channels.push(channel)
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
removeChannel(index: number): void {
|
|
498
|
-
this.channels.splice(index, 1)
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
getActiveAlerts(): AlertNotification[] {
|
|
502
|
-
return this.notifications.filter(n => !n.resolved)
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
getAllNotifications(): AlertNotification[] {
|
|
506
|
-
return [...this.notifications]
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
resolveAlert(alertId: string): void {
|
|
510
|
-
const alert = this.notifications.find(n => n.id === alertId)
|
|
511
|
-
if (alert && !alert.resolved) {
|
|
512
|
-
alert.resolved = true
|
|
513
|
-
alert.resolvedAt = Date.now()
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
async handle(req: EnhancedRequest, next: NextFunction): Promise<Response> {
|
|
518
|
-
// This middleware doesn't intercept requests, it just provides the alerting service
|
|
519
|
-
const response = await next()
|
|
520
|
-
if (response)
|
|
521
|
-
return response
|
|
522
|
-
return new Response('No response from next middleware', { status: 500 })
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
destroy(): void {
|
|
526
|
-
if (this.checkInterval) {
|
|
527
|
-
clearInterval(this.checkInterval)
|
|
528
|
-
}
|
|
529
|
-
this.rules.clear()
|
|
530
|
-
this.channels = []
|
|
531
|
-
this.notifications = []
|
|
532
|
-
this.lastAlertTimes.clear()
|
|
533
|
-
this.metricsBuffer = []
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// Factory function for easy use
|
|
538
|
-
export function performanceAlerting(options: AlertingOptions = {}): PerformanceAlerting {
|
|
539
|
-
return new PerformanceAlerting(options)
|
|
540
|
-
}
|