@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,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
- }