@chidchanun/bcp 0.2.14 → 0.2.16
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 +190 -12
- package/docs/README.md +45 -43
- package/docs/api-manifest.json +13 -5
- package/docs/api-reference.md +108 -3
- package/docs/cache-platform-v2.md +487 -0
- package/docs/docs-web-manifest.json +9 -5
- package/docs/platform-manifest.json +29 -4
- package/docs/plugin-module-platform.md +391 -0
- package/docs/releases/0.2.15.md +118 -0
- package/docs/releases/0.2.16.md +147 -0
- package/package.json +7 -2
- package/packages/bundler/src/client-boundary.ts +1 -0
- package/packages/cache/src/platform-v2.ts +1705 -0
- package/packages/client/src/cache.mjs +1554 -0
- package/packages/client/src/cache.ts +29 -0
- package/packages/client/src/plugins.mjs +811 -0
- package/packages/client/src/plugins.ts +26 -0
- package/packages/server/src/plugins.ts +1225 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Plugin & Module Platform
|
|
2
|
+
|
|
3
|
+
BCP `0.2.15` adds the server-only `bcp/plugins` entrypoint for composing framework/application extensions with explicit dependencies and lifecycle control.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import {
|
|
9
|
+
createPluginHost,
|
|
10
|
+
defineModule,
|
|
11
|
+
definePlugin,
|
|
12
|
+
} from "bcp/plugins";
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`bcp/plugins` is server-only and cannot be imported from page/client bundles.
|
|
16
|
+
|
|
17
|
+
## Define a plugin
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
export const databasePlugin =
|
|
21
|
+
definePlugin({
|
|
22
|
+
name: "database",
|
|
23
|
+
|
|
24
|
+
async setup(context) {
|
|
25
|
+
context.services.provide(
|
|
26
|
+
"database",
|
|
27
|
+
db
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
async start() {
|
|
32
|
+
await db.connect();
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async stop() {
|
|
36
|
+
await db.disconnect();
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
A plugin can expose four lifecycle hooks:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
setup()
|
|
45
|
+
start()
|
|
46
|
+
stop()
|
|
47
|
+
dispose()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`setup()` prepares dependency wiring and shared services. `start()` begins runtime work. `stop()` shuts active work down. `dispose()` releases setup-level resources when the host closes.
|
|
51
|
+
|
|
52
|
+
## Dependency ordering
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const jobsPlugin =
|
|
56
|
+
definePlugin({
|
|
57
|
+
name: "jobs",
|
|
58
|
+
requires: [
|
|
59
|
+
"database",
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The host resolves required dependencies before dependents:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
database
|
|
68
|
+
↓
|
|
69
|
+
jobs
|
|
70
|
+
↓
|
|
71
|
+
workflow
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Startup follows this order. Shutdown and disposal run in reverse order.
|
|
75
|
+
|
|
76
|
+
Missing required dependencies throw `PluginDependencyError` before lifecycle execution begins.
|
|
77
|
+
|
|
78
|
+
Dependency cycles are rejected:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
a -> b -> c -> a
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Optional dependencies participate in ordering only when they are registered:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
definePlugin({
|
|
88
|
+
name: "metrics-addon",
|
|
89
|
+
optional: [
|
|
90
|
+
"observability",
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Create a host
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const host =
|
|
99
|
+
createPluginHost({
|
|
100
|
+
plugins: [
|
|
101
|
+
databasePlugin,
|
|
102
|
+
jobsPlugin,
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await host.start();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
For graceful shutdown:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
await host.stop();
|
|
113
|
+
await host.close();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`close()` is idempotent. If the host is still started it stops the runtime before disposal.
|
|
117
|
+
|
|
118
|
+
## Modules
|
|
119
|
+
|
|
120
|
+
A module is a named bundle of plugin definitions.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
export const backendModule =
|
|
124
|
+
defineModule({
|
|
125
|
+
name: "backend",
|
|
126
|
+
plugins: [
|
|
127
|
+
databasePlugin,
|
|
128
|
+
jobsPlugin,
|
|
129
|
+
workflowPlugin,
|
|
130
|
+
],
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const host =
|
|
134
|
+
createPluginHost({
|
|
135
|
+
modules: [
|
|
136
|
+
backendModule,
|
|
137
|
+
],
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Modules do not create a second lifecycle system. They only group plugins; dependency resolution still occurs globally across the host.
|
|
142
|
+
|
|
143
|
+
You can also register before setup begins:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
host.use(databasePlugin);
|
|
147
|
+
host.use(backendModule);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Registration is locked after setup starts so runtime dependency graphs cannot change underneath active plugins.
|
|
151
|
+
|
|
152
|
+
## Typed plugin configuration
|
|
153
|
+
|
|
154
|
+
A plugin can parse its own configuration:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const httpPlugin =
|
|
158
|
+
definePlugin<{
|
|
159
|
+
port: number;
|
|
160
|
+
}>({
|
|
161
|
+
name: "http",
|
|
162
|
+
|
|
163
|
+
config: {
|
|
164
|
+
port: 3000,
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
schema: {
|
|
168
|
+
parse(value) {
|
|
169
|
+
const port =
|
|
170
|
+
Number(
|
|
171
|
+
(
|
|
172
|
+
value as {
|
|
173
|
+
port?: unknown;
|
|
174
|
+
}
|
|
175
|
+
)?.port
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (!Number.isInteger(port)) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
"port must be an integer"
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
port,
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
setup(context) {
|
|
191
|
+
console.log(
|
|
192
|
+
context.config.port
|
|
193
|
+
);
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Application-level host configuration overrides the plugin default:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const host =
|
|
202
|
+
createPluginHost({
|
|
203
|
+
plugins: [
|
|
204
|
+
httpPlugin,
|
|
205
|
+
],
|
|
206
|
+
configs: {
|
|
207
|
+
http: {
|
|
208
|
+
port: 8080,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
A schema may be an object with `parse()` or a parser function.
|
|
215
|
+
|
|
216
|
+
## Shared service registry
|
|
217
|
+
|
|
218
|
+
Plugins can publish typed application services:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
setup(context) {
|
|
222
|
+
context.services.provide(
|
|
223
|
+
"mailer",
|
|
224
|
+
mailer
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
A dependent plugin can consume the service:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
setup(context) {
|
|
233
|
+
const mailer =
|
|
234
|
+
context.services.get<Mailer>(
|
|
235
|
+
"mailer"
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Available operations:
|
|
241
|
+
|
|
242
|
+
```text
|
|
243
|
+
provide()
|
|
244
|
+
get()
|
|
245
|
+
optional()
|
|
246
|
+
has()
|
|
247
|
+
delete()
|
|
248
|
+
keys()
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Duplicate `provide()` calls are rejected unless `{ replace: true }` is explicit.
|
|
252
|
+
|
|
253
|
+
String and Symbol service keys are supported.
|
|
254
|
+
|
|
255
|
+
## Asynchronous extension hooks
|
|
256
|
+
|
|
257
|
+
The host also exposes a small asynchronous hook bus:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
const unsubscribe =
|
|
261
|
+
context.hooks.on<{
|
|
262
|
+
userId: number;
|
|
263
|
+
}>(
|
|
264
|
+
"user.created",
|
|
265
|
+
async event => {
|
|
266
|
+
await sendWelcomeEmail(
|
|
267
|
+
event.userId
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Emit from another plugin:
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
await context.hooks.emit(
|
|
277
|
+
"user.created",
|
|
278
|
+
{
|
|
279
|
+
userId: 42,
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Handlers run in registration order and are awaited. The hook bus is intended for in-process extension points, not durable event delivery.
|
|
285
|
+
|
|
286
|
+
Use `bcp/events` for durable integration events that must survive process failure.
|
|
287
|
+
|
|
288
|
+
## Lifecycle failure behavior
|
|
289
|
+
|
|
290
|
+
If a plugin fails during `start()`, plugins that already started in that transition are stopped in reverse order before `PluginLifecycleError` is propagated.
|
|
291
|
+
|
|
292
|
+
```text
|
|
293
|
+
database start ✅
|
|
294
|
+
jobs start ✅
|
|
295
|
+
workflow start ❌
|
|
296
|
+
|
|
297
|
+
rollback:
|
|
298
|
+
jobs stop
|
|
299
|
+
database stop
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
This prevents partially-started application runtimes from being left active after a startup failure.
|
|
303
|
+
|
|
304
|
+
Lifecycle records can be inspected:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
host.plugin("jobs");
|
|
308
|
+
host.plugins();
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
States include:
|
|
312
|
+
|
|
313
|
+
```text
|
|
314
|
+
registered
|
|
315
|
+
setting-up
|
|
316
|
+
ready
|
|
317
|
+
starting
|
|
318
|
+
started
|
|
319
|
+
stopping
|
|
320
|
+
stopped
|
|
321
|
+
failed
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Relationship to existing BCP platforms
|
|
325
|
+
|
|
326
|
+
`bcp/plugins` composes services; it does not replace their own durability/lifecycle contracts.
|
|
327
|
+
|
|
328
|
+
```text
|
|
329
|
+
Plugin Host
|
|
330
|
+
|
|
|
331
|
+
+-- Database plugin ------> bcp/database
|
|
332
|
+
+-- Jobs plugin ----------> bcp/jobs
|
|
333
|
+
+-- Workflow plugin ------> bcp/workflow
|
|
334
|
+
+-- Events plugin --------> bcp/events
|
|
335
|
+
+-- Realtime plugin ------> bcp/realtime
|
|
336
|
+
+-- Observability plugin -> bcp/observability
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
A plugin can wrap any existing BCP subsystem and expose the resulting instance through the service registry.
|
|
340
|
+
|
|
341
|
+
## Production guidance
|
|
342
|
+
|
|
343
|
+
Keep plugin names stable because dependency declarations reference names.
|
|
344
|
+
|
|
345
|
+
Prefer explicit required dependencies instead of relying on registration order.
|
|
346
|
+
|
|
347
|
+
Use plugin configuration parsing near the plugin boundary so invalid configuration fails before startup.
|
|
348
|
+
|
|
349
|
+
Use `dispose()` for resources created during setup, and `stop()` for active runtime processes such as workers, schedulers and network listeners.
|
|
350
|
+
|
|
351
|
+
Do not use the hook bus as a durable message broker. Use Transactional Outbox/Jobs for delivery guarantees.
|
|
352
|
+
|
|
353
|
+
## 0.2.15 scope
|
|
354
|
+
|
|
355
|
+
Included:
|
|
356
|
+
|
|
357
|
+
```text
|
|
358
|
+
bcp/plugins
|
|
359
|
+
definePlugin()
|
|
360
|
+
defineModule()
|
|
361
|
+
createPluginHost()
|
|
362
|
+
required dependencies
|
|
363
|
+
optional dependencies
|
|
364
|
+
topological ordering
|
|
365
|
+
cycle detection
|
|
366
|
+
setup/start/stop/dispose lifecycle
|
|
367
|
+
reverse shutdown
|
|
368
|
+
startup rollback
|
|
369
|
+
plugin state records
|
|
370
|
+
config parser/schema contract
|
|
371
|
+
host config overrides
|
|
372
|
+
service registry
|
|
373
|
+
async hook bus
|
|
374
|
+
server-only boundary
|
|
375
|
+
compiled plugins.mjs
|
|
376
|
+
unit tests
|
|
377
|
+
prepared package smoke
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Not included yet:
|
|
381
|
+
|
|
382
|
+
```text
|
|
383
|
+
automatic npm plugin discovery
|
|
384
|
+
remote plugin loading
|
|
385
|
+
sandboxed/untrusted plugins
|
|
386
|
+
hot plugin replacement
|
|
387
|
+
plugin marketplace
|
|
388
|
+
CLI plugin install command
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Those can be added later without changing the core host contract.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# BCP Framework 0.2.15 — Plugin & Module Platform
|
|
2
|
+
|
|
3
|
+
**Release state:** unreleased
|
|
4
|
+
|
|
5
|
+
BCP `0.2.15` adds a server-only plugin/module composition layer through `bcp/plugins`.
|
|
6
|
+
|
|
7
|
+
## Highlights
|
|
8
|
+
|
|
9
|
+
- new `bcp/plugins` public entrypoint,
|
|
10
|
+
- `definePlugin()` plugin definitions,
|
|
11
|
+
- `defineModule()` named plugin bundles,
|
|
12
|
+
- `createPluginHost()` lifecycle orchestration,
|
|
13
|
+
- required and optional plugin dependencies,
|
|
14
|
+
- deterministic topological dependency ordering,
|
|
15
|
+
- dependency cycle and missing-dependency errors,
|
|
16
|
+
- setup/start/stop/dispose lifecycle hooks,
|
|
17
|
+
- reverse shutdown/disposal ordering,
|
|
18
|
+
- startup rollback when a dependent plugin fails,
|
|
19
|
+
- inspectable plugin runtime state,
|
|
20
|
+
- plugin-local config parser/schema contract,
|
|
21
|
+
- host-level config overrides,
|
|
22
|
+
- shared string/Symbol service registry,
|
|
23
|
+
- asynchronous in-process hook bus,
|
|
24
|
+
- server-only browser boundary,
|
|
25
|
+
- compiled `plugins.mjs` npm runtime,
|
|
26
|
+
- unit and prepared-package smoke coverage.
|
|
27
|
+
|
|
28
|
+
## Public API
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {
|
|
32
|
+
createPluginHookBus,
|
|
33
|
+
createPluginHost,
|
|
34
|
+
createPluginServiceRegistry,
|
|
35
|
+
defineModule,
|
|
36
|
+
definePlugin,
|
|
37
|
+
PluginDependencyError,
|
|
38
|
+
PluginLifecycleError,
|
|
39
|
+
} from "bcp/plugins";
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Dependency lifecycle
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
database
|
|
46
|
+
↓
|
|
47
|
+
jobs
|
|
48
|
+
↓
|
|
49
|
+
workflow
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Startup follows dependency order. Stop/dispose runs in reverse order.
|
|
53
|
+
|
|
54
|
+
If a plugin fails during startup, already-started plugins from that transition are stopped before the failure is propagated.
|
|
55
|
+
|
|
56
|
+
## Modules
|
|
57
|
+
|
|
58
|
+
Modules are composition bundles rather than a second runtime:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const backend =
|
|
62
|
+
defineModule({
|
|
63
|
+
name: "backend",
|
|
64
|
+
plugins: [
|
|
65
|
+
databasePlugin,
|
|
66
|
+
jobsPlugin,
|
|
67
|
+
workflowPlugin,
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
All plugins still share one host-level dependency graph.
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
Plugin definitions can provide defaults and a parser/schema. `PluginHostOptions.configs` can override raw configuration per plugin before parsing.
|
|
77
|
+
|
|
78
|
+
Invalid configuration fails at the plugin setup boundary.
|
|
79
|
+
|
|
80
|
+
## Extension services
|
|
81
|
+
|
|
82
|
+
`PluginServiceRegistry` provides explicit shared service lookup between plugins.
|
|
83
|
+
|
|
84
|
+
`PluginHookBus` provides awaited in-process extension hooks. It is not a durable event system; `bcp/events` remains the durable integration-event platform.
|
|
85
|
+
|
|
86
|
+
## Package/runtime contract
|
|
87
|
+
|
|
88
|
+
The prepared npm package exposes:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
bcp/plugins
|
|
92
|
+
types -> packages/client/src/plugins.ts
|
|
93
|
+
browser -> packages/client/src/server-only.browser.mjs
|
|
94
|
+
default -> packages/client/src/plugins.mjs
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`plugins.mjs` is compiled during package preparation.
|
|
98
|
+
|
|
99
|
+
## Compatibility
|
|
100
|
+
|
|
101
|
+
`0.2.15` has no intentional breaking changes from `0.2.14`.
|
|
102
|
+
|
|
103
|
+
Existing BCP entrypoints remain supported.
|
|
104
|
+
|
|
105
|
+
## Validation
|
|
106
|
+
|
|
107
|
+
Before publishing:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npm run typecheck
|
|
111
|
+
npm run test:unit
|
|
112
|
+
npm run test:integration
|
|
113
|
+
npm run test:e2e
|
|
114
|
+
npm run test:package
|
|
115
|
+
npm run rc:check
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Do not tag or publish until the exact final release commit passes the full RC sequence.
|
|
@@ -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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chidchanun/bcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "BCP Framework - a React full-stack framework with file-based routing, SSR, APIs, middleware, islands, caching and standalone production builds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"./cache": {
|
|
40
40
|
"types": "./packages/client/src/cache.ts",
|
|
41
|
-
"default": "./packages/client/src/cache.
|
|
41
|
+
"default": "./packages/client/src/cache.mjs"
|
|
42
42
|
},
|
|
43
43
|
"./config": {
|
|
44
44
|
"types": "./packages/client/src/config.ts",
|
|
@@ -87,6 +87,11 @@
|
|
|
87
87
|
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
88
88
|
"default": "./packages/client/src/testing.mjs"
|
|
89
89
|
},
|
|
90
|
+
"./plugins": {
|
|
91
|
+
"types": "./packages/client/src/plugins.ts",
|
|
92
|
+
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
93
|
+
"default": "./packages/client/src/plugins.mjs"
|
|
94
|
+
},
|
|
90
95
|
"./observability": {
|
|
91
96
|
"types": "./packages/client/src/observability.ts",
|
|
92
97
|
"browser": "./packages/client/src/server-only.browser.mjs",
|