@onebun/metrics 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -242,4 +242,8 @@ The metrics endpoint is compatible with:
242
242
  - **Prometheus** - For scraping and storage
243
243
  - **Grafana** - For visualization and dashboards
244
244
  - **AlertManager** - For alerting based on metrics
245
- - Any other Prometheus-compatible monitoring solution
245
+ - Any other Prometheus-compatible monitoring solution
246
+
247
+ ## License
248
+
249
+ [LGPL-3.0](../../LICENSE)
package/package.json CHANGED
@@ -1,9 +1,19 @@
1
1
  {
2
2
  "name": "@onebun/metrics",
3
- "version": "0.1.0",
4
- "description": "Prometheus-compatible metrics module for OneBun framework",
3
+ "version": "0.1.1",
4
+ "description": "Prometheus-compatible metrics for OneBun framework - HTTP, system, and custom metrics",
5
5
  "license": "LGPL-3.0",
6
6
  "author": "RemRyahirev",
7
+ "keywords": [
8
+ "onebun",
9
+ "metrics",
10
+ "prometheus",
11
+ "monitoring",
12
+ "observability",
13
+ "bun",
14
+ "typescript",
15
+ "effect"
16
+ ],
7
17
  "repository": {
8
18
  "type": "git",
9
19
  "url": "git+https://github.com/RemRyahirev/onebun.git",
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Documentation Examples Tests for @onebun/metrics
3
+ *
4
+ * This file tests code examples from:
5
+ * - packages/metrics/README.md
6
+ * - docs/api/metrics.md
7
+ */
8
+
9
+ import {
10
+ describe,
11
+ it,
12
+ expect,
13
+ beforeEach,
14
+ afterEach,
15
+ } from 'bun:test';
16
+ import { Effect } from 'effect';
17
+
18
+ import type { MetricsService as MetricsServiceInterface } from './metrics.service';
19
+
20
+ import {
21
+ MeasureTime,
22
+ CountCalls,
23
+ MetricsMiddleware,
24
+ createMetricsService,
25
+ } from './';
26
+
27
+ // Helper to create metrics service for tests
28
+ const getMetricsService = (): MetricsServiceInterface => {
29
+ return Effect.runSync(createMetricsService({ prefix: 'test_' }));
30
+ };
31
+
32
+ describe('Metrics README Examples', () => {
33
+ describe('Method Decorators (README)', () => {
34
+ it('should have @MeasureTime decorator available', () => {
35
+ // From README: Method Decorators - @MeasureTime
36
+ expect(MeasureTime).toBeDefined();
37
+ expect(typeof MeasureTime).toBe('function');
38
+ });
39
+
40
+ it('should have @CountCalls decorator available', () => {
41
+ // From README: Method Decorators - @CountCalls
42
+ expect(CountCalls).toBeDefined();
43
+ expect(typeof CountCalls).toBe('function');
44
+ });
45
+
46
+ it('should use decorators on controller methods', () => {
47
+ // From README: Method Decorators example
48
+ class ApiController {
49
+ @MeasureTime('heavy_operation_duration')
50
+ @CountCalls('heavy_operation_calls')
51
+ async heavyOperation(): Promise<unknown> {
52
+ // This method's execution time and call count will be automatically tracked
53
+ return { success: true };
54
+ }
55
+ }
56
+
57
+ expect(ApiController).toBeDefined();
58
+ expect(typeof new ApiController().heavyOperation).toBe('function');
59
+ });
60
+ });
61
+
62
+ describe('Configuration Options (README)', () => {
63
+ it('should define valid metrics options', () => {
64
+ // From README: Configuration Options
65
+ const metricsOptions = {
66
+ // Enable/disable metrics collection (default: true)
67
+ enabled: true,
68
+
69
+ // HTTP path for metrics endpoint (default: '/metrics')
70
+ path: '/metrics',
71
+
72
+ // Default labels for all metrics
73
+ defaultLabels: {
74
+ service: 'my-service',
75
+ environment: 'development',
76
+ },
77
+
78
+ // Enable HTTP request metrics (default: true)
79
+ collectHttpMetrics: true,
80
+
81
+ // Enable system metrics (default: true)
82
+ collectSystemMetrics: true,
83
+
84
+ // Enable GC metrics (default: true)
85
+ collectGcMetrics: true,
86
+
87
+ // System metrics collection interval in ms (default: 5000)
88
+ systemMetricsInterval: 5000,
89
+
90
+ // Metric name prefix (default: 'onebun_')
91
+ prefix: 'myapp_',
92
+
93
+ // HTTP duration histogram buckets
94
+ httpDurationBuckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10],
95
+ };
96
+
97
+ expect(metricsOptions.enabled).toBe(true);
98
+ expect(metricsOptions.path).toBe('/metrics');
99
+ expect(metricsOptions.prefix).toBe('myapp_');
100
+ expect(metricsOptions.systemMetricsInterval).toBe(5000);
101
+ });
102
+ });
103
+ });
104
+
105
+ describe('Metrics API Documentation Examples', () => {
106
+ let metricsService: MetricsServiceInterface;
107
+
108
+ beforeEach(() => {
109
+ metricsService = getMetricsService();
110
+ metricsService.clear();
111
+ });
112
+
113
+ afterEach(() => {
114
+ metricsService.clear();
115
+ });
116
+
117
+ describe('MetricsService (docs/api/metrics.md)', () => {
118
+ it('should create metrics service instance', () => {
119
+ // From docs: MetricsService usage
120
+ // Use createMetricsService() to get an Effect that yields the service
121
+ const service = Effect.runSync(createMetricsService());
122
+
123
+ expect(service).toBeDefined();
124
+ expect(typeof service.createCounter).toBe('function');
125
+ expect(typeof service.createGauge).toBe('function');
126
+ expect(typeof service.createHistogram).toBe('function');
127
+ });
128
+ });
129
+
130
+ describe('Counter (docs/api/metrics.md)', () => {
131
+ it('should create and use counter', async () => {
132
+ // From docs: Counter example
133
+ // createCounter returns a prom-client Counter object
134
+ const requestsCounter = metricsService.createCounter({
135
+ name: 'requests_total',
136
+ help: 'Total requests',
137
+ labelNames: ['method', 'endpoint'],
138
+ });
139
+
140
+ // Increment counter using prom-client API
141
+ requestsCounter.inc({ method: 'GET', endpoint: '/api/users' });
142
+
143
+ const metrics = await metricsService.getMetrics();
144
+ expect(metrics).toContain('requests_total');
145
+ });
146
+ });
147
+
148
+ describe('Gauge (docs/api/metrics.md)', () => {
149
+ it('should create and use gauge', async () => {
150
+ // From docs: Gauge example
151
+ // createGauge returns a prom-client Gauge object
152
+ const connectionsGauge = metricsService.createGauge({
153
+ name: 'active_connections',
154
+ help: 'Active connections',
155
+ });
156
+
157
+ // Use prom-client Gauge API
158
+ connectionsGauge.set(42);
159
+ connectionsGauge.inc();
160
+ connectionsGauge.dec();
161
+
162
+ const metrics = await metricsService.getMetrics();
163
+ expect(metrics).toContain('active_connections');
164
+ });
165
+ });
166
+
167
+ describe('Histogram (docs/api/metrics.md)', () => {
168
+ it('should create and use histogram', async () => {
169
+ // From docs: Histogram example
170
+ // createHistogram returns a prom-client Histogram object
171
+ const responseTimeHistogram = metricsService.createHistogram({
172
+ name: 'response_time_seconds',
173
+ help: 'Response time in seconds',
174
+ buckets: [0.1, 0.5, 1, 2, 5],
175
+ });
176
+
177
+ // Observe value using prom-client API
178
+ responseTimeHistogram.observe(1.23);
179
+
180
+ const metrics = await metricsService.getMetrics();
181
+ expect(metrics).toContain('response_time_seconds');
182
+ });
183
+ });
184
+
185
+ describe('Custom Metrics in Services (README)', () => {
186
+ it('should register custom metrics pattern', async () => {
187
+ // From README: Custom Metrics in Services example
188
+ // Create custom metrics - returns prom-client metric objects
189
+ const loginsCounter = metricsService.createCounter({
190
+ name: 'user_logins_total',
191
+ help: 'Total number of user logins',
192
+ labelNames: ['method', 'status'],
193
+ });
194
+
195
+ const activeUsersGauge = metricsService.createGauge({
196
+ name: 'active_users',
197
+ help: 'Number of currently active users',
198
+ });
199
+
200
+ // Simulate login using prom-client API
201
+ loginsCounter.inc({ method: 'password', status: 'success' });
202
+ activeUsersGauge.inc();
203
+
204
+ const metrics = await metricsService.getMetrics();
205
+ expect(metrics).toContain('user_logins_total');
206
+ expect(metrics).toContain('active_users');
207
+ });
208
+ });
209
+
210
+ describe('Service Metrics Pattern (docs/api/metrics.md)', () => {
211
+ it('should implement payment service metrics pattern', async () => {
212
+ // From docs: Service Metrics Pattern
213
+ // Register custom metrics on service init
214
+ const paymentsCounter = metricsService.createCounter({
215
+ name: 'payments_processed_total',
216
+ help: 'Total number of payments processed',
217
+ labelNames: ['status', 'method'],
218
+ });
219
+
220
+ const processingHistogram = metricsService.createHistogram({
221
+ name: 'payment_processing_seconds',
222
+ help: 'Payment processing duration',
223
+ buckets: [0.1, 0.5, 1, 2, 5, 10],
224
+ });
225
+
226
+ const queueGauge = metricsService.createGauge({
227
+ name: 'payment_queue_size',
228
+ help: 'Current payment queue size',
229
+ });
230
+
231
+ // Simulate payment processing
232
+ const startTime = Date.now();
233
+
234
+ // Record success using prom-client API
235
+ paymentsCounter.inc({ status: 'success', method: 'credit_card' });
236
+
237
+ // Record duration
238
+ const duration = (Date.now() - startTime) / 1000;
239
+ processingHistogram.observe(duration);
240
+
241
+ // Set queue size
242
+ queueGauge.set(5);
243
+
244
+ const metrics = await metricsService.getMetrics();
245
+ expect(metrics).toContain('payments_processed_total');
246
+ expect(metrics).toContain('payment_processing_seconds');
247
+ expect(metrics).toContain('payment_queue_size');
248
+ });
249
+ });
250
+
251
+ describe('Built-in Metrics Format (docs/api/metrics.md)', () => {
252
+ it('should describe HTTP metrics format', () => {
253
+ // From docs: Built-in Metrics - HTTP Metrics
254
+ // These are the metrics that would be automatically collected
255
+ const expectedMetrics = [
256
+ 'http_request_duration_seconds_bucket',
257
+ 'http_request_duration_seconds_sum',
258
+ 'http_request_duration_seconds_count',
259
+ 'http_requests_total',
260
+ ];
261
+
262
+ // Just verifying the expected metric names
263
+ expectedMetrics.forEach((metric) => {
264
+ expect(typeof metric).toBe('string');
265
+ });
266
+ });
267
+
268
+ it('should describe System metrics format', () => {
269
+ // From docs: Built-in Metrics - System Metrics
270
+ const expectedMetrics = [
271
+ 'process_cpu_seconds_total',
272
+ 'process_cpu_user_seconds_total',
273
+ 'process_cpu_system_seconds_total',
274
+ 'process_memory_bytes',
275
+ 'nodejs_eventloop_lag_seconds',
276
+ 'nodejs_active_handles_total',
277
+ 'nodejs_active_requests_total',
278
+ ];
279
+
280
+ // Just verifying the expected metric names
281
+ expectedMetrics.forEach((metric) => {
282
+ expect(typeof metric).toBe('string');
283
+ });
284
+ });
285
+ });
286
+
287
+ describe('Middleware (docs/api/metrics.md)', () => {
288
+ it('should have MetricsMiddleware class', () => {
289
+ expect(MetricsMiddleware).toBeDefined();
290
+ expect(typeof MetricsMiddleware).toBe('function');
291
+ });
292
+ });
293
+ });
294
+
295
+ describe('Prometheus Format (docs/api/metrics.md)', () => {
296
+ it('should produce Prometheus-compatible output', async () => {
297
+ const service = getMetricsService();
298
+ service.clear();
299
+
300
+ const counter = service.createCounter({
301
+ name: 'test_counter',
302
+ help: 'Test counter',
303
+ });
304
+
305
+ // Use prom-client API to increment
306
+ counter.inc();
307
+
308
+ const metrics = await service.getMetrics();
309
+
310
+ // Prometheus format should contain HELP and TYPE comments
311
+ expect(metrics).toContain('# HELP');
312
+ expect(metrics).toContain('# TYPE');
313
+ expect(metrics).toContain('test_counter');
314
+ });
315
+ });