@stacksjs/bun-router 0.0.5 → 0.0.7

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 (269) hide show
  1. package/dist/auth.d.ts +134 -0
  2. package/dist/cache/lru-cache.d.ts +139 -0
  3. package/dist/cache/middleware-memoization.d.ts +151 -0
  4. package/dist/cache/route-cache-warmer.d.ts +161 -0
  5. package/dist/cache/sqlite-cache.d.ts +209 -0
  6. package/dist/cache/streaming-cache.d.ts +132 -0
  7. package/dist/cli/colors.d.ts +15 -0
  8. package/dist/cli/index.d.ts +10 -0
  9. package/dist/cli/middleware.d.ts +31 -0
  10. package/dist/cli/openapi.d.ts +17 -0
  11. package/dist/cli/router.d.ts +15 -0
  12. package/dist/cli/routes.d.ts +29 -0
  13. package/dist/cli/utils.d.ts +74 -0
  14. package/dist/cli.d.ts +1 -1
  15. package/dist/cli.js +1 -1
  16. package/dist/config.d.ts +7 -0
  17. package/dist/container/container.d.ts +273 -0
  18. package/dist/container/contextual-binding.d.ts +240 -0
  19. package/dist/container/decorators.d.ts +141 -0
  20. package/dist/container/service-provider.d.ts +285 -0
  21. package/dist/development/hot-reload.d.ts +187 -0
  22. package/dist/development/index.d.ts +167 -0
  23. package/dist/development/performance-profiler.d.ts +217 -0
  24. package/dist/development/route-debugger.d.ts +154 -0
  25. package/dist/development/route-inspector.d.ts +211 -0
  26. package/dist/development/typescript-utilities.d.ts +209 -0
  27. package/dist/docs.d.ts +11 -0
  28. package/dist/errors/circuit-breaker.d.ts +195 -0
  29. package/dist/errors/error-handler.d.ts +88 -0
  30. package/dist/errors/error-reporting.d.ts +144 -0
  31. package/dist/errors/exceptions.d.ts +184 -0
  32. package/dist/errors/graceful-degradation.d.ts +154 -0
  33. package/dist/errors/index.d.ts +9 -0
  34. package/dist/errors/router-errors.d.ts +110 -0
  35. package/dist/file-serving/static-files.d.ts +143 -0
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +375 -227
  38. package/dist/middleware/auth.d.ts +63 -0
  39. package/dist/middleware/content_security_policy.d.ts +53 -0
  40. package/dist/middleware/cors.d.ts +5 -0
  41. package/dist/middleware/csrf.d.ts +7 -0
  42. package/dist/middleware/ddos_protection.d.ts +39 -0
  43. package/dist/middleware/file_security.d.ts +26 -0
  44. package/dist/middleware/file_upload.d.ts +38 -0
  45. package/dist/middleware/helmet.d.ts +54 -0
  46. package/dist/middleware/index.d.ts +44 -0
  47. package/dist/middleware/input_validation.d.ts +45 -0
  48. package/dist/middleware/json_body.d.ts +4 -0
  49. package/dist/middleware/performance_alerting.d.ts +87 -0
  50. package/dist/middleware/performance_dashboard.d.ts +87 -0
  51. package/dist/middleware/performance_monitor.d.ts +209 -0
  52. package/dist/middleware/pipeline.d.ts +131 -0
  53. package/dist/middleware/rate_limit.d.ts +36 -0
  54. package/dist/middleware/request_id.d.ts +4 -0
  55. package/dist/middleware/request_signing.d.ts +153 -0
  56. package/dist/middleware/request_tracer.d.ts +72 -0
  57. package/dist/middleware/response_cache.d.ts +97 -0
  58. package/dist/middleware/security.d.ts +69 -0
  59. package/dist/middleware/security_suite.d.ts +46 -0
  60. package/dist/middleware/session.d.ts +23 -0
  61. package/dist/model-binding/index.d.ts +2 -0
  62. package/dist/model-binding/model-middleware.d.ts +118 -0
  63. package/dist/model-binding/model-registry.d.ts +164 -0
  64. package/dist/model-binding.d.ts +185 -0
  65. package/dist/model-resolver-factory.d.ts +31 -0
  66. package/dist/observability/correlation.d.ts +187 -0
  67. package/dist/observability/health-checks.d.ts +186 -0
  68. package/dist/observability/index.d.ts +60 -0
  69. package/dist/observability/integration.d.ts +147 -0
  70. package/dist/observability/metrics.d.ts +184 -0
  71. package/dist/observability/tracing.d.ts +187 -0
  72. package/dist/optimization/bun-utilities.d.ts +224 -0
  73. package/dist/query-builder-integration.d.ts +36 -0
  74. package/dist/request/context.d.ts +34 -0
  75. package/dist/request/enhanced-request.d.ts +213 -0
  76. package/dist/request/macros.d.ts +300 -0
  77. package/dist/response/macros.d.ts +245 -0
  78. package/dist/response/response-factory.d.ts +133 -0
  79. package/dist/router/api-routes.d.ts +5 -0
  80. package/dist/router/file-based-routing.d.ts +118 -0
  81. package/dist/router/file-streaming.d.ts +5 -0
  82. package/dist/router/fluent-router.d.ts +315 -0
  83. package/dist/router/fluent-routing.d.ts +271 -0
  84. package/dist/router/group-organization.d.ts +5 -0
  85. package/dist/router/handler-resolver.d.ts +24 -0
  86. package/dist/router/http-methods.d.ts +10 -0
  87. package/dist/router/index.d.ts +113 -0
  88. package/dist/router/middleware-groups.d.ts +94 -0
  89. package/dist/router/middleware-integration.d.ts +112 -0
  90. package/dist/router/middleware.d.ts +5 -0
  91. package/dist/router/model-binding.d.ts +5 -0
  92. package/dist/router/optimized-route-matching.d.ts +6 -0
  93. package/dist/router/route-building.d.ts +5 -0
  94. package/dist/router/route-compiler.d.ts +153 -0
  95. package/dist/router/route-matching.d.ts +5 -0
  96. package/dist/router/route-trie.d.ts +114 -0
  97. package/dist/router/router.d.ts +276 -0
  98. package/dist/router/server.d.ts +5 -0
  99. package/dist/router/validation-integration.d.ts +170 -0
  100. package/dist/router/view-rendering.d.ts +5 -0
  101. package/dist/router/websocket.d.ts +5 -0
  102. package/dist/routing/route-caching.d.ts +129 -0
  103. package/dist/routing/route-throttling.d.ts +150 -0
  104. package/dist/routing/subdomain-routing.d.ts +206 -0
  105. package/dist/session/database-store.d.ts +51 -0
  106. package/dist/session/file-store.d.ts +22 -0
  107. package/dist/session/index.d.ts +106 -0
  108. package/dist/session/memory-store.d.ts +21 -0
  109. package/dist/session/redis-store.d.ts +31 -0
  110. package/dist/streaming/index.d.ts +3 -0
  111. package/dist/streaming/sse-handler.d.ts +138 -0
  112. package/dist/streaming/stream-handler.d.ts +114 -0
  113. package/dist/testing/auth-testing.d.ts +156 -0
  114. package/dist/testing/file-upload-testing.d.ts +190 -0
  115. package/dist/testing/index.d.ts +10 -0
  116. package/dist/testing/middleware-testing.d.ts +138 -0
  117. package/dist/testing/model-binding-testing.d.ts +186 -0
  118. package/dist/testing/performance-testing.d.ts +235 -0
  119. package/dist/testing/test-client.d.ts +117 -0
  120. package/dist/testing/test-request.d.ts +85 -0
  121. package/dist/testing/test-response.d.ts +90 -0
  122. package/dist/testing/types.d.ts +207 -0
  123. package/dist/testing/websocket-testing.d.ts +227 -0
  124. package/dist/types/controller-types.d.ts +208 -0
  125. package/dist/types/core.d.ts +543 -0
  126. package/dist/types/middleware-types.d.ts +227 -0
  127. package/dist/types/request-response-augmentation.d.ts +261 -0
  128. package/dist/types/route-inference.d.ts +168 -0
  129. package/dist/types.d.ts +1776 -0
  130. package/dist/url.d.ts +56 -0
  131. package/dist/utils/index.d.ts +1 -0
  132. package/dist/utils/query-preservation.d.ts +48 -0
  133. package/dist/utils.d.ts +69 -0
  134. package/dist/validation/validator.d.ts +140 -0
  135. package/dist/websocket/clustering.d.ts +185 -0
  136. package/package.json +3 -2
  137. package/src/auth.ts +469 -0
  138. package/src/cache/lru-cache.ts +457 -0
  139. package/src/cache/middleware-memoization.ts +531 -0
  140. package/src/cache/route-cache-warmer.ts +486 -0
  141. package/src/cache/sqlite-cache.ts +783 -0
  142. package/src/cache/streaming-cache.ts +572 -0
  143. package/src/cli/colors.ts +29 -0
  144. package/src/cli/index.ts +287 -0
  145. package/src/cli/middleware.ts +291 -0
  146. package/src/cli/openapi.ts +407 -0
  147. package/src/cli/router.ts +188 -0
  148. package/src/cli/routes.ts +265 -0
  149. package/src/cli/utils.ts +531 -0
  150. package/src/cli.ts +5 -0
  151. package/src/config.ts +322 -0
  152. package/src/container/container.ts +740 -0
  153. package/src/container/contextual-binding.ts +603 -0
  154. package/src/container/decorators.ts +359 -0
  155. package/src/container/service-provider.ts +596 -0
  156. package/src/development/hot-reload.ts +673 -0
  157. package/src/development/index.ts +499 -0
  158. package/src/development/performance-profiler.ts +717 -0
  159. package/src/development/route-debugger.ts +527 -0
  160. package/src/development/route-inspector.ts +749 -0
  161. package/src/development/typescript-utilities.ts +678 -0
  162. package/src/docs.ts +397 -0
  163. package/src/errors/circuit-breaker.ts +732 -0
  164. package/src/errors/error-handler.ts +569 -0
  165. package/src/errors/error-reporting.ts +672 -0
  166. package/src/errors/exceptions.ts +536 -0
  167. package/src/errors/graceful-degradation.ts +621 -0
  168. package/src/errors/index.ts +21 -0
  169. package/src/errors/router-errors.ts +632 -0
  170. package/src/file-serving/static-files.ts +581 -0
  171. package/src/index.ts +14 -0
  172. package/src/middleware/auth.ts +219 -0
  173. package/src/middleware/content_security_policy.ts +215 -0
  174. package/src/middleware/cors.ts +74 -0
  175. package/src/middleware/csrf.ts +108 -0
  176. package/src/middleware/ddos_protection.ts +255 -0
  177. package/src/middleware/file_security.ts +191 -0
  178. package/src/middleware/file_upload.ts +264 -0
  179. package/src/middleware/helmet.ts +268 -0
  180. package/src/middleware/index.ts +117 -0
  181. package/src/middleware/input_validation.ts +449 -0
  182. package/src/middleware/json_body.ts +37 -0
  183. package/src/middleware/performance_alerting.ts +538 -0
  184. package/src/middleware/performance_dashboard.ts +661 -0
  185. package/src/middleware/performance_monitor.ts +943 -0
  186. package/src/middleware/pipeline.ts +489 -0
  187. package/src/middleware/rate_limit.ts +245 -0
  188. package/src/middleware/request_id.ts +36 -0
  189. package/src/middleware/request_signing.ts +636 -0
  190. package/src/middleware/request_tracer.ts +636 -0
  191. package/src/middleware/response_cache.ts +743 -0
  192. package/src/middleware/security.ts +482 -0
  193. package/src/middleware/security_suite.ts +257 -0
  194. package/src/middleware/session.ts +91 -0
  195. package/src/model-binding/index.ts +18 -0
  196. package/src/model-binding/model-middleware.ts +425 -0
  197. package/src/model-binding/model-registry.ts +550 -0
  198. package/src/model-binding.ts +370 -0
  199. package/src/model-resolver-factory.ts +106 -0
  200. package/src/observability/correlation.ts +691 -0
  201. package/src/observability/health-checks.ts +729 -0
  202. package/src/observability/index.ts +184 -0
  203. package/src/observability/integration.ts +548 -0
  204. package/src/observability/metrics.ts +753 -0
  205. package/src/observability/tracing.ts +638 -0
  206. package/src/optimization/bun-utilities.ts +778 -0
  207. package/src/query-builder-integration.ts +137 -0
  208. package/src/request/context.ts +62 -0
  209. package/src/request/enhanced-request.ts +857 -0
  210. package/src/request/macros.ts +688 -0
  211. package/src/response/macros.ts +612 -0
  212. package/src/response/response-factory.ts +588 -0
  213. package/src/router/api-routes.ts +243 -0
  214. package/src/router/file-based-routing.ts +670 -0
  215. package/src/router/file-streaming.ts +372 -0
  216. package/src/router/fluent-router.ts +927 -0
  217. package/src/router/fluent-routing.ts +797 -0
  218. package/src/router/group-organization.ts +213 -0
  219. package/src/router/handler-resolver.ts +291 -0
  220. package/src/router/http-methods.ts +377 -0
  221. package/src/router/index.ts +185 -0
  222. package/src/router/middleware-groups.ts +222 -0
  223. package/src/router/middleware-integration.ts +399 -0
  224. package/src/router/middleware.ts +231 -0
  225. package/src/router/model-binding.ts +215 -0
  226. package/src/router/optimized-route-matching.ts +253 -0
  227. package/src/router/route-building.ts +212 -0
  228. package/src/router/route-compiler.ts +702 -0
  229. package/src/router/route-matching.ts +349 -0
  230. package/src/router/route-trie.ts +464 -0
  231. package/src/router/router.ts +1524 -0
  232. package/src/router/server.ts +429 -0
  233. package/src/router/validation-integration.ts +443 -0
  234. package/src/router/view-rendering.ts +233 -0
  235. package/src/router/websocket.ts +100 -0
  236. package/src/routing/route-caching.ts +402 -0
  237. package/src/routing/route-throttling.ts +469 -0
  238. package/src/routing/subdomain-routing.ts +492 -0
  239. package/src/session/database-store.ts +109 -0
  240. package/src/session/file-store.ts +148 -0
  241. package/src/session/index.ts +244 -0
  242. package/src/session/memory-store.ts +88 -0
  243. package/src/session/redis-store.ts +93 -0
  244. package/src/streaming/index.ts +17 -0
  245. package/src/streaming/sse-handler.ts +482 -0
  246. package/src/streaming/stream-handler.ts +552 -0
  247. package/src/testing/auth-testing.ts +446 -0
  248. package/src/testing/file-upload-testing.ts +543 -0
  249. package/src/testing/index.ts +10 -0
  250. package/src/testing/middleware-testing.ts +322 -0
  251. package/src/testing/model-binding-testing.ts +645 -0
  252. package/src/testing/performance-testing.ts +738 -0
  253. package/src/testing/test-client.ts +310 -0
  254. package/src/testing/test-request.ts +315 -0
  255. package/src/testing/test-response.ts +332 -0
  256. package/src/testing/types.ts +224 -0
  257. package/src/testing/websocket-testing.ts +590 -0
  258. package/src/types/controller-types.ts +385 -0
  259. package/src/types/core.ts +683 -0
  260. package/src/types/middleware-types.ts +419 -0
  261. package/src/types/request-response-augmentation.ts +489 -0
  262. package/src/types/route-inference.ts +357 -0
  263. package/src/types.ts +2049 -0
  264. package/src/url.ts +126 -0
  265. package/src/utils/index.ts +1 -0
  266. package/src/utils/query-preservation.ts +201 -0
  267. package/src/utils.ts +327 -0
  268. package/src/validation/validator.ts +685 -0
  269. package/src/websocket/clustering.ts +762 -0
@@ -0,0 +1,753 @@
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)
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
+ }