@chidchanun/bcp 0.2.13 → 0.2.15

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
@@ -1,10 +1,10 @@
1
1
  # BCP Framework
2
2
 
3
- BCP Framework is a React full-stack framework for file-based routing, SSR, SPA navigation, server data loading, API routes, authentication, authorization, SQL databases, background jobs, scheduling, workflow orchestration, transactional events, realtime delivery, observability, uploads, storage and standalone Node.js deployment.
3
+ BCP Framework is a React full-stack framework for file-based routing, SSR, SPA navigation, server data loading, API routes, authentication, authorization, SQL databases, background jobs, scheduling, workflow orchestration, transactional events, realtime delivery, framework-native testing, plugin/module composition, observability, uploads, storage and standalone Node.js deployment.
4
4
 
5
- > **Development target:** `0.2.13Realtime Platform`
5
+ > **Development target:** `0.2.15Plugin & Module Platform`
6
6
  >
7
- > `0.2.13` remains unreleased until local validation, RC checks, tagging and npm publication complete.
7
+ > `0.2.15` remains unreleased until local validation, RC checks, tagging and npm publication complete.
8
8
 
9
9
  ## Current platform
10
10
 
@@ -25,6 +25,8 @@ BCP Framework is a React full-stack framework for file-based routing, SSR, SPA n
25
25
  | Workflows | Sequential/parallel steps, retries, persisted delays, compensation and run leases |
26
26
  | Events | Transactional outbox, SQL persistence, dispatcher leases, retries, event bus and durable queue handoff |
27
27
  | Realtime | Channels/rooms, presence, broker delivery, WebSocket adapter contract, SSE and heartbeat |
28
+ | Testing | Request/route/page/auth/database/middleware/jobs/workflow/outbox/realtime/SSE test harnesses |
29
+ | Plugins & modules | Dependency ordering, lifecycle hooks, config parsing, service registry and async extension hooks |
28
30
  | Observability | Structured logs, metrics, Prometheus output and health/readiness checks |
29
31
  | Uploads & storage | Multipart streaming, Local/S3-compatible storage and signed URLs |
30
32
  | Caching | Response cache and revalidation primitives |
@@ -73,6 +75,8 @@ import { createJobQueue } from "bcp/jobs";
73
75
  import { createWorkflow } from "bcp/workflow";
74
76
  import { createTransactionalOutbox } from "bcp/events";
75
77
  import { createRealtime } from "bcp/realtime";
78
+ import { createTestApp } from "bcp/testing";
79
+ import { createPluginHost } from "bcp/plugins";
76
80
  import { createMetricsRegistry } from "bcp/observability";
77
81
  ```
78
82
 
@@ -165,10 +169,6 @@ Read more: [Transactional Outbox & Events](docs/transactional-outbox-events.md)
165
169
 
166
170
  ## Realtime Platform — 0.2.13
167
171
 
168
- `0.2.13` adds the server-only `bcp/realtime` public entrypoint.
169
-
170
- Create a hub:
171
-
172
172
  ```ts
173
173
  import {
174
174
  createRealtime,
@@ -178,7 +178,7 @@ export const realtime =
178
178
  createRealtime();
179
179
  ```
180
180
 
181
- ### Channels / rooms
181
+ Join a channel and broadcast:
182
182
 
183
183
  ```ts
184
184
  const connection =
@@ -197,135 +197,254 @@ await realtime.broadcast(
197
197
  );
198
198
  ```
199
199
 
200
- Rooms are represented by channel names. Connections receive broadcasts only for channels they joined.
200
+ BCP intentionally does not install a WebSocket server library. Applications adapt `ws`, uWebSockets.js or another transport to `RealtimeSocket`. SSE is built in through Web `Response`.
201
+
202
+ Read more: [Realtime Platform](docs/realtime-platform.md)
203
+
204
+ ## Testing Platform — 0.2.14
205
+
206
+ `0.2.14` adds the server-only `bcp/testing` entrypoint. It is test-runner neutral and does not add Jest or Vitest as framework dependencies.
201
207
 
202
- ### Presence
208
+ ### Request and route tests
203
209
 
204
210
  ```ts
205
- await connection.join(
206
- "project:7",
207
- {
208
- presence: {
209
- status: "online",
211
+ import {
212
+ createRouteTestHandler,
213
+ createTestApp,
214
+ expectResponse,
215
+ } from "bcp/testing";
216
+
217
+ const handler =
218
+ createRouteTestHandler({
219
+ GET() {
220
+ return {
221
+ ok: true,
222
+ };
210
223
  },
211
- }
212
- );
213
-
214
- const members =
215
- await realtime.members(
216
- "project:7"
217
- );
218
- ```
224
+ });
219
225
 
220
- The built-in memory presence store is process-local. Multi-instance deployments should provide a shared `RealtimePresenceStore`.
226
+ const app =
227
+ createTestApp({
228
+ handler,
229
+ });
221
230
 
222
- ### Authentication and private channels
231
+ const response =
232
+ await app.get(
233
+ "/api/health"
234
+ );
223
235
 
224
- ```ts
225
- const realtime =
226
- createRealtime({
227
- authenticate:
228
- async ({ request }) =>
229
- request
230
- ? loadRealtimeUser(
231
- request
232
- )
233
- : null,
234
-
235
- getUserId:
236
- user => user.id,
237
-
238
- authorizeChannel:
239
- ({ connection, channel }) =>
240
- !channel.startsWith(
241
- "private:"
242
- ) ||
243
- Boolean(
244
- connection.user
245
- ),
236
+ await expectResponse(response)
237
+ .status(200)
238
+ .json({
239
+ ok: true,
246
240
  });
247
241
  ```
248
242
 
249
- ### WebSocket provider contract
243
+ `createTestApp()` keeps an in-memory cookie jar, supports default headers and can send JSON bodies directly.
250
244
 
251
- BCP intentionally does not install `ws`, Socket.IO or another WebSocket server dependency.
245
+ ### Authentication tests
252
246
 
253
- Adapt the selected server/provider to `RealtimeSocket`, then attach it:
247
+ Create a real signed BCP session token instead of a fake test-only user header:
254
248
 
255
249
  ```ts
256
- const connection =
257
- await realtime.attachSocket(
258
- socketAdapter,
250
+ import {
251
+ createTestAuthSession,
252
+ } from "bcp/testing";
253
+
254
+ const session =
255
+ await createTestAuthSession(
259
256
  {
260
- request,
257
+ id: 42,
258
+ role: "admin",
259
+ },
260
+ {
261
+ secret:
262
+ process.env.BCP_SESSION_SECRET,
263
+ store:
264
+ authSessionStore,
261
265
  }
262
266
  );
263
- ```
264
267
 
265
- The JSON socket protocol supports `join`, `leave`, `event` and `ping` messages.
268
+ app.setCookie(
269
+ session.cookieName,
270
+ session.token
271
+ );
272
+ ```
266
273
 
267
- ### Server-Sent Events
274
+ When `store` is provided, the matching server-side auth session record is inserted as well.
268
275
 
269
- SSE is built in through Web `Response`:
276
+ ### Rollback database tests
270
277
 
271
278
  ```ts
272
- export function GET(
273
- request: Request
274
- ) {
275
- return realtime.sse(
276
- "workflow:42",
277
- {
278
- signal:
279
- request.signal,
280
- }
281
- );
282
- }
279
+ import {
280
+ withTestTransaction,
281
+ } from "bcp/testing";
282
+
283
+ await withTestTransaction(
284
+ db,
285
+ async tx => {
286
+ await tx.execute(
287
+ "INSERT INTO users ..."
288
+ );
289
+
290
+ // assertions run here
291
+ }
292
+ );
283
293
  ```
284
294
 
285
- ### Cross-instance delivery
295
+ The callback uses the real BCP transaction and is deliberately rolled back after the test callback succeeds.
286
296
 
287
- `RealtimeBroker` is the provider-neutral pub/sub boundary. The built-in memory broker supports same-process hubs; production applications can implement shared Redis/NATS/etc. brokers without changing hub APIs.
297
+ ### Infrastructure harnesses
288
298
 
289
- ```text
290
- Browser A -> App A ----\
291
- Shared Broker
292
- Browser B -> App B ----/
293
- | |
294
- +---- Shared Presence Store
299
+ ```ts
300
+ import {
301
+ createJobTestHarness,
302
+ createOutboxTestHarness,
303
+ createRealtimeTestHarness,
304
+ createWorkflowTestHarness,
305
+ } from "bcp/testing";
295
306
  ```
296
307
 
297
- ### Heartbeat
308
+ These harnesses use the real platform contracts rather than separate mock implementations.
309
+
310
+ Background jobs:
298
311
 
299
312
  ```ts
300
- const heartbeat =
301
- realtime.startHeartbeat({
302
- intervalMs: 20_000,
303
- });
313
+ const jobTest =
314
+ createJobTestHarness(
315
+ jobs
316
+ );
317
+
318
+ await jobTest.drain();
304
319
 
305
- // shutdown
306
- await heartbeat.stop();
307
- await realtime.close();
320
+ await jobTest.expectCount(
321
+ 1,
322
+ {
323
+ name: "email.welcome",
324
+ state: "succeeded",
325
+ }
326
+ );
308
327
  ```
309
328
 
310
- `realtime.ping` / `realtime.pong` and `sweepStale()` provide connection liveness cleanup.
329
+ Realtime:
311
330
 
312
- ### Jobs / workflows / events integration
331
+ ```ts
332
+ const realtimeTest =
333
+ createRealtimeTestHarness(
334
+ realtime
335
+ );
313
336
 
314
- Realtime is the transient delivery edge for durable backend state:
337
+ const {
338
+ socket,
339
+ connection,
340
+ } =
341
+ await realtimeTest.connect();
342
+
343
+ await connection.join(
344
+ "orders:42"
345
+ );
315
346
 
316
- ```ts
317
347
  await realtime.broadcast(
318
- `jobs:${job.id}`,
319
- "job.progress",
348
+ "orders:42",
349
+ "order.updated",
320
350
  {
321
- progress: 60,
351
+ status: "paid",
322
352
  }
323
353
  );
354
+
355
+ realtimeTest.expectEvent(
356
+ socket,
357
+ "order.updated",
358
+ "orders:42"
359
+ );
324
360
  ```
325
361
 
326
- Important business facts should remain in database/outbox/jobs/workflows. Realtime delivery is transient, so reconnecting clients should refetch durable state/history when catch-up is required.
362
+ Other testing primitives include:
327
363
 
328
- Read more: [Realtime Platform](docs/realtime-platform.md)
364
+ ```text
365
+ createFakeClock()
366
+ createSequenceIdFactory()
367
+ runTestMiddleware()
368
+ createRealtimeTestSocket()
369
+ readSseEvents()
370
+ ```
371
+
372
+ Read more: [Testing Platform](docs/testing-platform.md)
373
+
374
+ ## Plugin & Module Platform — 0.2.15
375
+
376
+ `0.2.15` adds the server-only `bcp/plugins` entrypoint for reusable application/framework extensions.
377
+
378
+ Define plugins with explicit dependencies:
379
+
380
+ ```ts
381
+ import {
382
+ createPluginHost,
383
+ definePlugin,
384
+ } from "bcp/plugins";
385
+
386
+ const databasePlugin =
387
+ definePlugin({
388
+ name: "database",
389
+ setup(context) {
390
+ context.services.provide(
391
+ "database",
392
+ db
393
+ );
394
+ },
395
+ start() {
396
+ return db.connect();
397
+ },
398
+ stop() {
399
+ return db.disconnect();
400
+ },
401
+ });
402
+
403
+ const jobsPlugin =
404
+ definePlugin({
405
+ name: "jobs",
406
+ requires: [
407
+ "database",
408
+ ],
409
+ });
410
+
411
+ const host =
412
+ createPluginHost({
413
+ plugins: [
414
+ jobsPlugin,
415
+ databasePlugin,
416
+ ],
417
+ });
418
+
419
+ await host.start();
420
+ ```
421
+
422
+ Dependency order is resolved automatically. Startup follows dependency order while stop/dispose runs in reverse order.
423
+
424
+ Modules group reusable plugin sets:
425
+
426
+ ```ts
427
+ import {
428
+ defineModule,
429
+ } from "bcp/plugins";
430
+
431
+ const backendModule =
432
+ defineModule({
433
+ name: "backend",
434
+ plugins: [
435
+ databasePlugin,
436
+ jobsPlugin,
437
+ ],
438
+ });
439
+ ```
440
+
441
+ Plugin configuration can be parsed at setup time and overridden through `createPluginHost({ configs })`.
442
+
443
+ Plugins share a service registry and awaited in-process hook bus through `context.services` and `context.hooks`.
444
+
445
+ If startup fails, already-started plugins are stopped in reverse order before the lifecycle error is propagated.
446
+
447
+ Read more: [Plugin & Module Platform](docs/plugin-module-platform.md)
329
448
 
330
449
  ## Public entrypoints
331
450
 
@@ -342,6 +461,8 @@ bcp/jobs
342
461
  bcp/workflow
343
462
  bcp/events
344
463
  bcp/realtime
464
+ bcp/testing
465
+ bcp/plugins
345
466
  bcp/observability
346
467
  bcp/server
347
468
  bcp/server-only
@@ -390,13 +511,15 @@ Browser / API / Realtime clients
390
511
  |
391
512
  security + auth
392
513
  |
393
- application APIs
514
+ Plugin Host
394
515
  / | \
395
516
  database workflows realtime
396
517
  | | ^
397
518
  outbox jobs |
398
519
  \__________|_________/
399
520
  durable state
521
+
522
+ Testing Platform exercises these server contracts without becoming part of browser runtime.
400
523
  ```
401
524
 
402
525
  ## Packaging
@@ -424,7 +547,7 @@ docs/api-manifest.json
424
547
 
425
548
  ## Release validation
426
549
 
427
- Before publishing `0.2.13`:
550
+ Before publishing `0.2.15`:
428
551
 
429
552
  ```bash
430
553
  npm run typecheck
@@ -435,7 +558,7 @@ npm run test:package
435
558
  npm run rc:check
436
559
  ```
437
560
 
438
- `0.2.13` adds unit and prepared-package smoke coverage for broker broadcasts, channel membership, presence, authentication/channel authorization, socket protocol, heartbeat cleanup, SSE streaming, public runtime compilation and browser boundary enforcement.
561
+ `0.2.15` adds unit and prepared-package smoke coverage for plugin dependency ordering, missing/cyclic dependencies, modules, config parsing, shared services, async hooks, lifecycle rollback, compiled `plugins.mjs` execution and browser boundary enforcement.
439
562
 
440
563
  Do not tag or publish until the exact final release commit passes the full RC sequence.
441
564
 
@@ -463,12 +586,14 @@ Do not tag or publish until the exact final release commit passes the full RC se
463
586
  | `0.2.11` | Workflow Orchestration |
464
587
  | `0.2.12` | Transactional Outbox & Events |
465
588
  | `0.2.13` | Realtime Platform |
589
+ | `0.2.14` | Testing Platform |
590
+ | `0.2.15` | Plugin & Module Platform |
466
591
 
467
592
  ## Roadmap
468
593
 
469
- `0.2.13` establishes provider-neutral live delivery on top of the durable database/outbox/jobs/workflow stack.
594
+ `0.2.15` establishes reusable server-side extension composition while keeping BCP subsystem contracts provider-neutral and independently testable.
470
595
 
471
- The next logical milestone is **`0.2.14Testing Platform`**, focused on framework-native request/route/auth/database/jobs/workflow/realtime testing utilities and application test harnesses.
596
+ The next logical milestone is **`0.2.16Cache Platform v2`**, focused on distributed cache adapters, Redis-compatible caching, locking, stampede protection and production cache observability.
472
597
 
473
598
  Native desktop/mobile compilation remains later roadmap work.
474
599
 
package/docs/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The `docs/` directory is the documentation source of truth for BCP Framework and is organized for **`bcp-docs-web`**.
4
4
 
5
- > **Documentation target:** BCP Framework `0.2.13Realtime Platform`
5
+ > **Documentation target:** BCP Framework `0.2.15Plugin & Module Platform`
6
6
  >
7
7
  > **Release state:** unreleased development target until RC validation, tagging and npm publication complete.
8
8
 
@@ -39,56 +39,57 @@ Framework source and tests remain authoritative for runtime behavior.
39
39
  | `0.2.11` | Workflow Orchestration |
40
40
  | `0.2.12` | Transactional Outbox & Events |
41
41
  | `0.2.13` | Realtime Platform |
42
+ | `0.2.14` | Testing Platform |
43
+ | `0.2.15` | Plugin & Module Platform |
42
44
 
43
- ## 0.2.13Realtime Platform
45
+ ## 0.2.15Plugin & Module Platform
44
46
 
45
- `0.2.13` adds the server-only `bcp/realtime` public entrypoint.
47
+ `0.2.15` adds the server-only `bcp/plugins` public entrypoint.
46
48
 
47
49
  Primary APIs:
48
50
 
49
51
  ```ts
50
52
  import {
51
- createMemoryRealtimeBroker,
52
- createMemoryRealtimePresenceStore,
53
- createRealtime,
54
- createRealtimeSseResponse,
55
- } from "bcp/realtime";
53
+ createPluginHookBus,
54
+ createPluginHost,
55
+ createPluginServiceRegistry,
56
+ defineModule,
57
+ definePlugin,
58
+ } from "bcp/plugins";
56
59
  ```
57
60
 
58
61
  Runtime model:
59
62
 
60
63
  ```text
61
- client connection
62
- |
63
- v
64
- RealtimeHub
65
- |
66
- +-- channels / rooms
67
- +-- auth / channel authorization
68
- +-- presence
69
- +-- heartbeat
70
- |
71
- +-- RealtimeBroker
72
- | -> cross-hub delivery
73
- |
74
- +-- RealtimeSocket
75
- | -> WebSocket provider adapter
76
- |
77
- +-- SSE Response
64
+ Plugin Host
65
+ |
66
+ +-- dependency graph
67
+ | -> required / optional dependencies
68
+ |
69
+ +-- lifecycle
70
+ | -> setup -> start -> stop -> dispose
71
+ |
72
+ +-- service registry
73
+ | -> typed shared application services
74
+ |
75
+ +-- async hook bus
76
+ -> in-process extension points
78
77
  ```
79
78
 
80
- The memory broker/presence store are process-local. Multi-instance deployments should provide shared implementations.
79
+ Startup follows topological dependency order. Stop/dispose runs in reverse order. A failed startup rolls back plugins that already started.
80
+
81
+ Modules are named bundles of plugins; they do not create a separate lifecycle graph.
81
82
 
82
83
  New/updated sources:
83
84
 
84
85
  | Source | Purpose |
85
86
  | --- | --- |
86
- | `realtime-platform.md` | Channels, presence, WebSocket adapter, SSE, heartbeat and broker model |
87
- | `api-reference.md` | `bcp/realtime` public APIs |
88
- | `platform-manifest.json` | Realtime capability flags and public entrypoint |
89
- | `api-manifest.json` | `bcp/realtime` source/guide ownership |
90
- | `docs-web-manifest.json` | Realtime docs navigation and `0.2.13` release route |
91
- | `releases/0.2.13.md` | Realtime Platform release notes |
87
+ | `plugin-module-platform.md` | Plugin definitions, modules, dependency order, lifecycle, config, services and hooks |
88
+ | `api-reference.md` | `bcp/plugins` public APIs |
89
+ | `platform-manifest.json` | Plugin capability flags and public entrypoint |
90
+ | `api-manifest.json` | `bcp/plugins` source/guide ownership |
91
+ | `docs-web-manifest.json` | Plugin docs navigation and `0.2.15` release route |
92
+ | `releases/0.2.15.md` | Plugin & Module Platform release notes |
92
93
 
93
94
  ## Update rule
94
95
 
@@ -114,8 +115,10 @@ When framework behavior or public surface changes:
114
115
  | `/docs/workflow-orchestration` | `workflow-orchestration.md` |
115
116
  | `/docs/transactional-outbox-events` | `transactional-outbox-events.md` |
116
117
  | `/docs/realtime-platform` | `realtime-platform.md` |
118
+ | `/docs/testing-platform` | `testing-platform.md` |
119
+ | `/docs/plugin-module-platform` | `plugin-module-platform.md` |
117
120
  | `/docs/api-reference` | `api-reference.md` |
118
- | `/releases/0.2.13` | `releases/0.2.13.md` |
121
+ | `/releases/0.2.15` | `releases/0.2.15.md` |
119
122
 
120
123
  Every route/source pair is validated by unit tests.
121
124
 
@@ -134,6 +137,8 @@ bcp/jobs
134
137
  bcp/workflow
135
138
  bcp/events
136
139
  bcp/realtime
140
+ bcp/testing
141
+ bcp/plugins
137
142
  bcp/observability
138
143
  bcp/server
139
144
  bcp/server-only
@@ -144,7 +149,7 @@ The API-manifest entrypoint set must match the platform public-entrypoint set ex
144
149
 
145
150
  ## Release validation
146
151
 
147
- Before publishing `0.2.13`:
152
+ Before publishing `0.2.15`:
148
153
 
149
154
  ```bash
150
155
  npm run typecheck
@@ -155,6 +160,6 @@ npm run test:package
155
160
  npm run rc:check
156
161
  ```
157
162
 
158
- Realtime validation covers broker broadcasts, presence, authentication/channel authorization, socket protocol, heartbeat cleanup, SSE streaming, server-only boundaries, compiled `realtime.mjs` package execution and docs/platform/API parity.
163
+ Plugin Platform validation covers dependency ordering, missing/cyclic dependencies, module composition, typed config parsing, service sharing, async hooks, reverse shutdown, startup rollback, server-only boundaries, compiled `plugins.mjs` package execution and docs/platform/API parity.
159
164
 
160
165
  The final release tag must point to the exact commit that passed the complete RC sequence.