@astilba/cache 0.0.0-placeholder.0 → 0.1.0
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/LICENSE +21 -0
- package/README.md +13 -1
- package/dist/adapters/cloudflare/index.d.ts +299 -0
- package/dist/adapters/cloudflare/index.js +1699 -0
- package/dist/adapters/react-router/index.d.ts +92 -0
- package/dist/adapters/react-router/index.js +272 -0
- package/dist/cache-YWn0VZpn.d.ts +930 -0
- package/dist/cache-Z4cJzPmm.js +3528 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +2 -0
- package/package.json +91 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rees Morris
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
1
|
# @astilba/cache
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A portable TypeScript caching library — tag invalidation (soft/hard), classified stale-if-error resilience, and private-by-default scopes. Targets Node, Bun, and Cloudflare Workers.
|
|
4
|
+
|
|
5
|
+
> **Public alpha (0.1).** The correctness kernel and the Cloudflare Workers and React Router paths ship and run under the test suite, but elapsed TTL and grace periods are not enforced yet, a few methods still throw, and there is no production support commitment. Expect breaking changes before 1.0. Check [implementation status](https://astilba.com/docs/cache/api-status/) before relying on a feature.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @astilba/cache
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Documentation lives at [astilba.com/docs/cache](https://astilba.com/docs/cache/).
|
|
12
|
+
|
|
13
|
+
## License
|
|
14
|
+
|
|
15
|
+
MIT © Rees Morris
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { Ct as TelemetrySink, G as Bus, Tt as TagKnowledge, at as Registry, bt as TelemetryConfig, ct as Store, ot as RegistryAck, r as CacheDefaults, st as Rng, t as Cache } from "../../cache-YWn0VZpn.js";
|
|
2
|
+
import { DurableObject } from "cloudflare:workers";
|
|
3
|
+
//#region src/adapters/cloudflare/bus-client.d.ts
|
|
4
|
+
/** A message event as delivered to a WS `message` listener — `data` is the payload string. */
|
|
5
|
+
interface WireMessageEvent {
|
|
6
|
+
readonly data: unknown;
|
|
7
|
+
}
|
|
8
|
+
/** A close event as delivered to a WS `close` listener. */
|
|
9
|
+
interface WireCloseEvent {
|
|
10
|
+
readonly code: number;
|
|
11
|
+
readonly reason: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The minimal client-side socket surface the doBus client needs. workerd's client WebSocket
|
|
15
|
+
* (`response.webSocket`, after `accept()`) satisfies it structurally; the conformance in-memory
|
|
16
|
+
* pair implements the same shape.
|
|
17
|
+
*/
|
|
18
|
+
interface ClientSocket {
|
|
19
|
+
close(code?: number, reason?: string): void;
|
|
20
|
+
addEventListener(type: "message", listener: (event: WireMessageEvent) => void): void;
|
|
21
|
+
addEventListener(type: "close", listener: (event: WireCloseEvent) => void): void;
|
|
22
|
+
}
|
|
23
|
+
/** How the client obtains an OPEN socket. Returns synchronously (the conformance pair) or a promise
|
|
24
|
+
* (a real `stub.fetch` → `accept()`); the client handles both. */
|
|
25
|
+
type Dial = () => ClientSocket | Promise<ClientSocket>;
|
|
26
|
+
/** Why the doBus socket closed (status report). `local` distinguishes a close the client
|
|
27
|
+
* ITSELF initiated (a protocol/scope violation) from one the transport/server delivered. EXPORTED at
|
|
28
|
+
* its first consumer, the redial driver (`redial.ts`), has arrived, and an app that supplies
|
|
29
|
+
* its own `onClose` reads the same shape. */
|
|
30
|
+
interface DoBusCloseInfo {
|
|
31
|
+
readonly code: number;
|
|
32
|
+
readonly reason: string;
|
|
33
|
+
readonly local: boolean;
|
|
34
|
+
}
|
|
35
|
+
interface DoBusOptions {
|
|
36
|
+
/** The mirror-key scope this subscriber expects the DO to echo in its `hello`. */
|
|
37
|
+
readonly regId: string;
|
|
38
|
+
/** Status report: the socket closed. Fired at most once per subscription. The
|
|
39
|
+
* driver redials off this; integration tests redial explicitly. */
|
|
40
|
+
readonly onClose?: (info: DoBusCloseInfo) => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build a `doBus` client `Bus`. `dial` obtains an OPEN socket (synchronously in the
|
|
44
|
+
* conformance pair; a `stub.fetch` → `accept()` promise in workerd). `options.regId` is the scope
|
|
45
|
+
* the DO's `hello` must echo; `options.onClose` receives the socket's close (
|
|
46
|
+
* NO self-reconnect). The reader subscribes to the returned `Bus`; the client decodes each message
|
|
47
|
+
* and dispatches the `BusEvent`, closing loud on any protocol/scope violation.
|
|
48
|
+
*/
|
|
49
|
+
declare function doBus(dial: Dial, options: DoBusOptions): Bus;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/adapters/cloudflare/coordinator.d.ts
|
|
52
|
+
/** The bindings the Coordinator reads. `REGISTRY_KV` is the replication mirror. */
|
|
53
|
+
interface CoordinatorEnv {
|
|
54
|
+
/** The KV namespace the `__delta`/`__snap`/`__state` replication mirror is written to. */
|
|
55
|
+
readonly REGISTRY_KV: KVNamespace;
|
|
56
|
+
/**
|
|
57
|
+
* liveness heartbeat interval in ms, as a config var (`env` decides WHEN the shell
|
|
58
|
+
* issues a heartbeat command — it never parameterizes the machine). When set (Paid deployments),
|
|
59
|
+
* an idle alarm refreshes `__state`'s freshness even with no purges, via the machine's
|
|
60
|
+
* state-neutral heartbeat routed through the shared executor; earliest-wins arbitration keeps it
|
|
61
|
+
* from starving the flush deadline, and a flush never also heartbeats in the same alarm.
|
|
62
|
+
* UNSET by default (off — the default in every test lane; the heartbeat suite sets it per-test),
|
|
63
|
+
* so the heartbeat path stays fully dormant.
|
|
64
|
+
*
|
|
65
|
+
* THE COORDINATOR END OF THE ONE HEARTBEAT NUMBER, and the end NO WORKER CAN REACH.
|
|
66
|
+
* This var and the reader's `defaults.heartbeatInterval` are the two ends of the liveness
|
|
67
|
+
* contract, but a Worker cannot write a Durable Object's `env`: deploy-time wrangler config is
|
|
68
|
+
* this end's ONLY seed. So the two ends are NOT matched by construction — they are ONE DOCUMENTED
|
|
69
|
+
* NUMBER over TWO DEPLOYMENT SURFACES, and matching them is a deployment step. Recommended
|
|
70
|
+
* production posture: `"vars": { "REGISTRY_HEARTBEAT_MS": "30000" }` — the same 30s the
|
|
71
|
+
* `createWorkersCache` reader defaults to (see `DEFAULT_HEARTBEAT_INTERVAL` in `workers-cache.ts`
|
|
72
|
+
* for the full price of leaving it unset). A change here lands at the DO's next boot (one boot's
|
|
73
|
+
* lag). A drift between the ends is safe-direction (a shorter reader interval raises suspicion
|
|
74
|
+
* SPURIOUSLY: live registry checks, telemetry-visible, never a stale serve), which is why nothing
|
|
75
|
+
* enforces equality; the asymmetry is documented at that constant rather than engineered away.
|
|
76
|
+
*/
|
|
77
|
+
readonly REGISTRY_HEARTBEAT_MS?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Snapshot cadence knobs, as config vars. `env` may only AUTHOR a journaled `configure`
|
|
80
|
+
* command (Ruling B) — it never parameterizes the machine directly. On boot the shell diffs
|
|
81
|
+
* `env` against the journal's effective config and, if changed, journals a fresh `configure`
|
|
82
|
+
* (see `#reconcileConfig`), applied PROSPECTIVELY at a definite journal position — replay-exact
|
|
83
|
+
* forever. So a later var change can never retroactively fork `snapBatch` or bless a `__snap`
|
|
84
|
+
* that was never written; the journal is the single durable truth. UNSET ⇒ the machine's
|
|
85
|
+
* 500-batch / 6h defaults.
|
|
86
|
+
*/
|
|
87
|
+
readonly REGISTRY_SNAPSHOT_EVERY_BATCHES?: string;
|
|
88
|
+
readonly REGISTRY_SNAPSHOT_INTERVAL_MS?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Snapshot SIZE ceiling in bytes, as a config var. Seeds the machine's
|
|
91
|
+
* `maxSnapshotBytes` through the SAME journaled `configure` channel as the cadence (Ruling B):
|
|
92
|
+
* `env` authors it, the journal is the single durable truth, replay reproduces the skip decision.
|
|
93
|
+
* A due snapshot whose canonical shipped bytes exceed this is SKIPPED (never truncated, never a
|
|
94
|
+
* phantom bless). UNSET ⇒ the machine's 25 MiB default (the KV per-value profile).
|
|
95
|
+
*/
|
|
96
|
+
readonly REGISTRY_MAX_SNAPSHOT_BYTES?: string;
|
|
97
|
+
/**
|
|
98
|
+
* The registry id scoping the mirror keyspace — the DEMOTED FALLBACK, as a config var.
|
|
99
|
+
* Identity = address: a Coordinator's `regId` derives from its OWN Durable Object name
|
|
100
|
+
* (`ctx.id.name`), the string clients address it by. `REGISTRY_ID` is consulted ONLY for an
|
|
101
|
+
* un-named object (`newUniqueId` ⇒ `ctx.id.name === undefined`), never as an override (an override
|
|
102
|
+
* would resurrect the one-registry-per-deployment defect kills). UNSET ⇒ `"default"`
|
|
103
|
+
* (single-registry deployments stay zero-config). Validated `[a-z0-9._-]`, ≤ 64 — a malformed
|
|
104
|
+
* value fails the boot loud (see `resolveRegId`) rather than minting a bad key.
|
|
105
|
+
*/
|
|
106
|
+
readonly REGISTRY_ID?: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* A name/journal regId DIVERGENCE detected at boot. regId is journaled at first
|
|
110
|
+
* `configure` and NEVER re-journaled; on a later boot where the DO's name derives a DIFFERENT id
|
|
111
|
+
* than the journaled one, the Coordinator KEEPS the journaled identity and records this — it can
|
|
112
|
+
* never serve dead, because both replication channels already fail closed on identity mismatch (the
|
|
113
|
+
* pointer's regId echo, `hello`'s regId echo). Migration to a new identity =
|
|
114
|
+
* addressing a NEW name (fresh journal, fresh keyspace), never a rekey of the live one.
|
|
115
|
+
*/
|
|
116
|
+
interface RegIdDivergence {
|
|
117
|
+
/** The regId journaled at first boot — the identity that WINS (mirror keys stay `__reg:<this>:*`). */
|
|
118
|
+
readonly journaled: string;
|
|
119
|
+
/** The regId the current Durable Object name derives — observed, priced (misses/checks), never served. */
|
|
120
|
+
readonly name: string;
|
|
121
|
+
}
|
|
122
|
+
declare class Coordinator extends DurableObject<CoordinatorEnv> {
|
|
123
|
+
#private;
|
|
124
|
+
/**
|
|
125
|
+
* The name/journal regId divergence detected at this instance's boot, or null when the
|
|
126
|
+
* name still derives the journaled identity. A read-only boot diagnostic (surfaced to the
|
|
127
|
+
* integration divergence scene via `runInDurableObject`); never consulted on a hot path.
|
|
128
|
+
*/
|
|
129
|
+
get regIdDivergence(): RegIdDivergence | null;
|
|
130
|
+
/** Strong read: live, un-memoized, primary-only — the DO IS the primary. */
|
|
131
|
+
check(tags: readonly string[]): Promise<readonly TagKnowledge[]>;
|
|
132
|
+
/** Soft purge: max-on-write bump of `softEpoch`. */
|
|
133
|
+
expire(tags: readonly string[]): Promise<RegistryAck>;
|
|
134
|
+
/** Hard purge: max-on-write bump of `hardEpoch`. */
|
|
135
|
+
delete(tags: readonly string[]): Promise<RegistryAck>;
|
|
136
|
+
/** Register a retention lease; the GC horizon rises to the high-water. */
|
|
137
|
+
registerRetention(retentionMs: number): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* The doBus live-channel endpoint. A subscriber opens a WebSocket here; the DO
|
|
140
|
+
* accepts it HIBERNATABLY (no per-subscriber attachment, ZERO replay state) and runs
|
|
141
|
+
* the establishment handshake: `reset` → `hello{head, regId}`, BEFORE any frame. `head`
|
|
142
|
+
* is the DO's OWN last-flushed epoch (`#flushedEpoch`) and the `hello` echoes `regId`,
|
|
143
|
+
*so a mismatched subscriber closes loud. Serialized under `blockConcurrencyWhile` so the
|
|
144
|
+
* handshake snapshots a consistent (cursor, regId) against any in-flight mutation.
|
|
145
|
+
*/
|
|
146
|
+
fetch(request: Request): Promise<Response>;
|
|
147
|
+
/**
|
|
148
|
+
* Hibernatable WebSocket message handler. The doBus client is RECEIVE-ONLY —
|
|
149
|
+
* it dials, decodes, and dispatches; it never sends. Any inbound message is unexpected and there
|
|
150
|
+
* is no per-subscriber state to update, so it is ignored. Defined so the hibernation runtime has a
|
|
151
|
+
* message handler for woken sockets.
|
|
152
|
+
*/
|
|
153
|
+
webSocketMessage(_ws: WebSocket, _message: ArrayBuffer | string): void;
|
|
154
|
+
/**
|
|
155
|
+
* Hibernatable WebSocket close handler. Stateless fan-out holds ZERO
|
|
156
|
+
* per-subscriber state, so a closed socket needs no reclamation — a later `getWebSockets()` simply
|
|
157
|
+
* will not include it. Defined so the hibernation runtime has a close handler for woken sockets.
|
|
158
|
+
*/
|
|
159
|
+
webSocketClose(_ws: WebSocket, _code: number, _reason: string, _wasClean: boolean): void;
|
|
160
|
+
/** Hibernatable WebSocket error handler — best-effort close; no state to reclaim. */
|
|
161
|
+
webSocketError(ws: WebSocket, _error: unknown): void;
|
|
162
|
+
/**
|
|
163
|
+
* The DO alarm handler, serving BOTH the flush window and the heartbeat cadence off the one
|
|
164
|
+
* alarm slot. A PURE function of durable state, idempotent under at-least-once
|
|
165
|
+
* delivery + redeploy: it rehydrates the machine from the journal and either FLUSHES the
|
|
166
|
+
* coalescing window `(kvFlushedEpoch, epoch]` into one content-addressed batch (advancing the
|
|
167
|
+
* durable cursor ONLY after KV accepts), or — on an EMPTY window — refreshes the `__state`
|
|
168
|
+
* freshness via a state-neutral heartbeat. A redelivery before the cursor advances re-writes the
|
|
169
|
+
* identical (content-addressed, write-once) bytes; a redelivery after finds an empty window and
|
|
170
|
+
* heartbeats (or no-ops when the cadence is unset).
|
|
171
|
+
*/
|
|
172
|
+
alarm(): Promise<void>;
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/adapters/cloudflare/do-registry.d.ts
|
|
176
|
+
/**
|
|
177
|
+
* Where a Coordinator stub comes from (widened by ADDITIVELY, in the warrant).
|
|
178
|
+
*
|
|
179
|
+
* A STUB is a client-held I/O handle: it belongs to the request context that minted it, and the
|
|
180
|
+
* platform refuses it anywhere else. A long-lived cache therefore cannot hold one — but the API defines
|
|
181
|
+
* `doRegistry(stub, regId)`, and a caller who mints and uses inside ONE request (the explicit
|
|
182
|
+
* composition, as written) is doing nothing wrong. So the parameter widens instead of changing: a
|
|
183
|
+
* bare stub stays exactly as valid as it was, and a THUNK lets a caller that outlives a request hand
|
|
184
|
+
* over the ADDRESS — the recipe for a stub — rather than a stub. The thunk is invoked per use, so
|
|
185
|
+
* whichever request is running gets a handle of its own.
|
|
186
|
+
*/
|
|
187
|
+
type StubSource = DurableObjectStub<Coordinator> | (() => DurableObjectStub<Coordinator>);
|
|
188
|
+
/** Build a `Registry` from a Coordinator DO stub OR a thunk that mints one (`StubSource` —
|
|
189
|
+
* the widening is additive, so `doRegistry(stub, regId)` remains exactly as signed). `regId`
|
|
190
|
+
* must match the Coordinator's regId — which is its Durable Object NAME
|
|
191
|
+
* (identity = address) — so the client-side reader resyncs the same scoped keyspace the DO writes;
|
|
192
|
+
* defaults to `"default"` for single-registry (zero-config) deployments.
|
|
193
|
+
*
|
|
194
|
+
* Pass a THUNK from anything that outlives a request (`createWorkersCache` does); pass a bare stub
|
|
195
|
+
* when the caller mints and uses within one. */
|
|
196
|
+
declare function doRegistry(source: StubSource, regId?: string): Registry;
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/adapters/cloudflare/kv.d.ts
|
|
199
|
+
/** Build a `Store` over a Cloudflare `KVNamespace` binding. */
|
|
200
|
+
declare function cloudflareKV(ns: KVNamespace): Store;
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/adapters/cloudflare/redial.d.ts
|
|
203
|
+
/** `redialingDoBus` configuration. `regId` + `onClose` mirror `DoBusOptions` (the close report is
|
|
204
|
+
* PASSED THROUGH to the app after the redial is scheduled — the driver consumes it, it does not
|
|
205
|
+
* swallow it); `rng` is the jitter source (injected — the platform boundary owns ambient randomness,
|
|
206
|
+
* and `createWorkersCache` hands its one `Rng` to both the kernel and this driver). */
|
|
207
|
+
interface RedialOptions {
|
|
208
|
+
readonly regId: string;
|
|
209
|
+
readonly rng: Rng;
|
|
210
|
+
/** Status report, after this driver has scheduled the redial. Observability only. */
|
|
211
|
+
readonly onClose?: (info: DoBusCloseInfo) => void;
|
|
212
|
+
/** First-attempt delay before the ×2 ladder (default 1s). */
|
|
213
|
+
readonly baseDelayMs?: number;
|
|
214
|
+
/** Symmetric jitter fraction (default 0.2 → ±20%). */
|
|
215
|
+
readonly jitterFraction?: number;
|
|
216
|
+
}
|
|
217
|
+
/** A `Bus` whose redial ladder is driven by ticks rather than timers. `tick(nowMs)` performs a
|
|
218
|
+
* DUE redial and is a no-op otherwise; the composition calls it from a request context. */
|
|
219
|
+
interface RedialingBusHandle extends Bus {
|
|
220
|
+
tick(nowMs: number): void;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Build a self-redialing doBus `Bus`. This is the bus `createWorkersCache` wires: the
|
|
224
|
+
* transport reports its close, and this driver owns the reconnect + the ×2/300s jittered
|
|
225
|
+
* backoff — LAZILY, performed by `tick` inside a request context rather than by a timer that would
|
|
226
|
+
* be refused at global scope and would race a dead isolate everywhere else. Use the bare `doBus`
|
|
227
|
+
* instead when the caller drives redials itself (the integration lane does — tests own time).
|
|
228
|
+
*/
|
|
229
|
+
declare function redialingDoBus(dial: Dial, options: RedialOptions): RedialingBusHandle;
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region src/adapters/cloudflare/reg-id.d.ts
|
|
232
|
+
/**
|
|
233
|
+
* Refused: a Coordinator addressed by a Durable Object name that violates the regId
|
|
234
|
+
* grammar (`[a-z0-9._-]`, 1–64). Typed so the boot failure is unambiguous and machine-checkable —
|
|
235
|
+
* the fix is a conforming name or an explicit `REGISTRY_ID`. Thrown at first boot only (before any
|
|
236
|
+
* data), so it strands nothing: validate-and-refuse, not journal-wins. `Error.name` stays the class
|
|
237
|
+
* name; `registryName` carries the offending Durable Object name.
|
|
238
|
+
*/
|
|
239
|
+
declare class InvalidRegistryNameError extends Error {
|
|
240
|
+
readonly registryName: string;
|
|
241
|
+
constructor(registryName: string);
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/adapters/cloudflare/workers-cache.d.ts
|
|
245
|
+
/**
|
|
246
|
+
* `createWorkersCache` configuration (SIGNED shape). The required key set is EXACTLY
|
|
247
|
+
* `{ name, kv, coordinator }`; `defaults` is optional. `name` is the ONE identity string: it becomes
|
|
248
|
+
* the Coordinator DO address (`idFromName(name)`), the registry id (`regId`), and the cache key
|
|
249
|
+
* namespace — identity = address. `coordinator` is parameterized by `Coordinator` so
|
|
250
|
+
* `.get(idFromName(name))` yields the typed stub `doRegistry` needs.
|
|
251
|
+
*/
|
|
252
|
+
interface WorkersCacheConfig {
|
|
253
|
+
readonly name: string;
|
|
254
|
+
readonly kv: KVNamespace;
|
|
255
|
+
readonly coordinator: DurableObjectNamespace<Coordinator>;
|
|
256
|
+
readonly defaults?: CacheDefaults;
|
|
257
|
+
/** A plain sink, or a hosted config that pseudonymizes every string field. Passed
|
|
258
|
+
* straight through to `createCache`. OPTIONAL, so the signed required key set is untouched — but
|
|
259
|
+
* PRESENT, because without it the events this composition emits (`bus_dial_failed`,
|
|
260
|
+
* `strong_degraded`, …) are unreachable for every hero-path user, which is a health surface that
|
|
261
|
+
* exists only on paper. */
|
|
262
|
+
readonly telemetry?: TelemetrySink | TelemetryConfig;
|
|
263
|
+
/** `unknown` resolves to `'error'` — the read THROWS rather than refilling. An OVERRIDE:
|
|
264
|
+
* it outranks `defaults.unknownPolicy`, including this factory's own safe-posture default. For
|
|
265
|
+
* namespaces under a takedown or legal embargo, where refilling from origin could resurrect the
|
|
266
|
+
* content that had to disappear. Optional; the key set is untouched. */
|
|
267
|
+
readonly takedownSensitive?: boolean;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Build a Workers-native `Cache` from one registry `name` + a KV namespace + a Coordinator DO
|
|
271
|
+
* namespace. Sugar over `createCache`: the wall clock and RNG enter here at the platform
|
|
272
|
+
* boundary and reach the kernel only as the injected `clock`/`rng`; the drivers are composed against
|
|
273
|
+
* the single `name` (DO stub, registry id, key namespace). A caller's `defaults` override the safe
|
|
274
|
+
* posture per-field; everything else is fixed.
|
|
275
|
+
*
|
|
276
|
+
* WHY THE L1 IS NOT OPTIONAL HERE, AND WHAT IT COSTS TO SKIP IT. This factory always composes an L1,
|
|
277
|
+
* and under an L2 outage that L1 is the absorber. A failing L2 read is suppressed to a plain miss,
|
|
278
|
+
*so the reader refills from origin — and with an L1 the refill lands there, is checked first
|
|
279
|
+
* on every subsequent read, and the failing tier is never consulted again: one origin call,
|
|
280
|
+
* however long the outage runs. Take the L1 away and nothing catches the refill. The read suppresses
|
|
281
|
+
* to a miss, the write-back's read-compare cannot read, so nothing lands durably (`durable: false`),
|
|
282
|
+
* and the next call repeats the whole trip — every call, forever, for as long as L2 is down. So a
|
|
283
|
+
* raw `createCache` composed WITHOUT an `l1` converts an L2 outage into an unbounded origin
|
|
284
|
+
* stampede, exactly when the origin is least able to absorb one. That is a priced consequence of
|
|
285
|
+
* the miss-class posture, not a defect — a suppressed read must refill, and a refill it cannot
|
|
286
|
+
* cache must happen again. Compose an L1, or accept the bill.
|
|
287
|
+
*
|
|
288
|
+
* ONE DEPLOYMENT STEP THIS FACTORY CANNOT DO FOR YOU (R6). A Worker cannot write a Durable Object's
|
|
289
|
+
* `env`, so the Coordinator's idle liveness heartbeat is seeded ONLY by its own wrangler config, and it
|
|
290
|
+
* ships DORMANT. Recommended production posture — set it to the same 30s this reader defaults to
|
|
291
|
+
* (see `DEFAULT_HEARTBEAT_INTERVAL` for the full price of leaving it unset, and for why a mismatch
|
|
292
|
+
* is safe-direction rather than a bug):
|
|
293
|
+
*
|
|
294
|
+
* // wrangler.jsonc (the Worker that hosts the Coordinator Durable Object)
|
|
295
|
+
* "vars": { "REGISTRY_HEARTBEAT_MS": "30000" }
|
|
296
|
+
*/
|
|
297
|
+
declare function createWorkersCache(config: WorkersCacheConfig): Cache;
|
|
298
|
+
//#endregion
|
|
299
|
+
export { Coordinator, type Dial, type DoBusCloseInfo, type DoBusOptions, InvalidRegistryNameError, type RedialOptions, type RedialingBusHandle, type StubSource, type WorkersCacheConfig, cloudflareKV, createWorkersCache, doBus, doRegistry, redialingDoBus };
|