@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,753 +0,0 @@
1
- /**
2
- * Observability & Monitoring - Custom Metrics Collection with Prometheus Format
3
- *
4
- * Enterprise-grade metrics collection and Prometheus-compatible exposition
5
- */
6
-
7
- import type { EnhancedRequest } from '../types'
8
- import process from 'node:process'
9
-
10
- export interface MetricConfig {
11
- prefix?: string
12
- labels?: Record<string, string>
13
- enableDefaultMetrics?: boolean
14
- collectInterval?: number
15
- maxHistogramBuckets?: number
16
- maxSummaryAge?: number
17
- }
18
-
19
- export interface MetricLabels {
20
- [key: string]: string
21
- }
22
-
23
- export interface HistogramBucket {
24
- le: number
25
- count: number
26
- }
27
-
28
- export interface SummaryQuantile {
29
- quantile: number
30
- value: number
31
- }
32
-
33
- /**
34
- * Base metric interface
35
- */
36
- export abstract class Metric {
37
- public readonly name: string
38
- public readonly help: string
39
- public readonly type: string
40
- public readonly labels: MetricLabels
41
-
42
- constructor(name: string, help: string, type: string, labels: MetricLabels = {}) {
43
- this.name = name
44
- this.help = help
45
- this.type = type
46
- this.labels = labels
47
- }
48
-
49
- abstract getValue(): number | string
50
- abstract reset(): void
51
- abstract toPrometheusString(): string
52
- }
53
-
54
- /**
55
- * Counter metric - monotonically increasing value
56
- */
57
- export class Counter extends Metric {
58
- private value = 0
59
- private labeledCounters = new Map<string, number>()
60
-
61
- constructor(name: string, help: string, labels: MetricLabels = {}) {
62
- super(name, help, 'counter', labels)
63
- }
64
-
65
- inc(value = 1, labels: MetricLabels = {}): void {
66
- if (Object.keys(labels).length === 0) {
67
- this.value += value
68
- }
69
- else {
70
- const key = this.getLabelKey(labels)
71
- this.labeledCounters.set(key, (this.labeledCounters.get(key) || 0) + value)
72
- }
73
- }
74
-
75
- getValue(): number {
76
- return this.value
77
- }
78
-
79
- reset(): void {
80
- this.value = 0
81
- this.labeledCounters.clear()
82
- }
83
-
84
- private getLabelKey(labels: MetricLabels): string {
85
- return Object.entries({ ...this.labels, ...labels })
86
- .sort(([a], [b]) => a.localeCompare(b))
87
- .map(([k, v]) => `${k}="${v}"`)
88
- .join(',')
89
- }
90
-
91
- toPrometheusString(): string {
92
- let output = `# HELP ${this.name} ${this.help}\n`
93
- output += `# TYPE ${this.name} counter\n`
94
-
95
- if (this.labeledCounters.size === 0) {
96
- const labelStr = Object.keys(this.labels).length > 0
97
- ? `{${this.getLabelKey(this.labels)}}`
98
- : ''
99
- output += `${this.name}${labelStr} ${this.value}\n`
100
- }
101
- else {
102
- for (const [labelKey, value] of this.labeledCounters.entries()) {
103
- output += `${this.name}{${labelKey}} ${value}\n`
104
- }
105
- }
106
-
107
- return output
108
- }
109
- }
110
-
111
- /**
112
- * Gauge metric - can go up and down
113
- */
114
- export class Gauge extends Metric {
115
- private value = 0
116
- private labeledGauges = new Map<string, number>()
117
-
118
- constructor(name: string, help: string, labels: MetricLabels = {}) {
119
- super(name, help, 'gauge', labels)
120
- }
121
-
122
- set(value: number, labels: MetricLabels = {}): void {
123
- if (Object.keys(labels).length === 0) {
124
- this.value = value
125
- }
126
- else {
127
- const key = this.getLabelKey(labels)
128
- this.labeledGauges.set(key, value)
129
- }
130
- }
131
-
132
- inc(value = 1, labels: MetricLabels = {}): void {
133
- if (Object.keys(labels).length === 0) {
134
- this.value += value
135
- }
136
- else {
137
- const key = this.getLabelKey(labels)
138
- this.labeledGauges.set(key, (this.labeledGauges.get(key) || 0) + value)
139
- }
140
- }
141
-
142
- dec(value = 1, labels: MetricLabels = {}): void {
143
- this.inc(-value, labels)
144
- }
145
-
146
- getValue(): number {
147
- return this.value
148
- }
149
-
150
- reset(): void {
151
- this.value = 0
152
- this.labeledGauges.clear()
153
- }
154
-
155
- private getLabelKey(labels: MetricLabels): string {
156
- return Object.entries({ ...this.labels, ...labels })
157
- .sort(([a], [b]) => a.localeCompare(b))
158
- .map(([k, v]) => `${k}="${v}"`)
159
- .join(',')
160
- }
161
-
162
- toPrometheusString(): string {
163
- let output = `# HELP ${this.name} ${this.help}\n`
164
- output += `# TYPE ${this.name} gauge\n`
165
-
166
- if (this.labeledGauges.size === 0) {
167
- const labelStr = Object.keys(this.labels).length > 0
168
- ? `{${this.getLabelKey(this.labels)}}`
169
- : ''
170
- output += `${this.name}${labelStr} ${this.value}\n`
171
- }
172
- else {
173
- for (const [labelKey, value] of this.labeledGauges.entries()) {
174
- output += `${this.name}{${labelKey}} ${value}\n`
175
- }
176
- }
177
-
178
- return output
179
- }
180
- }
181
-
182
- /**
183
- * Histogram metric - tracks distribution of values
184
- */
185
- export class Histogram extends Metric {
186
- private buckets: Map<number, number> = new Map()
187
- private sum = 0
188
- private count = 0
189
- private readonly bucketBounds: number[]
190
-
191
- constructor(
192
- name: string,
193
- help: string,
194
- buckets: number[] = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
195
- labels: MetricLabels = {},
196
- ) {
197
- super(name, help, 'histogram', labels)
198
- this.bucketBounds = [...buckets, Infinity].sort((a, b) => a - b)
199
-
200
- // Initialize buckets
201
- for (const bound of this.bucketBounds) {
202
- this.buckets.set(bound, 0)
203
- }
204
- }
205
-
206
- observe(value: number, _labels: MetricLabels = {}): void {
207
- this.sum += value
208
- this.count++
209
-
210
- // Increment appropriate buckets
211
- for (const bound of this.bucketBounds) {
212
- if (value <= bound) {
213
- this.buckets.set(bound, (this.buckets.get(bound) || 0) + 1)
214
- }
215
- }
216
- }
217
-
218
- getValue(): number {
219
- return this.sum
220
- }
221
-
222
- getCount(): number {
223
- return this.count
224
- }
225
-
226
- getSum(): number {
227
- return this.sum
228
- }
229
-
230
- getBuckets(): HistogramBucket[] {
231
- return Array.from(this.buckets.entries()).map(([le, count]) => ({ le, count }))
232
- }
233
-
234
- reset(): void {
235
- this.sum = 0
236
- this.count = 0
237
- for (const bound of this.bucketBounds) {
238
- this.buckets.set(bound, 0)
239
- }
240
- }
241
-
242
- toPrometheusString(): string {
243
- let output = `# HELP ${this.name} ${this.help}\n`
244
- output += `# TYPE ${this.name} histogram\n`
245
-
246
- const labelStr = Object.keys(this.labels).length > 0
247
- ? `{${Object.entries(this.labels).map(([k, v]) => `${k}="${v}"`).join(',')}}`
248
- : ''
249
-
250
- // Output buckets
251
- for (const [bound, count] of this.buckets.entries()) {
252
- const le = bound === Infinity ? '+Inf' : bound.toString()
253
- const bucketLabels = labelStr ? `{${labelStr.slice(1, -1)},le="${le}"}` : `{le="${le}"}`
254
- output += `${this.name}_bucket${bucketLabels} ${count}\n`
255
- }
256
-
257
- // Output sum and count
258
- output += `${this.name}_sum${labelStr} ${this.sum}\n`
259
- output += `${this.name}_count${labelStr} ${this.count}\n`
260
-
261
- return output
262
- }
263
- }
264
-
265
- /**
266
- * Summary metric - tracks quantiles over sliding time window
267
- */
268
- export class Summary extends Metric {
269
- private observations: Array<{ value: number, timestamp: number }> = []
270
- private sum = 0
271
- private count = 0
272
- private readonly quantiles: number[]
273
- private readonly maxAge: number
274
-
275
- constructor(
276
- name: string,
277
- help: string,
278
- quantiles: number[] = [0.5, 0.9, 0.95, 0.99],
279
- maxAge = 600000, // 10 minutes
280
- labels: MetricLabels = {},
281
- ) {
282
- super(name, help, 'summary', labels)
283
- this.quantiles = quantiles.sort((a, b) => a - b)
284
- this.maxAge = maxAge
285
- }
286
-
287
- observe(value: number, _labels: MetricLabels = {}): void {
288
- const now = Date.now()
289
- this.observations.push({ value, timestamp: now })
290
- this.sum += value
291
- this.count++
292
-
293
- // Clean old observations
294
- this.cleanOldObservations(now)
295
- }
296
-
297
- getValue(): number {
298
- return this.sum
299
- }
300
-
301
- getCount(): number {
302
- return this.count
303
- }
304
-
305
- getSum(): number {
306
- return this.sum
307
- }
308
-
309
- getQuantiles(): SummaryQuantile[] {
310
- this.cleanOldObservations(Date.now())
311
-
312
- if (this.observations.length === 0) {
313
- return this.quantiles.map(q => ({ quantile: q, value: 0 }))
314
- }
315
-
316
- const sortedValues = this.observations
317
- .map(obs => obs.value)
318
- .sort((a, b) => a - b)
319
-
320
- return this.quantiles.map((quantile) => {
321
- const index = Math.ceil(quantile * sortedValues.length) - 1
322
- const value = sortedValues[Math.max(0, index)] || 0
323
- return { quantile, value }
324
- })
325
- }
326
-
327
- private cleanOldObservations(now: number): void {
328
- const cutoff = now - this.maxAge
329
- const oldLength = this.observations.length
330
-
331
- this.observations = this.observations.filter(obs => obs.timestamp >= cutoff)
332
-
333
- // Adjust sum and count for removed observations
334
- const removedCount = oldLength - this.observations.length
335
- if (removedCount > 0) {
336
- // Recalculate sum from remaining observations
337
- this.sum = this.observations.reduce((sum, obs) => sum + obs.value, 0)
338
- this.count = this.observations.length
339
- }
340
- }
341
-
342
- reset(): void {
343
- this.observations = []
344
- this.sum = 0
345
- this.count = 0
346
- }
347
-
348
- toPrometheusString(): string {
349
- let output = `# HELP ${this.name} ${this.help}\n`
350
- output += `# TYPE ${this.name} summary\n`
351
-
352
- const labelStr = Object.keys(this.labels).length > 0
353
- ? `{${Object.entries(this.labels).map(([k, v]) => `${k}="${v}"`).join(',')}}`
354
- : ''
355
-
356
- // Output quantiles
357
- const quantiles = this.getQuantiles()
358
- for (const { quantile, value } of quantiles) {
359
- const quantileLabels = labelStr
360
- ? `{${labelStr.slice(1, -1)},quantile="${quantile}"}`
361
- : `{quantile="${quantile}"}`
362
- output += `${this.name}${quantileLabels} ${value}\n`
363
- }
364
-
365
- // Output sum and count
366
- output += `${this.name}_sum${labelStr} ${this.sum}\n`
367
- output += `${this.name}_count${labelStr} ${this.count}\n`
368
-
369
- return output
370
- }
371
- }
372
-
373
- /**
374
- * Metrics registry
375
- */
376
- export class MetricsRegistry {
377
- private metrics = new Map<string, Metric>()
378
- private config: MetricConfig
379
- private defaultMetrics: Map<string, Metric> = new Map()
380
-
381
- constructor(config: MetricConfig = {}) {
382
- this.config = {
383
- prefix: '',
384
- enableDefaultMetrics: true,
385
- collectInterval: 15000, // 15 seconds
386
- maxHistogramBuckets: 20,
387
- maxSummaryAge: 600000, // 10 minutes
388
- ...config,
389
- }
390
-
391
- if (this.config.enableDefaultMetrics) {
392
- this.registerDefaultMetrics()
393
- }
394
- }
395
-
396
- /**
397
- * Register a metric
398
- */
399
- register(metric: Metric): void {
400
- const name = this.config.prefix ? `${this.config.prefix}_${metric.name}` : metric.name
401
- this.metrics.set(name, metric)
402
- }
403
-
404
- /**
405
- * Get a metric by name
406
- */
407
- get(name: string): Metric | undefined {
408
- const fullName = this.config.prefix ? `${this.config.prefix}_${name}` : name
409
- return this.metrics.get(fullName)
410
- }
411
-
412
- /**
413
- * Create and register a counter
414
- */
415
- createCounter(name: string, help: string, labels: MetricLabels = {}): Counter {
416
- const counter = new Counter(name, help, labels)
417
- this.register(counter)
418
- return counter
419
- }
420
-
421
- /**
422
- * Create and register a gauge
423
- */
424
- createGauge(name: string, help: string, labels: MetricLabels = {}): Gauge {
425
- const gauge = new Gauge(name, help, labels)
426
- this.register(gauge)
427
- return gauge
428
- }
429
-
430
- /**
431
- * Create and register a histogram
432
- */
433
- createHistogram(
434
- name: string,
435
- help: string,
436
- buckets?: number[],
437
- labels: MetricLabels = {},
438
- ): Histogram {
439
- const histogram = new Histogram(name, help, buckets, labels)
440
- this.register(histogram)
441
- return histogram
442
- }
443
-
444
- /**
445
- * Create and register a summary
446
- */
447
- createSummary(
448
- name: string,
449
- help: string,
450
- quantiles?: number[],
451
- maxAge?: number,
452
- labels: MetricLabels = {},
453
- ): Summary {
454
- const summary = new Summary(name, help, quantiles, maxAge, labels)
455
- this.register(summary)
456
- return summary
457
- }
458
-
459
- /**
460
- * Register default system metrics
461
- */
462
- private registerDefaultMetrics(): void {
463
- // HTTP request metrics
464
- const httpRequestsTotal = this.createCounter(
465
- 'http_requests_total',
466
- 'Total number of HTTP requests',
467
- )
468
-
469
- const httpRequestDuration = this.createHistogram(
470
- 'http_request_duration_seconds',
471
- 'HTTP request duration in seconds',
472
- [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
473
- )
474
-
475
- const httpRequestSize = this.createHistogram(
476
- 'http_request_size_bytes',
477
- 'HTTP request size in bytes',
478
- [100, 1000, 10000, 100000, 1000000, 10000000],
479
- )
480
-
481
- const httpResponseSize = this.createHistogram(
482
- 'http_response_size_bytes',
483
- 'HTTP response size in bytes',
484
- [100, 1000, 10000, 100000, 1000000, 10000000],
485
- )
486
-
487
- // System metrics
488
- const processStartTime = this.createGauge(
489
- 'process_start_time_seconds',
490
- 'Start time of the process since unix epoch in seconds',
491
- )
492
- processStartTime.set(Date.now() / 1000)
493
-
494
- const processUptime = this.createGauge(
495
- 'process_uptime_seconds',
496
- 'Process uptime in seconds',
497
- )
498
-
499
- // Memory metrics
500
- const processMemoryUsage = this.createGauge(
501
- 'process_memory_usage_bytes',
502
- 'Process memory usage in bytes',
503
- )
504
-
505
- // Active connections
506
- const activeConnections = this.createGauge(
507
- 'active_connections',
508
- 'Number of active connections',
509
- )
510
-
511
- this.defaultMetrics.set('http_requests_total', httpRequestsTotal)
512
- this.defaultMetrics.set('http_request_duration_seconds', httpRequestDuration)
513
- this.defaultMetrics.set('http_request_size_bytes', httpRequestSize)
514
- this.defaultMetrics.set('http_response_size_bytes', httpResponseSize)
515
- this.defaultMetrics.set('process_uptime_seconds', processUptime)
516
- this.defaultMetrics.set('process_memory_usage_bytes', processMemoryUsage)
517
- this.defaultMetrics.set('active_connections', activeConnections)
518
-
519
- // Start collection interval for system metrics
520
- if (this.config.collectInterval && this.config.collectInterval > 0) {
521
- setInterval(() => {
522
- this.collectSystemMetrics()
523
- }, this.config.collectInterval).unref?.()
524
- }
525
- }
526
-
527
- /**
528
- * Collect system metrics
529
- */
530
- private collectSystemMetrics(): void {
531
- try {
532
- // Update uptime
533
- const uptimeGauge = this.defaultMetrics.get('process_uptime_seconds') as Gauge
534
- if (uptimeGauge) {
535
- uptimeGauge.set(process.uptime())
536
- }
537
-
538
- // Update memory usage
539
- const memoryGauge = this.defaultMetrics.get('process_memory_usage_bytes') as Gauge
540
- if (memoryGauge && typeof process.memoryUsage === 'function') {
541
- const memUsage = process.memoryUsage()
542
- memoryGauge.set(memUsage.rss)
543
- }
544
- }
545
- catch (error) {
546
- console.error('Error collecting system metrics:', error)
547
- }
548
- }
549
-
550
- /**
551
- * Get all metrics in Prometheus format
552
- */
553
- toPrometheusString(): string {
554
- let output = ''
555
-
556
- for (const metric of this.metrics.values()) {
557
- output += `${metric.toPrometheusString()}\n`
558
- }
559
-
560
- return output.trim()
561
- }
562
-
563
- /**
564
- * Get metrics as JSON
565
- */
566
- toJSON(): Record<string, any> {
567
- const result: Record<string, any> = {}
568
-
569
- for (const [name, metric] of this.metrics.entries()) {
570
- result[name] = {
571
- type: metric.type,
572
- help: metric.help,
573
- value: metric.getValue(),
574
- labels: metric.labels,
575
- }
576
- }
577
-
578
- return result
579
- }
580
-
581
- /**
582
- * Reset all metrics
583
- */
584
- reset(): void {
585
- for (const metric of this.metrics.values()) {
586
- metric.reset()
587
- }
588
- }
589
-
590
- /**
591
- * Get metric names
592
- */
593
- getMetricNames(): string[] {
594
- return Array.from(this.metrics.keys())
595
- }
596
-
597
- /**
598
- * Remove a metric
599
- */
600
- unregister(name: string): boolean {
601
- const fullName = this.config.prefix ? `${this.config.prefix}_${name}` : name
602
- return this.metrics.delete(fullName)
603
- }
604
-
605
- /**
606
- * Clear all metrics
607
- */
608
- clear(): void {
609
- this.metrics.clear()
610
- this.defaultMetrics.clear()
611
- }
612
- }
613
-
614
- /**
615
- * Global metrics registry
616
- */
617
- let globalRegistry: MetricsRegistry | null = null
618
-
619
- /**
620
- * Initialize global metrics registry
621
- */
622
- export function initializeMetrics(config?: MetricConfig): MetricsRegistry {
623
- globalRegistry = new MetricsRegistry(config)
624
- return globalRegistry
625
- }
626
-
627
- /**
628
- * Get global metrics registry
629
- */
630
- export function getMetricsRegistry(): MetricsRegistry | null {
631
- return globalRegistry
632
- }
633
-
634
- /**
635
- * Metrics middleware factory
636
- */
637
- export function createMetricsMiddleware(_config?: Partial<MetricConfig>) {
638
- return async (req: EnhancedRequest, next: () => Promise<Response>): Promise<Response> => {
639
- const registry = getMetricsRegistry()
640
- if (!registry) {
641
- return await next()
642
- }
643
-
644
- const startTime = Date.now()
645
- const url = new URL(req.url)
646
-
647
- // Get metrics
648
- const requestsTotal = registry.get('http_requests_total') as Counter
649
- const requestDuration = registry.get('http_request_duration_seconds') as Histogram
650
- const requestSize = registry.get('http_request_size_bytes') as Histogram
651
- const responseSize = registry.get('http_response_size_bytes') as Histogram
652
- const activeConnections = registry.get('active_connections') as Gauge
653
-
654
- // Increment active connections
655
- if (activeConnections) {
656
- activeConnections.inc()
657
- }
658
-
659
- // Record request size
660
- if (requestSize) {
661
- const contentLength = req.headers.get('content-length')
662
- if (contentLength) {
663
- requestSize.observe(Number.parseInt(contentLength, 10))
664
- }
665
- }
666
-
667
- let response: Response
668
- try {
669
- response = await next()
670
-
671
- // Record metrics
672
- const duration = (Date.now() - startTime) / 1000
673
- const labels = {
674
- method: req.method,
675
- status: response.status.toString(),
676
- path: url.pathname,
677
- }
678
-
679
- if (requestsTotal) {
680
- requestsTotal.inc(1, labels)
681
- }
682
-
683
- if (requestDuration) {
684
- requestDuration.observe(duration, labels)
685
- }
686
-
687
- if (responseSize) {
688
- const contentLength = response.headers.get('content-length')
689
- if (contentLength) {
690
- responseSize.observe(Number.parseInt(contentLength, 10), labels)
691
- }
692
- }
693
- }
694
- catch (error) {
695
- // Record error metrics
696
- const duration = (Date.now() - startTime) / 1000
697
- const labels = {
698
- method: req.method,
699
- status: '500',
700
- path: url.pathname,
701
- }
702
-
703
- if (requestsTotal) {
704
- requestsTotal.inc(1, labels)
705
- }
706
-
707
- if (requestDuration) {
708
- requestDuration.observe(duration, labels)
709
- }
710
-
711
- throw error
712
- }
713
- finally {
714
- // Decrement active connections
715
- if (activeConnections) {
716
- activeConnections.dec()
717
- }
718
- }
719
-
720
- return response
721
- }
722
- }
723
-
724
- /**
725
- * Metrics endpoint handler
726
- */
727
- export function createMetricsHandler() {
728
- return async (req: EnhancedRequest): Promise<Response> => {
729
- const registry = getMetricsRegistry()
730
- if (!registry) {
731
- return new Response('Metrics not initialized', { status: 500 })
732
- }
733
-
734
- const format = new URL(req.url).searchParams.get('format') || 'prometheus'
735
-
736
- if (format === 'json') {
737
- return new Response(JSON.stringify(registry.toJSON(), null, 2), {
738
- headers: {
739
- 'Content-Type': 'application/json',
740
- 'Cache-Control': 'no-cache',
741
- },
742
- })
743
- }
744
-
745
- // Default to Prometheus format
746
- return new Response(registry.toPrometheusString(), {
747
- headers: {
748
- 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
749
- 'Cache-Control': 'no-cache',
750
- },
751
- })
752
- }
753
- }