@stacksjs/bun-router 0.0.12 → 0.0.14

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