@chidchanun/bcp 0.2.15 → 0.2.17

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.
@@ -0,0 +1,402 @@
1
+ # Observability Platform v3
2
+
3
+ BCP Framework `0.2.17` extends `bcp/observability` from metrics/health into request-to-background distributed tracing and correlation.
4
+
5
+ The existing Observability Platform v2 APIs remain available. v3 adds tracing without changing the public entrypoint.
6
+
7
+ ## Goals
8
+
9
+ Observability v3 gives one logical operation a stable trace/correlation identity as it crosses HTTP middleware, background work, workflows, transactional events, realtime delivery and cache operations.
10
+
11
+ ```text
12
+ HTTP request
13
+ |
14
+ +-- database
15
+ |
16
+ +-- outbox event
17
+ | |
18
+ | +-- durable job
19
+ | |
20
+ | +-- workflow
21
+ | |
22
+ | +-- realtime
23
+ |
24
+ +-- cache
25
+
26
+ traceId + correlationId follow the operation.
27
+ ```
28
+
29
+ BCP provides propagation primitives instead of coupling tracing to one vendor or collector.
30
+
31
+ ## Create a tracer
32
+
33
+ ```ts
34
+ import {
35
+ createMemoryTraceSpanExporter,
36
+ createTracer,
37
+ } from "bcp/observability";
38
+
39
+ const exporter =
40
+ createMemoryTraceSpanExporter();
41
+
42
+ export const tracer =
43
+ createTracer({
44
+ exporter,
45
+ serviceName: "api",
46
+ defaultAttributes: {
47
+ environment: "production",
48
+ },
49
+ });
50
+ ```
51
+
52
+ `createMemoryTraceSpanExporter()` is mainly useful for tests and local inspection. Production applications can implement `TraceSpanExporter` and forward completed spans to an OpenTelemetry bridge, APM agent, log pipeline or another collector.
53
+
54
+ ## Root and child spans
55
+
56
+ ```ts
57
+ await tracer.withSpan(
58
+ "order.checkout",
59
+ async span => {
60
+ span.setAttribute(
61
+ "order.id",
62
+ order.id
63
+ );
64
+
65
+ await tracer.withSpan(
66
+ "database.order.insert",
67
+ async () => {
68
+ await createOrder();
69
+ },
70
+ {
71
+ kind: "client",
72
+ }
73
+ );
74
+ }
75
+ );
76
+ ```
77
+
78
+ Nested `withSpan()` calls automatically reuse the active `traceId`, create a new `spanId`, and set the active span as `parentSpanId`.
79
+
80
+ The active trace context uses Node `AsyncLocalStorage`, so it flows through normal awaited asynchronous work.
81
+
82
+ ## Span kinds
83
+
84
+ Supported kinds are:
85
+
86
+ ```text
87
+ internal
88
+ server
89
+ client
90
+ producer
91
+ consumer
92
+ ```
93
+
94
+ Examples:
95
+
96
+ - HTTP inbound request: `server`
97
+ - SQL/HTTP/cache call: `client`
98
+ - queue/outbox publish: `producer`
99
+ - queue/event handler: `consumer`
100
+ - framework/application operation: `internal`
101
+
102
+ ## Span attributes and events
103
+
104
+ ```ts
105
+ await tracer.withSpan(
106
+ "payment.charge",
107
+ async span => {
108
+ span.setAttribute(
109
+ "payment.provider",
110
+ "example"
111
+ );
112
+
113
+ span.addEvent(
114
+ "payment.requested",
115
+ {
116
+ amount: 1490,
117
+ }
118
+ );
119
+ }
120
+ );
121
+ ```
122
+
123
+ Attribute values are intentionally limited to strings, finite numbers and booleans.
124
+
125
+ If a `withSpan()` callback throws, BCP marks the span as `error`, records an `exception` event and rethrows the original application error.
126
+
127
+ ## HTTP request tracing
128
+
129
+ Use the Middleware System v2 integration:
130
+
131
+ ```ts
132
+ import {
133
+ createRequestTracingMiddleware,
134
+ } from "bcp/observability";
135
+
136
+ export const middleware =
137
+ createRequestTracingMiddleware(
138
+ tracer
139
+ );
140
+ ```
141
+
142
+ The middleware:
143
+
144
+ 1. Reads an incoming W3C `traceparent` header when valid.
145
+ 2. Preserves the incoming `x-correlation-id` when supplied.
146
+ 3. Starts a server span.
147
+ 4. Makes the span context available through `AsyncLocalStorage`.
148
+ 5. Adds the current `traceparent` and `x-correlation-id` to the response.
149
+
150
+ The default span name is `HTTP <METHOD>`. Applications can provide a lower-cardinality route name:
151
+
152
+ ```ts
153
+ createRequestTracingMiddleware(
154
+ tracer,
155
+ {
156
+ spanName:
157
+ request =>
158
+ `route ${request.nextUrl.pathname}`,
159
+ }
160
+ );
161
+ ```
162
+
163
+ Avoid putting unbounded IDs directly into metric labels. Span names can also become labels when explicitly configured in trace metrics.
164
+
165
+ ## W3C trace context
166
+
167
+ BCP supports W3C version `00` `traceparent` values:
168
+
169
+ ```text
170
+ 00-<32 hex trace id>-<16 hex span id>-<2 hex flags>
171
+ ```
172
+
173
+ Helpers:
174
+
175
+ ```ts
176
+ import {
177
+ extractTraceHeaders,
178
+ formatTraceparent,
179
+ injectTraceHeaders,
180
+ parseTraceparent,
181
+ } from "bcp/observability";
182
+ ```
183
+
184
+ Example outbound request:
185
+
186
+ ```ts
187
+ const headers =
188
+ new Headers();
189
+
190
+ injectTraceHeaders(
191
+ headers
192
+ );
193
+
194
+ await fetch(
195
+ serviceUrl,
196
+ {
197
+ headers,
198
+ }
199
+ );
200
+ ```
201
+
202
+ `injectTraceHeaders()` uses the current context by default.
203
+
204
+ ## Propagate through jobs, workflows and events
205
+
206
+ For non-HTTP transports use a trace carrier:
207
+
208
+ ```ts
209
+ import {
210
+ createTraceCarrier,
211
+ } from "bcp/observability";
212
+
213
+ const trace =
214
+ createTraceCarrier();
215
+
216
+ await jobs.enqueue(
217
+ "order.process",
218
+ {
219
+ orderId,
220
+ trace,
221
+ }
222
+ );
223
+ ```
224
+
225
+ Consumer:
226
+
227
+ ```ts
228
+ import {
229
+ runWithTraceCarrier,
230
+ } from "bcp/observability";
231
+
232
+ jobs.register(
233
+ "order.process",
234
+ async ({ payload }) =>
235
+ runWithTraceCarrier(
236
+ payload.trace,
237
+ () =>
238
+ tracer.withSpan(
239
+ "job order.process",
240
+ async () => {
241
+ await processOrder(
242
+ payload.orderId
243
+ );
244
+ },
245
+ {
246
+ kind: "consumer",
247
+ }
248
+ )
249
+ )
250
+ );
251
+ ```
252
+
253
+ The same carrier can be stored in workflow input, outbox/event metadata or realtime payload metadata when the application needs trace continuity across those boundaries.
254
+
255
+ BCP does not mutate durable job/event schemas automatically in `0.2.17`; propagation remains explicit and provider-neutral.
256
+
257
+ ## Correlation IDs
258
+
259
+ Every root trace gets a `correlationId`. Incoming HTTP requests may supply one through:
260
+
261
+ ```text
262
+ x-correlation-id
263
+ ```
264
+
265
+ Access trace fields for structured logs:
266
+
267
+ ```ts
268
+ import {
269
+ getTraceLogFields,
270
+ } from "bcp/observability";
271
+
272
+ logger.info(
273
+ "order created",
274
+ {
275
+ ...getTraceLogFields(),
276
+ orderId,
277
+ }
278
+ );
279
+ ```
280
+
281
+ Output fields are:
282
+
283
+ ```text
284
+ traceId
285
+ spanId
286
+ correlationId
287
+ ```
288
+
289
+ This keeps logs searchable even when the log backend does not understand tracing natively.
290
+
291
+ ## Trace metrics
292
+
293
+ Bridge completed spans into the existing BCP metrics registry:
294
+
295
+ ```ts
296
+ import {
297
+ createCompositeTraceSpanExporter,
298
+ createMetricsRegistry,
299
+ createTraceMetricsExporter,
300
+ createTracer,
301
+ } from "bcp/observability";
302
+
303
+ const metrics =
304
+ createMetricsRegistry();
305
+
306
+ const traceMetrics =
307
+ createTraceMetricsExporter(
308
+ metrics
309
+ );
310
+
311
+ const tracer =
312
+ createTracer({
313
+ exporter:
314
+ createCompositeTraceSpanExporter([
315
+ traceMetrics,
316
+ myProductionExporter,
317
+ ]),
318
+ });
319
+ ```
320
+
321
+ Default metrics:
322
+
323
+ ```text
324
+ bcp_trace_spans_total
325
+ bcp_trace_span_duration_seconds
326
+ ```
327
+
328
+ Default labels are only:
329
+
330
+ ```text
331
+ kind
332
+ status
333
+ ```
334
+
335
+ `includeSpanName: true` adds a `span` label. Use it only when span names are controlled and low-cardinality.
336
+
337
+ ## Exporter failure behavior
338
+
339
+ Span-export errors are intentionally isolated from application execution. A failing exporter does not turn a successful business operation into an application failure.
340
+
341
+ Exporter lifecycle can be closed explicitly:
342
+
343
+ ```ts
344
+ await tracer.shutdown();
345
+ ```
346
+
347
+ Composite exporter shutdown runs in reverse registration order.
348
+
349
+ ## Production topology
350
+
351
+ ```text
352
+ Application
353
+ |
354
+ +-- createTracer()
355
+ | |
356
+ | +-- memory exporter (tests)
357
+ | +-- trace metrics exporter
358
+ | +-- custom production exporter
359
+ |
360
+ +-- createRequestTracingMiddleware()
361
+ |
362
+ +-- createTraceCarrier()/runWithTraceCarrier()
363
+ ```
364
+
365
+ BCP does not install an OpenTelemetry SDK or vendor APM client. Applications own collector/provider dependencies, authentication, batching and transport shutdown.
366
+
367
+ ## Compatibility
368
+
369
+ `0.2.17` is intended to be backward-compatible with `0.2.16`.
370
+
371
+ Existing APIs remain available:
372
+
373
+ ```text
374
+ createMetricsRegistry()
375
+ createMetricsResponse()
376
+ createRequestMetricsMiddleware()
377
+ createHealthRegistry()
378
+ ```
379
+
380
+ The npm-prepared `bcp/observability` runtime is now compiled to `observability.mjs`, matching the hardened runtime packaging already used by cache/jobs/workflow/events/realtime/testing/plugins.
381
+
382
+ ## Validation
383
+
384
+ Before releasing `0.2.17` run:
385
+
386
+ ```bash
387
+ npm run typecheck
388
+ npm run test:unit
389
+ npm run test:integration
390
+ npm run test:e2e
391
+ npm run test:package
392
+ npm run rc:check
393
+ ```
394
+
395
+ Related guides:
396
+
397
+ - [Observability Platform v2](observability.md)
398
+ - [Cache Platform v2](cache-platform-v2.md)
399
+ - [Durable Jobs](durable-jobs.md)
400
+ - [Workflow Orchestration](workflow-orchestration.md)
401
+ - [Transactional Outbox & Events](transactional-outbox-events.md)
402
+ - [Realtime Platform](realtime-platform.md)
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "framework": "bcp",
4
- "version": "0.2.15",
4
+ "version": "0.2.17",
5
5
  "releaseState": "unreleased",
6
- "baseline": "plugin-module-platform",
6
+ "baseline": "observability-platform-v3",
7
7
  "runtime": {
8
8
  "node": ">=24.11.0",
9
9
  "react": "19",
@@ -71,6 +71,17 @@
71
71
  "prometheusMetrics": true,
72
72
  "requestMetricsMiddleware": true,
73
73
  "healthChecks": true,
74
+ "observabilityPlatformV3": true,
75
+ "distributedTracing": true,
76
+ "traceContextPropagation": true,
77
+ "w3cTraceparent": true,
78
+ "correlationIds": true,
79
+ "requestTracingMiddleware": true,
80
+ "memorySpanExporter": true,
81
+ "compositeSpanExporter": true,
82
+ "traceMetricsExporter": true,
83
+ "traceLogFields": true,
84
+ "compiledObservabilityRuntime": true,
74
85
  "backgroundJobsPlatform": true,
75
86
  "jobQueueAdapterContract": true,
76
87
  "inMemoryJobQueue": true,
@@ -148,6 +159,19 @@
148
159
  "pluginServiceRegistry": true,
149
160
  "pluginHookBus": true,
150
161
  "pluginLifecycleRollback": true,
162
+ "cachePlatformV2": true,
163
+ "cacheAdapterContract": true,
164
+ "memoryCacheAdapter": true,
165
+ "redisCacheAdapter": true,
166
+ "cacheLockAdapter": true,
167
+ "redisCacheLockAdapter": true,
168
+ "cacheStampedeProtection": true,
169
+ "cacheLockHeartbeats": true,
170
+ "cacheTtl": true,
171
+ "cacheTagInvalidation": true,
172
+ "cachePathInvalidation": true,
173
+ "cacheMetrics": true,
174
+ "compiledCacheRuntime": true,
151
175
  "databaseMigrations": true,
152
176
  "databaseAdapterContract": true,
153
177
  "databasePostgresql": true,
@@ -187,7 +211,7 @@
187
211
  "s3-compatible"
188
212
  ],
189
213
  "compatibility": {
190
- "previousBaseline": "0.2.14",
214
+ "previousBaseline": "0.2.16",
191
215
  "intentionalBreakingChangesFromPreviousBaseline": false,
192
216
  "migrationGuide": "migration-0.2.md"
193
217
  },
@@ -204,6 +228,7 @@
204
228
  "authSessionStore": "auth-session-store.md",
205
229
  "authorizationSecurity": "authorization-security.md",
206
230
  "observability": "observability.md",
231
+ "observabilityV3": "observability-v3.md",
207
232
  "backgroundJobs": "background-jobs.md",
208
233
  "jobScheduling": "job-scheduling.md",
209
234
  "durableJobs": "durable-jobs.md",
@@ -212,7 +237,8 @@
212
237
  "realtimePlatform": "realtime-platform.md",
213
238
  "testingPlatform": "testing-platform.md",
214
239
  "pluginModulePlatform": "plugin-module-platform.md",
240
+ "cachePlatformV2": "cache-platform-v2.md",
215
241
  "migrationGuide": "migration-0.2.md",
216
- "releaseNotes": "releases/0.2.15.md"
242
+ "releaseNotes": "releases/0.2.17.md"
217
243
  }
218
244
  }
@@ -0,0 +1,147 @@
1
+ # BCP Framework 0.2.16
2
+
3
+ **State:** unreleased
4
+
5
+ ## Cache Platform v2
6
+
7
+ `0.2.16` upgrades the existing `bcp/cache` public entrypoint with provider-neutral asynchronous cache storage, distributed lock contracts, Redis-compatible reference adapters, cache-stampede protection and metrics integration.
8
+
9
+ The existing `cache()`, `dedupe()`, `revalidateTag()` and `revalidatePath()` APIs remain available with their previous process-local behavior.
10
+
11
+ ## New public APIs
12
+
13
+ ```ts
14
+ createCacheStore()
15
+ createMemoryCacheAdapter()
16
+ createMemoryCacheLockAdapter()
17
+ createRedisCacheAdapter()
18
+ createRedisCacheLockAdapter()
19
+ createCacheMetrics()
20
+ ```
21
+
22
+ New public contracts include:
23
+
24
+ ```text
25
+ CacheAdapter
26
+ CacheAdapterEntry
27
+ CacheAdapterSetOptions
28
+ CacheLockAdapter
29
+ CacheStore
30
+ CacheStoreOptions
31
+ CacheStoreSetOptions
32
+ CacheGetOrSetOptions
33
+ CacheStoreStats
34
+ CacheMetricsSink
35
+ CacheMetricsRegistryLike
36
+ RedisCacheCommandClient
37
+ RedisCacheAdapterOptions
38
+ RedisCacheLockAdapterOptions
39
+ ```
40
+
41
+ ## Cache-aside loading
42
+
43
+ `CacheStore.getOrSet()` provides a framework-native cache-aside primitive.
44
+
45
+ Within one process it deduplicates concurrent loaders for the same key. With a shared `CacheLockAdapter`, multiple BCP instances coordinate cache fills through distributed leases.
46
+
47
+ ## Distributed lock behavior
48
+
49
+ The built-in lock implementations support:
50
+
51
+ - acquire with owner identity and TTL
52
+ - compare-and-release
53
+ - optional compare-and-extend
54
+ - heartbeat renewal while a loader is active
55
+ - contention wait/poll against shared cache state
56
+ - explicit timeout failure with `onLockTimeout: "error"`
57
+ - availability-oriented unlocked fallback by default after timeout
58
+
59
+ Distributed locks reduce duplicate cache fill work. They are not a substitute for database transactions or uniqueness constraints protecting business invariants.
60
+
61
+ ## Redis-compatible adapters
62
+
63
+ BCP still does not depend on a Redis package.
64
+
65
+ Applications provide a minimal command client:
66
+
67
+ ```ts
68
+ interface RedisCacheCommandClient {
69
+ sendCommand(
70
+ command: string[]
71
+ ): Promise<unknown>;
72
+ }
73
+ ```
74
+
75
+ The default namespace is `bcp:{cache}` so related keys share a Redis Cluster hash slot.
76
+
77
+ The reference adapter uses Lua for record/index changes, tag invalidation, lock release and lock renewal.
78
+
79
+ Applications remain responsible for Redis authentication, TLS, Cluster/Sentinel configuration, reconnect behavior and connection shutdown.
80
+
81
+ ## TTL and invalidation
82
+
83
+ Cache Store v2 supports:
84
+
85
+ - millisecond TTL via `ttlMs`
86
+ - tag invalidation
87
+ - hierarchical path invalidation
88
+ - adapter-wide clear
89
+ - optional provider entry counts
90
+
91
+ The older `cache()` API continues to accept `revalidate` in seconds for compatibility.
92
+
93
+ ## Observability
94
+
95
+ `createCacheMetrics()` adapts Cache Store events to `bcp/observability`'s existing `MetricsRegistry` contract.
96
+
97
+ Default metric families:
98
+
99
+ ```text
100
+ bcp_cache_operations_total{event="..."}
101
+ bcp_cache_in_flight
102
+ ```
103
+
104
+ ## Published runtime
105
+
106
+ The prepared npm package now compiles `bcp/cache` to:
107
+
108
+ ```text
109
+ packages/client/src/cache.mjs
110
+ ```
111
+
112
+ The prepared package export points runtime imports at this compiled file while preserving `cache.ts` as the public type source.
113
+
114
+ ## Testing
115
+
116
+ The `0.2.16` suite adds coverage for:
117
+
118
+ - TTL expiration
119
+ - tag/path invalidation
120
+ - store statistics
121
+ - local in-flight deduplication
122
+ - cross-store distributed stampede protection
123
+ - lock timeout errors
124
+ - BCP metrics registry integration
125
+ - Redis command/namespace contract
126
+ - compiled prepared-package runtime smoke
127
+
128
+ ## Compatibility
129
+
130
+ `0.2.16` has no intentional breaking changes from `0.2.15`.
131
+
132
+ Existing application calls using the original cache APIs do not need to migrate.
133
+
134
+ ## Release validation
135
+
136
+ Before publishing:
137
+
138
+ ```bash
139
+ npm run typecheck
140
+ npm run test:unit
141
+ npm run test:integration
142
+ npm run test:e2e
143
+ npm run test:package
144
+ npm run rc:check
145
+ ```
146
+
147
+ Tag and publish only the exact commit that passes the complete RC sequence.