@chidchanun/bcp 0.2.5 → 0.2.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "framework": "bcp",
4
- "versionTarget": "0.2.5",
4
+ "versionTarget": "0.2.7",
5
5
  "releaseState": "unreleased",
6
6
  "sections": [
7
7
  {
@@ -35,12 +35,13 @@
35
35
  },
36
36
  {
37
37
  "id": "authentication",
38
- "title": "Authentication",
39
- "description": "Authentication Platform v2, revocable session stores, guest/auth route guards and JWT cookie sessions.",
38
+ "title": "Authentication & Authorization",
39
+ "description": "Authentication Platform v2, revocable sessions, permission/policy authorization, route guards and CSRF protection.",
40
40
  "pages": [
41
41
  { "route": "/docs/authentication", "source": "authentication.md", "title": "Authentication" },
42
42
  { "route": "/docs/auth-session-store", "source": "auth-session-store.md", "title": "Auth Session Stores" },
43
43
  { "route": "/docs/auth-route-guards", "source": "auth-route-guards.md", "title": "Auth Route Guards" },
44
+ { "route": "/docs/authorization-security", "source": "authorization-security.md", "title": "Authorization & Security v2" },
44
45
  { "route": "/docs/session-auth", "source": "session-auth.md", "title": "JWT Sessions" }
45
46
  ]
46
47
  },
@@ -56,11 +57,12 @@
56
57
  {
57
58
  "id": "runtime",
58
59
  "title": "Runtime & Infrastructure",
59
- "description": "Middleware, hydration, logging, caching, security and production hardening.",
60
+ "description": "Middleware, observability, logging, caching, security and production hardening.",
60
61
  "pages": [
61
62
  { "route": "/docs/middleware", "source": "middleware.md", "title": "Middleware" },
62
63
  { "route": "/docs/hydration", "source": "hydration.md", "title": "Hydration" },
63
- { "route": "/docs/development-logging", "source": "development-logging.md", "title": "Logging & Observability" },
64
+ { "route": "/docs/development-logging", "source": "development-logging.md", "title": "Logging" },
65
+ { "route": "/docs/observability", "source": "observability.md", "title": "Observability Platform v2" },
64
66
  { "route": "/docs/caching", "source": "caching.md", "title": "Caching" },
65
67
  { "route": "/docs/security", "source": "security.md", "title": "Security" },
66
68
  { "route": "/docs/production-hardening", "source": "production-hardening.md", "title": "Production Hardening" }
@@ -106,7 +108,9 @@
106
108
  }
107
109
  ],
108
110
  "releases": [
109
- { "route": "/releases/0.2.5", "source": "releases/0.2.5.md", "version": "0.2.5", "state": "unreleased" },
111
+ { "route": "/releases/0.2.7", "source": "releases/0.2.7.md", "version": "0.2.7", "state": "unreleased" },
112
+ { "route": "/releases/0.2.6", "source": "releases/0.2.6.md", "version": "0.2.6" },
113
+ { "route": "/releases/0.2.5", "source": "releases/0.2.5.md", "version": "0.2.5" },
110
114
  { "route": "/releases/0.2.4", "source": "releases/0.2.4.md", "version": "0.2.4" },
111
115
  { "route": "/releases/0.2.3", "source": "releases/0.2.3.md", "version": "0.2.3" },
112
116
  { "route": "/releases/0.2.2", "source": "releases/0.2.2.md", "version": "0.2.2" },
@@ -0,0 +1,445 @@
1
+ # Observability Platform v2
2
+
3
+ BCP Framework `0.2.7` adds a server-only observability layer through `bcp/observability`.
4
+
5
+ The platform is dependency-free and provides:
6
+
7
+ - in-process counters, gauges and histograms,
8
+ - Prometheus text exposition,
9
+ - HTTP request count and duration middleware,
10
+ - health/readiness check registration,
11
+ - health responses with HTTP `200` / `503` semantics.
12
+
13
+ It complements the existing structured logger rather than replacing it.
14
+
15
+ ## Metrics registry
16
+
17
+ Create one application-level registry:
18
+
19
+ ```ts
20
+ import {
21
+ createMetricsRegistry,
22
+ } from "bcp/observability";
23
+
24
+ export const metrics =
25
+ createMetricsRegistry();
26
+ ```
27
+
28
+ ### Counter
29
+
30
+ ```ts
31
+ const jobs =
32
+ metrics.counter(
33
+ "app_jobs_total",
34
+ {
35
+ help:
36
+ "Jobs processed by the application.",
37
+ labelNames: [
38
+ "queue",
39
+ ],
40
+ }
41
+ );
42
+
43
+ jobs.inc(
44
+ 1,
45
+ {
46
+ queue: "email",
47
+ }
48
+ );
49
+ ```
50
+
51
+ Counters cannot be decreased.
52
+
53
+ ### Gauge
54
+
55
+ ```ts
56
+ const workers =
57
+ metrics.gauge(
58
+ "app_active_workers",
59
+ {
60
+ labelNames: [
61
+ "pool",
62
+ ],
63
+ }
64
+ );
65
+
66
+ workers.set(
67
+ 4,
68
+ {
69
+ pool: "default",
70
+ }
71
+ );
72
+ workers.inc(
73
+ 1,
74
+ {
75
+ pool: "default",
76
+ }
77
+ );
78
+ workers.dec(
79
+ 1,
80
+ {
81
+ pool: "default",
82
+ }
83
+ );
84
+ ```
85
+
86
+ ### Histogram
87
+
88
+ ```ts
89
+ const queryDuration =
90
+ metrics.histogram(
91
+ "app_query_duration_seconds",
92
+ {
93
+ labelNames: [
94
+ "operation",
95
+ ],
96
+ buckets: [
97
+ 0.01,
98
+ 0.05,
99
+ 0.1,
100
+ 0.5,
101
+ 1,
102
+ ],
103
+ }
104
+ );
105
+
106
+ queryDuration.observe(
107
+ 0.032,
108
+ {
109
+ operation: "users.list",
110
+ }
111
+ );
112
+ ```
113
+
114
+ If no buckets are supplied, BCP uses a general-purpose HTTP/runtime latency bucket set.
115
+
116
+ ## Label contract
117
+
118
+ Metric label names are declared when the metric is registered. Every update must provide exactly that set of labels.
119
+
120
+ This fails intentionally:
121
+
122
+ ```ts
123
+ const requests =
124
+ metrics.counter(
125
+ "app_requests_total",
126
+ {
127
+ labelNames: [
128
+ "method",
129
+ ],
130
+ }
131
+ );
132
+
133
+ // Missing the declared method label.
134
+ requests.inc();
135
+ ```
136
+
137
+ BCP does not automatically attach request paths, user IDs, session IDs, emails or other high-cardinality identifiers to metrics.
138
+
139
+ Avoid labels whose value set grows without a small bound.
140
+
141
+ Good examples:
142
+
143
+ ```text
144
+ method
145
+ status
146
+ queue
147
+ provider
148
+ operation
149
+ ```
150
+
151
+ Risky examples:
152
+
153
+ ```text
154
+ user_id
155
+ session_id
156
+ raw_url
157
+ email
158
+ request_id
159
+ ```
160
+
161
+ ## Prometheus exposition
162
+
163
+ Use `createMetricsResponse()` from an API route:
164
+
165
+ ```ts
166
+ import {
167
+ createMetricsResponse,
168
+ } from "bcp/observability";
169
+ import {
170
+ metrics,
171
+ } from "@/lib/observability";
172
+
173
+ export function GET() {
174
+ return createMetricsResponse(
175
+ metrics
176
+ );
177
+ }
178
+ ```
179
+
180
+ Typical output:
181
+
182
+ ```text
183
+ # HELP app_requests_total Application requests.
184
+ # TYPE app_requests_total counter
185
+ app_requests_total{method="GET"} 42
186
+ ```
187
+
188
+ The response content type is compatible with Prometheus text exposition and uses `Cache-Control: no-store`.
189
+
190
+ ### Protect the metrics endpoint
191
+
192
+ Metrics frequently expose operational information. Do not assume `/metrics` should be public.
193
+
194
+ Protect it using your deployment network, reverse proxy, authorization policy, internal service routing or another appropriate boundary.
195
+
196
+ ## HTTP request metrics middleware
197
+
198
+ Create middleware from the same application registry:
199
+
200
+ ```ts
201
+ import {
202
+ createMetricsRegistry,
203
+ createRequestMetricsMiddleware,
204
+ } from "bcp/observability";
205
+
206
+ export const metrics =
207
+ createMetricsRegistry();
208
+
209
+ export const requestMetrics =
210
+ createRequestMetricsMiddleware(
211
+ metrics
212
+ );
213
+ ```
214
+
215
+ Then include it in the application middleware pipeline:
216
+
217
+ ```ts
218
+ import {
219
+ requestMetrics,
220
+ } from "@/lib/observability";
221
+
222
+ export const middleware = [
223
+ requestMetrics,
224
+ ];
225
+ ```
226
+
227
+ Default series:
228
+
229
+ ```text
230
+ bcp_http_requests_total{method,status}
231
+ bcp_http_request_duration_seconds{method,status}
232
+ ```
233
+
234
+ The default middleware intentionally does not label requests by raw path. This avoids generating one metric series per dynamic URL or identifier.
235
+
236
+ Customize the prefix:
237
+
238
+ ```ts
239
+ createRequestMetricsMiddleware(
240
+ metrics,
241
+ {
242
+ prefix: "shop",
243
+ }
244
+ );
245
+ ```
246
+
247
+ This produces metrics beginning with `shop_`.
248
+
249
+ You can disable the method or status labels:
250
+
251
+ ```ts
252
+ createRequestMetricsMiddleware(
253
+ metrics,
254
+ {
255
+ includeMethod: false,
256
+ includeStatus: false,
257
+ }
258
+ );
259
+ ```
260
+
261
+ ## Health and readiness checks
262
+
263
+ Create an application-level health registry:
264
+
265
+ ```ts
266
+ import {
267
+ createHealthRegistry,
268
+ } from "bcp/observability";
269
+
270
+ export const health =
271
+ createHealthRegistry();
272
+ ```
273
+
274
+ Register dependencies:
275
+
276
+ ```ts
277
+ health.register(
278
+ "database",
279
+ async () => {
280
+ await db.query(
281
+ "SELECT 1"
282
+ );
283
+
284
+ return {
285
+ ok: true,
286
+ detail: "connected",
287
+ };
288
+ }
289
+ );
290
+ ```
291
+
292
+ A check may return:
293
+
294
+ ```ts
295
+ true
296
+ false
297
+ ```
298
+
299
+ or:
300
+
301
+ ```ts
302
+ {
303
+ ok: true,
304
+ detail: "connected",
305
+ }
306
+ ```
307
+
308
+ ### Health endpoint
309
+
310
+ ```ts
311
+ import {
312
+ health,
313
+ } from "@/lib/observability";
314
+
315
+ export function GET() {
316
+ return health.response();
317
+ }
318
+ ```
319
+
320
+ Healthy response:
321
+
322
+ ```text
323
+ HTTP 200
324
+ ```
325
+
326
+ If any registered check fails:
327
+
328
+ ```text
329
+ HTTP 503
330
+ ```
331
+
332
+ The JSON report contains:
333
+
334
+ ```ts
335
+ {
336
+ ok: boolean;
337
+ status: "healthy" | "unhealthy";
338
+ checkedAt: string;
339
+ checks: Array<{
340
+ name: string;
341
+ ok: boolean;
342
+ durationMs: number;
343
+ detail?: string;
344
+ }>;
345
+ }
346
+ ```
347
+
348
+ ## Health check timeout
349
+
350
+ The default timeout is 5 seconds per check.
351
+
352
+ Override it when registering a check:
353
+
354
+ ```ts
355
+ health.register(
356
+ "database",
357
+ checkDatabase,
358
+ {
359
+ timeoutMs: 1500,
360
+ }
361
+ );
362
+ ```
363
+
364
+ A timed-out or thrown check is reported as unhealthy instead of rejecting the complete health report.
365
+
366
+ Timeout does not cancel the underlying application operation. Provider-specific cancellation remains the responsibility of the check implementation.
367
+
368
+ ## Liveness vs readiness
369
+
370
+ Use lightweight liveness checks for whether the process/runtime is functioning.
371
+
372
+ Use readiness checks for dependencies required before receiving traffic, for example:
373
+
374
+ ```text
375
+ database
376
+ cache
377
+ message queue
378
+ critical upstream API
379
+ ```
380
+
381
+ A common deployment structure is:
382
+
383
+ ```text
384
+ /api/health/live
385
+ /api/health/ready
386
+ /metrics
387
+ ```
388
+
389
+ BCP provides the primitives but does not force endpoint names.
390
+
391
+ ## Process-local scope
392
+
393
+ The built-in metrics registry is process-local.
394
+
395
+ With multiple Node processes or containers:
396
+
397
+ ```text
398
+ instance A -> its own registry
399
+ instance B -> its own registry
400
+ instance C -> its own registry
401
+ ```
402
+
403
+ Prometheus-style monitoring should scrape each instance, or deployment infrastructure should aggregate metrics externally.
404
+
405
+ BCP `0.2.7` does not provide distributed metric aggregation or an OpenTelemetry exporter.
406
+
407
+ ## Relationship to logging
408
+
409
+ Structured logs remain available through `bcp/server`:
410
+
411
+ ```ts
412
+ import {
413
+ logger,
414
+ } from "bcp/server";
415
+ ```
416
+
417
+ Use logs for event detail and debugging, and metrics for aggregate system behavior.
418
+
419
+ A typical production model is:
420
+
421
+ ```text
422
+ structured logs
423
+ +
424
+ metrics
425
+ +
426
+ health/readiness
427
+ ```
428
+
429
+ ## Server-only boundary
430
+
431
+ `bcp/observability` is server-only.
432
+
433
+ Do not import it into client components or browser islands. The package browser export maps to BCP's server-only guard.
434
+
435
+ ## Release scope
436
+
437
+ `0.2.7` intentionally does not add:
438
+
439
+ - distributed tracing,
440
+ - OpenTelemetry exporters,
441
+ - hosted monitoring integrations,
442
+ - automatic raw-route labels,
443
+ - cross-process metric aggregation.
444
+
445
+ Those can be layered on later without changing the metric/health application contract introduced here.
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "framework": "bcp",
4
- "version": "0.2.5",
4
+ "version": "0.2.7",
5
5
  "releaseState": "unreleased",
6
- "baseline": "authentication-platform-v2",
6
+ "baseline": "observability-platform-v2",
7
7
  "runtime": {
8
8
  "node": ">=24.11.0",
9
9
  "react": "19",
@@ -19,6 +19,7 @@
19
19
  "bcp/error",
20
20
  "bcp/database",
21
21
  "bcp/auth",
22
+ "bcp/observability",
22
23
  "bcp/server",
23
24
  "bcp/server-only",
24
25
  "bcp/middleware"
@@ -53,6 +54,17 @@
53
54
  "authLogoutAll": true,
54
55
  "authIdleTimeout": true,
55
56
  "authGuestGuard": true,
57
+ "authorizationSecurityV2": true,
58
+ "permissionAuthorization": true,
59
+ "authorizationPolicies": true,
60
+ "permissionRouteGuards": true,
61
+ "sameOriginProtection": true,
62
+ "csrfProtection": true,
63
+ "observabilityPlatformV2": true,
64
+ "metricsRegistry": true,
65
+ "prometheusMetrics": true,
66
+ "requestMetricsMiddleware": true,
67
+ "healthChecks": true,
56
68
  "databaseMigrations": true,
57
69
  "databaseAdapterContract": true,
58
70
  "databasePostgresql": true,
@@ -92,7 +104,7 @@
92
104
  "s3-compatible"
93
105
  ],
94
106
  "compatibility": {
95
- "previousBaseline": "0.2.4",
107
+ "previousBaseline": "0.2.6",
96
108
  "intentionalBreakingChangesFromPreviousBaseline": false,
97
109
  "migrationGuide": "migration-0.2.md"
98
110
  },
@@ -107,7 +119,9 @@
107
119
  "applicationPackaging": "application-packaging.md",
108
120
  "authentication": "authentication.md",
109
121
  "authSessionStore": "auth-session-store.md",
122
+ "authorizationSecurity": "authorization-security.md",
123
+ "observability": "observability.md",
110
124
  "migrationGuide": "migration-0.2.md",
111
- "releaseNotes": "releases/0.2.5.md"
125
+ "releaseNotes": "releases/0.2.7.md"
112
126
  }
113
127
  }