@demlik/tea 0.15.0 → 0.17.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.
Files changed (115) hide show
  1. package/README.md +30 -7
  2. package/dist/agent/index.d.ts +49 -37
  3. package/dist/agent/index.js +12 -8
  4. package/dist/chunk-4LPY67E4.js +18 -0
  5. package/dist/chunk-4LPY67E4.js.map +1 -0
  6. package/dist/chunk-4NTCPTNQ.js +64 -0
  7. package/dist/chunk-4NTCPTNQ.js.map +1 -0
  8. package/dist/{chunk-3B5X7SBB.js → chunk-4PXNZ5UY.js} +11 -9
  9. package/dist/chunk-4PXNZ5UY.js.map +1 -0
  10. package/dist/{chunk-AYS5N6WY.js → chunk-6XBEYZME.js} +62 -172
  11. package/dist/chunk-6XBEYZME.js.map +1 -0
  12. package/dist/chunk-C4U5D5HP.js +380 -0
  13. package/dist/chunk-C4U5D5HP.js.map +1 -0
  14. package/dist/{chunk-QPEVCJ5Q.js → chunk-DFUEAA6L.js} +13 -6
  15. package/dist/chunk-DFUEAA6L.js.map +1 -0
  16. package/dist/{chunk-22POWGFA.js → chunk-GWWCCSDH.js} +46 -20
  17. package/dist/chunk-GWWCCSDH.js.map +1 -0
  18. package/dist/chunk-K4ETBNKS.js +733 -0
  19. package/dist/chunk-K4ETBNKS.js.map +1 -0
  20. package/dist/chunk-KWNCM6XM.js +539 -0
  21. package/dist/chunk-KWNCM6XM.js.map +1 -0
  22. package/dist/{chunk-V46HSVCK.js → chunk-LYVOGOBC.js} +3 -3
  23. package/dist/{chunk-V46HSVCK.js.map → chunk-LYVOGOBC.js.map} +1 -1
  24. package/dist/{chunk-VTQI2NHP.js → chunk-OPH6Y4UT.js} +9 -8
  25. package/dist/chunk-OPH6Y4UT.js.map +1 -0
  26. package/dist/{chunk-Q5CQTBFA.js → chunk-OYR7ENKU.js} +20 -30
  27. package/dist/chunk-OYR7ENKU.js.map +1 -0
  28. package/dist/{chunk-MYXFNWAJ.js → chunk-QNGYQMZB.js} +4 -4
  29. package/dist/chunk-QNGYQMZB.js.map +1 -0
  30. package/dist/chunk-QWIPH4YH.js +98 -0
  31. package/dist/chunk-QWIPH4YH.js.map +1 -0
  32. package/dist/{chunk-TCLBCERH.js → chunk-XBZQT2TD.js} +238 -212
  33. package/dist/chunk-XBZQT2TD.js.map +1 -0
  34. package/dist/{core-DnzqWy6u.d.ts → core-rDbOaJTN.d.ts} +236 -140
  35. package/dist/do/index.d.ts +37 -33
  36. package/dist/do/index.js +21 -14
  37. package/dist/do/index.js.map +1 -1
  38. package/dist/{durable-effects-CWzA-o2B.d.ts → durable-effects-CNaw97Be.d.ts} +1 -1
  39. package/dist/effect/index.d.ts +151 -0
  40. package/dist/effect/index.js +98 -0
  41. package/dist/effect/index.js.map +1 -0
  42. package/dist/extension/index.d.ts +6 -7
  43. package/dist/extension/index.js +3 -2
  44. package/dist/extension/index.js.map +1 -1
  45. package/dist/flow/index.d.ts +68 -59
  46. package/dist/flow/index.js +32 -27
  47. package/dist/flow/index.js.map +1 -1
  48. package/dist/idempotency/index.d.ts +6 -7
  49. package/dist/idempotency/index.js +4 -2
  50. package/dist/{index-KXqTRJyb.d.ts → index-BOrsbdAf.d.ts} +19 -96
  51. package/dist/{index-DrIRb_Ch.d.ts → index-B_BxplWi.d.ts} +1 -1
  52. package/dist/index-CWmsXNdX.d.ts +207 -0
  53. package/dist/{index-C2DwkjlP.d.ts → index-Cberb5Rr.d.ts} +57 -45
  54. package/dist/{index-CSyMYDAB.d.ts → index-CcrKLtse.d.ts} +1 -1
  55. package/dist/{index-CuNO9m8i.d.ts → index-Cx0F2M34.d.ts} +14 -16
  56. package/dist/{index-HSYgG2Yt.d.ts → index-DaX_P35Q.d.ts} +247 -211
  57. package/dist/{index-CA_7I5g9.d.ts → index-q1PoukOZ.d.ts} +137 -246
  58. package/dist/index.d.ts +121 -385
  59. package/dist/index.js +2 -1
  60. package/dist/jev/index.d.ts +133 -97
  61. package/dist/jev/index.js +205 -161
  62. package/dist/jev/index.js.map +1 -1
  63. package/dist/{machine-DGlPWCWI.d.ts → machine-BcfCFMYF.d.ts} +171 -288
  64. package/dist/machine-viz/index.d.ts +5 -5
  65. package/dist/machine-viz/index.js +8 -4
  66. package/dist/machine-viz/index.js.map +1 -1
  67. package/dist/mem/index.d.ts +3 -4
  68. package/dist/mem/index.js +2 -1
  69. package/dist/mem/index.js.map +1 -1
  70. package/dist/node/index.d.ts +119 -78
  71. package/dist/node/index.js +52 -38
  72. package/dist/node/index.js.map +1 -1
  73. package/dist/paginate/index.d.ts +27 -22
  74. package/dist/paginate/index.js +6 -4
  75. package/dist/parity/index.d.ts +4 -5
  76. package/dist/parity/index.js +3 -2
  77. package/dist/parity/index.js.map +1 -1
  78. package/dist/pbt/index.d.ts +2 -2
  79. package/dist/pbt/index.js +2 -1
  80. package/dist/pbt/index.js.map +1 -1
  81. package/dist/persistence/index.d.ts +10 -12
  82. package/dist/persistence/index.js +5 -3
  83. package/dist/promise/index.d.ts +186 -0
  84. package/dist/promise/index.js +5 -0
  85. package/dist/promise/index.js.map +1 -0
  86. package/dist/{protocol-D8VbH-VH.d.ts → protocol-ClRZBhHQ.d.ts} +7 -8
  87. package/dist/react/index.d.ts +36 -17
  88. package/dist/react/index.js +27 -4
  89. package/dist/react/index.js.map +1 -1
  90. package/dist/resilience/index.d.ts +63 -456
  91. package/dist/resilience/index.js +37 -483
  92. package/dist/resilience/index.js.map +1 -1
  93. package/dist/{runtime-types-CMF13BBh.d.ts → runtime-types-B62Ucnxr.d.ts} +132 -389
  94. package/dist/testing/index.d.ts +33 -113
  95. package/dist/testing/index.js +39 -181
  96. package/dist/testing/index.js.map +1 -1
  97. package/dist/timing/index.d.ts +25 -22
  98. package/dist/timing/index.js +4 -3
  99. package/dist/timing/index.js.map +1 -1
  100. package/dist/{types-BseNPIhr.d.ts → types-DPm5YCDQ.d.ts} +2 -2
  101. package/dist/work-queue/index.d.ts +5 -6
  102. package/package.json +20 -7
  103. package/dist/chunk-22POWGFA.js.map +0 -1
  104. package/dist/chunk-3B5X7SBB.js.map +0 -1
  105. package/dist/chunk-66PY5WYF.js +0 -1816
  106. package/dist/chunk-66PY5WYF.js.map +0 -1
  107. package/dist/chunk-AYS5N6WY.js.map +0 -1
  108. package/dist/chunk-L3TND7LA.js +0 -97
  109. package/dist/chunk-L3TND7LA.js.map +0 -1
  110. package/dist/chunk-MYXFNWAJ.js.map +0 -1
  111. package/dist/chunk-Q5CQTBFA.js.map +0 -1
  112. package/dist/chunk-QPEVCJ5Q.js.map +0 -1
  113. package/dist/chunk-TCLBCERH.js.map +0 -1
  114. package/dist/chunk-VTQI2NHP.js.map +0 -1
  115. package/dist/index-ZCMhnVKN.d.ts +0 -272
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/internal/idempotency/idempotent-intake/index.ts"],"names":[],"mappings":"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","file":"chunk-VTQI2NHP.js","sourcesContent":["/**\n * internal/idempotency/idempotent-intake — receive-once intake for webhooks / queue\n * messages: dedupe by key, enqueue the new ones, replay the cached result to\n * the duplicates. The \"process this payload exactly once even though it\n * arrives N times\" knob.\n *\n * **The recipe.** This is a Level-2 composition — it owns no primitive\n * queue/cache logic of its own, it *splices two L1 bricks into one Model slice*\n * and DELEGATES every transition to their VERB SEAMS — `IdempotencyMemory`\n * (`../idempotency/adapter`) and `QueueAdapter` (`../work-queue/adapter`) —\n * so the store ops and the `QueueItem` / `IdempotencyStore` shapes stay\n * insulated behind verbs the intake calls:\n *\n * - `../idempotency/adapter` — the dedupe-by-key + last-result cache. The\n * `seen` half of the slice is its `IdempotencyStore`, keyed by\n * `keyOf(payload)`. A key is recorded once `receive` calls `memory.remember`;\n * `complete` calls `memory.remember` again to swap its cached value from\n * `pending` to the finished `result`. The cache write is the brick's, never\n * re-rolled here.\n * - `../work-queue/adapter` — the pending-work list. The `queue` half of the\n * slice is its `QueueItem<P>[]`. Every status flip routes through the\n * adapter verbs: `receive` enqueues via `queue.enqueue`, `claimNext` stamps\n * the `running` claim via `queue.patch`, `boot` re-arms a dead worker's\n * claim via `queue.patch`. Intake owns only the SELECTION (which item,\n * gated on the done-cache reconcile below) — the array transition is the\n * brick's. The consumer drains it (claim → process → `complete`) at its own\n * pace.\n *\n * Both bricks are pure-state + pure-ops, host- and clock-agnostic by\n * construction, so the combined slice inherits TEA's two non-negotiables:\n *\n * - **Durable** — the slice is a plain serializable value (a record-backed\n * idempotency store + an array of queue items, no `Map`, no closures), so\n * it survives a Durable-Object eviction / reload and rehydrates whole.\n * - **Replayable** — every verb is a pure `(state, …) => [state, Cmd[]]`\n * transition; no `Date.now()`, no `Math.random()`, no I/O inside a verb.\n * `at` (the clock) and `id` (the queue-item id) are *injected* at the call\n * site, so `trace-replay` reconstructs each transition exactly.\n *\n * **The decision lives in a Cmd, not in a return shape.** `receive` cannot\n * \"return\" what it decided (the knob contract pins the shape to\n * `[State, Cmd[]]`), so the decision is *reified as a Cmd* the runtime\n * performs:\n *\n * - first sighting of a key → mark it `pending`, enqueue the payload, emit\n * `IntakeProcess` (go run the work).\n * - a key already `pending` → drop silently (the original is in flight);\n * emit nothing.\n * - a key already `done` → emit `IntakeReplay` carrying the cached\n * result so the duplicate caller gets the SAME response the first one\n * produced, with zero re-execution of the side effect.\n *\n * **Two crash-safety guarantees the verbs enforce (and the unit specs alone\n * missed — see the wired-machine test):**\n *\n * - *A slow worker is never double-enqueued.* The TTL clock starts at\n * `complete`, not at `receive`: a `pending` entry NEVER ages out, so a\n * duplicate of an in-flight key is always dropped no matter how long the\n * worker takes. Only a `done` entry expires (see `liveEntry`).\n * - *A complete-then-crash never re-runs.* `boot` and `claimNext` reconcile\n * the queue against the done cache: a queue item whose key already resolved\n * to `done` in `seen` is skipped (never reset to pending, never claimed),\n * so a worker that finished and cached its result before the DO evicted is\n * not re-executed on resume.\n *\n * Typical wiring (the combinator form — splice ~3 hooks into a host machine):\n *\n * const intake = createIntake<StripeEvent, ChargeResult>({\n * keyOf: (e) => e.id, // Stripe's idempotency key\n * ttlMs: 24 * 60 * 60 * 1000, // a replayed webhook 24h after it COMPLETED is re-run\n * });\n *\n * // in `init`:\n * intake: intake.init(),\n * // in `update`, on the inbound-webhook Msg:\n * const [next, cmds] = intake.receive(state.intake, payload, at, id);\n * // …route `IntakeProcess` to your worker, `IntakeReplay` to the responder.\n * // on the worker's done Msg:\n * const [next, _] = intake.complete(state.intake, key, result, at);\n * // once, after boot (DO reload), before resuming the drain:\n * const [next, _] = intake.boot(state.intake);\n *\n * Escape hatch: reach the raw L1 ops via `../idempotency` and `../../work-queue`\n * and thread them by hand.\n */\n\nimport { z } from \"zod\";\nimport { Cmd, type CmdOf } from \"../../../index\";\nimport type { QueueItem } from \"../../work-queue\";\nimport { queueAdapter } from \"../../work-queue/adapter\";\nimport type { IdempotencyStore } from \"../idempotency\";\nimport { idempotencyMemory } from \"../idempotency/adapter\";\n\n/**\n * A cached intake entry: either a key whose work is still in flight\n * (`pending`) or one whose work has finished and whose `result` is held for\n * replay to later duplicates (`done`). Discriminated on `phase` — `pending`\n * carries no payload, `done` carries the result `R`.\n *\n * Lives inside the `seen` half of the slice (`IdempotencyStore<IntakeEntry<R>>`).\n * It is the dedupe ledger's *value*; the ledger's *key* is `keyOf(payload)`.\n */\nexport type IntakeEntry<R> =\n | { readonly phase: \"pending\" }\n | { readonly phase: \"done\"; readonly result: R };\n\n/**\n * The intake knob's config — `keyOf` derives the idempotency key from a\n * payload, `ttlMs` (optional) ages a *completed* cached key out so a webhook\n * replayed long after the work finished is treated as new and re-processed.\n *\n * **The TTL clock starts at `complete`, not at `receive`.** A `pending` entry\n * (work still in flight) NEVER ages out, no matter how slow the worker is —\n * ageing a pending key would let the duplicate re-enqueue the in-flight\n * payload and double-process it. Only a `done` entry expires, measured from\n * the `at` passed to `complete` (the moment the result was cached). Inside the\n * window a duplicate replays the cached result; at/after the window it is\n * re-accepted and re-processed.\n *\n * Omit `ttlMs` for a cache whose `done` entries never expire by age (keys live\n * forever — fine for a bounded universe of ids, unbounded growth otherwise).\n *\n * The TTL is owned HERE, not delegated to the composed `IdempotencyStore`: the\n * store's uniform per-entry TTL cannot tell `pending` from `done`, so intake\n * keeps the store TTL-free and applies the phase-aware rule itself (see\n * `liveEntry`).\n */\nexport interface IntakeConfig<P> {\n /** Derive the idempotency key from a payload. Must be pure + deterministic. */\n readonly keyOf: (payload: P) => string;\n /**\n * Age a *completed* key out after this many ms, measured from the `at`\n * passed to `complete`. Pending keys never age out.\n */\n readonly ttlMs?: number;\n}\n\n/**\n * The Model slice this knob owns. `seen` is the dedupe + last-result cache\n * (the `../idempotency` brick); `queue` is the pending-work list (the\n * `../work-queue` brick). Both are plain serializable values — the whole slice\n * round-trips through `JSON` so it survives eviction and replays exactly.\n */\nexport interface IntakeState<P, R> {\n readonly seen: IdempotencyStore<IntakeEntry<R>>;\n readonly queue: readonly QueueItem<P>[];\n}\n\n// === Cmds the verbs reify ===\n//\n// The knob contract pins verbs to `(state, …) => [State, Cmd[]]`, so a verb's\n// *decision* (new vs. duplicate-pending vs. duplicate-done) cannot be a return\n// value — it is emitted as a Cmd the runtime's interpret handler performs.\n// Each Cmd is plain data (invariant 1): the payload / key / result it carries\n// are values, never closures.\n//\n// Both are `Cmd.define`d (ADR 0014). The payload `P` / result `R` are the\n// knob's own type parameters, so each def is minted per knob by a factory\n// (`createIntake` calls it once) rather than a module-scope constant — the\n// same shape `resilient-call`'s `runCmdDef<I, R>()` takes. The host performs\n// them and settles nothing back through intake, hence `ok: z.void()` and an\n// empty `err` list.\n\n/** A genuinely-new payload was accepted and enqueued — go run the work. */\nexport function intakeProcessDef<P>() {\n return Cmd.define(\"intake:process\", {\n input: z.custom<{\n /** The dedupe key the payload was filed under. */\n readonly key: string;\n /** The enqueued queue-item id (the same `id` passed to `receive`). */\n readonly itemId: string;\n /** The original payload, for the worker to process. */\n readonly payload: P;\n }>(),\n ok: z.void(),\n err: [],\n });\n}\nexport type IntakeProcessCmd<P> = CmdOf<ReturnType<typeof intakeProcessDef<P>>>;\n\n/**\n * A duplicate of an already-completed key arrived — replay the cached result\n * to the caller instead of re-running the side effect.\n */\nexport function intakeReplayDef<R>() {\n return Cmd.define(\"intake:replay\", {\n input: z.custom<{\n /** The dedupe key whose cached result is being replayed. */\n readonly key: string;\n /** The result the ORIGINAL request produced, recalled from the cache. */\n readonly result: R;\n }>(),\n ok: z.void(),\n err: [],\n });\n}\nexport type IntakeReplayCmd<R> = CmdOf<ReturnType<typeof intakeReplayDef<R>>>;\n\n/** The Cmd union the intake verbs emit. */\nexport type IntakeCmd<P, R> = IntakeProcessCmd<P> | IntakeReplayCmd<R>;\n\n/**\n * Build an idempotent-intake knob from `config`. Returns the uniform L2\n * contract — `init()` + the verbs (`receive`, `complete`, `boot`, `claimNext`).\n * No `subs` / `handlers`: intake owns no timers and does no I/O of its own (the\n * composed bricks are pure-ops; the worker that runs `IntakeProcess` and the\n * responder that runs `IntakeReplay` live in the host's interpret map).\n */\nexport function createIntake<P, R>(config: IntakeConfig<P>) {\n const { keyOf, ttlMs } = config;\n\n // The two store seams, bound once for this knob's `P`/`R`. The intake\n // delegates every queue status flip to `queue.*` and every dedupe-cache\n // transition to `memory.*`, so the `QueueItem<P>` / `IdempotencyStore` shapes\n // are insulated behind the verbs rather than reached by raw ops.\n const queue = queueAdapter<P>();\n const memory = idempotencyMemory<IntakeEntry<R>>();\n\n // The two Cmd constructors, minted once for this knob's `P`/`R`.\n const intakeProcess = intakeProcessDef<P>();\n const intakeReplay = intakeReplayDef<R>();\n\n /**\n * The empty slice: a TTL-free idempotency store (intake owns the TTL itself —\n * see `liveEntry`, because the store cannot tell `pending` from `done`) plus\n * an empty work queue. The rehydrate path is the host's `init(loaded)` — it\n * passes the persisted slice straight back, this is only the fresh-boot value.\n */\n function init(): IntakeState<P, R> {\n return {\n seen: memory.init(),\n queue: [],\n };\n }\n\n /**\n * The live cached entry for `key` at clock reading `at`, or `undefined` if\n * the key is absent or its `done` window has elapsed. This is intake's\n * phase-aware expiry rule, applied HERE rather than by the store's uniform\n * per-entry TTL:\n *\n * - **`pending`** — work is in flight; NEVER expires. Ageing a pending key\n * would let a slow worker's payload be re-enqueued and double-processed.\n * - **`done`** — expires `ttlMs` after the `atMs` the result was cached at\n * (the `at` passed to `complete`). Half-open, matching the store /\n * rate-limit window edge: `at - atMs >= ttlMs` reads as expired. No\n * `ttlMs` ⇒ a `done` entry never expires by age.\n *\n * PURE — reads `state.seen.entries` directly (no `Date.now`), never mutates.\n */\n function liveEntry(\n store: IdempotencyStore<IntakeEntry<R>>,\n key: string,\n at: number,\n ): IntakeEntry<R> | undefined {\n const cached = store.entries[key];\n if (cached === undefined) return undefined;\n const entry = cached.value;\n if (entry.phase === \"pending\") return entry; // in flight — never ages out\n if (ttlMs !== undefined && at - cached.atMs >= ttlMs) return undefined;\n return entry;\n }\n\n /**\n * Whether the queue item `it`'s key has already resolved to a live `done`\n * entry in `seen` (the work completed and its result is cached). Such an\n * item must NOT be re-armed by `boot` or claimed by `claimNext` — doing so\n * re-runs work whose result is already known (the complete-then-crash bug).\n *\n * `at = +Infinity` is the right reading for the reconcile check: a `done`\n * entry counts as done for reconciliation regardless of how far past its\n * replay TTL it is. (TTL governs whether a *duplicate receive* re-processes,\n * not whether a *queued, already-completed* item should re-run — that one\n * never should.)\n */\n function isAlreadyDone(state: IntakeState<P, R>, it: QueueItem<P>): boolean {\n return state.seen.entries[keyOf(it.input)]?.value.phase === \"done\";\n }\n\n /**\n * Receive a payload at clock reading `at`, filing it under the queue-item\n * id `id`. PURE — returns a NEW slice; the input is untouched.\n *\n * Three outcomes, each reified as a Cmd (see the Cmd union above):\n *\n * 1. **New key** (`liveEntry` is absent at `at`) — `remember` the key as\n * `pending`, `enqueue` the payload onto the work queue, and emit\n * `intake:process`. The only path that grows either half of the slice.\n * 2. **Duplicate, still pending** — the original is in flight. Drop\n * silently: the slice is returned unchanged and no Cmd is emitted, so\n * the duplicate neither re-enqueues nor double-processes. A pending\n * entry never ages out, so this holds no matter how slow the worker is.\n * 3. **Duplicate, already done (within TTL)** — emit `intake:replay`\n * carrying the cached `result` so the caller gets the original response.\n * The slice is unchanged (a replay touches neither cache nor queue). A\n * `done` key past its `ttlMs` window reads as absent and falls to (1).\n *\n * `id` is injected (work-queue's `genId` reads `Date.now()` / `crypto`, so\n * it cannot be called inside a pure verb). Generate it at the effect\n * boundary and thread it in; it becomes the enqueued item's id AND the\n * `itemId` on the emitted `intake:process` Cmd, so the worker can address the\n * exact queue item it must `complete`.\n */\n function receive(\n state: IntakeState<P, R>,\n payload: P,\n at: number,\n id: string,\n ): readonly [IntakeState<P, R>, readonly IntakeCmd<P, R>[]] {\n const key = keyOf(payload);\n\n // Duplicate path — the key has a live entry at `at` (pending never ages;\n // done ages only past `ttlMs` measured from `complete`).\n const entry = liveEntry(state.seen, key, at);\n if (entry !== undefined) {\n if (entry.phase === \"done\") {\n // Replay the original result; touch nothing.\n return [state, [intakeReplay({ key, result: entry.result })]];\n }\n // phase === \"pending\": original is in flight — drop silently.\n return [state, []];\n }\n\n // New path — record the key as pending, enqueue the payload, process it.\n const seenNext = memory.remember(state.seen, key, { phase: \"pending\" }, at);\n const { next: queueNext, item } = queue.enqueue(\n state.queue,\n payload,\n at,\n id,\n );\n const nextState: IntakeState<P, R> = { seen: seenNext, queue: queueNext };\n return [nextState, [intakeProcess({ key, itemId: item.id, payload })]];\n }\n\n /**\n * Record the finished `result` for `key` at clock reading `at`. PURE —\n * returns a NEW slice; the input is untouched. Emits no Cmds (completion is\n * a pure cache write; the host marks the queue item done via the work-queue\n * adapter at the effect boundary).\n *\n * Swaps the key's cached entry from `pending` to `done` so every subsequent\n * `receive` of the same key replays this `result`. `remember` stamps the\n * entry's `atMs` to `at`, which is the moment intake's `done`-TTL clock\n * starts (see `liveEntry`): a completed key stays replayable for `ttlMs`\n * past completion, not past first receipt.\n *\n * `complete` does NOT remove the queue item — the queue is the host's drain\n * ledger, and which items leave it (and when) is the host's call via the\n * work-queue adapter. `complete` owns the cache half only.\n */\n function complete(\n state: IntakeState<P, R>,\n key: string,\n result: R,\n at: number,\n ): readonly [IntakeState<P, R>, readonly IntakeCmd<P, R>[]] {\n const seenNext = memory.remember(\n state.seen,\n key,\n { phase: \"done\", result },\n at,\n );\n return [{ ...state, seen: seenNext }, []];\n }\n\n /**\n * Resume after a boot (Durable-Object reload / process restart). PURE —\n * returns a NEW slice; the input is untouched. Emits no Cmds.\n *\n * Resets every `running` queue item back to `pending` so the host's drain\n * re-claims it — EXCEPT an item whose key already resolved to `done` in\n * `seen`. That item's worker finished and cached its result before the crash;\n * re-arming it would re-run completed work (the complete-then-crash bug). It\n * is left as-is (and `claimNext` will likewise skip it), so the host drains\n * it via `markDone` without ever re-executing the side effect.\n *\n * A `running` item still `pending` in the cache is one a now-dead worker\n * claimed but never finished — that one IS reset, so it gets re-claimed.\n *\n * Returns the same reference when nothing changed (no running item needed\n * re-arming) so the host can skip a redundant save.\n *\n * Call once from a `boot` Msg the host dispatches after `run(...)` returns —\n * never from `init`'s rehydrate branch (that branch must be a pure\n * passthrough; see the substrate's `init` contract).\n */\n function boot(\n state: IntakeState<P, R>,\n ): readonly [IntakeState<P, R>, readonly IntakeCmd<P, R>[]] {\n // Selection lives HERE (the done-cache reconcile is intake's own rule); the\n // status FLIP is delegated to the `QueueAdapter`. A blanket reset would\n // re-arm completed-then-crashed items (it cannot see the done cache), so\n // intake picks the items and `queue.patch` performs each transition — the\n // same spread the work-queue lifecycle uses, not a re-rolled one.\n let items = state.queue;\n let changed = false;\n for (const it of state.queue) {\n if (it.status !== \"running\") continue;\n // Reconcile against the done cache: a completed-then-crashed item must\n // NOT be re-armed (it would re-run already-cached work).\n if (isAlreadyDone(state, it)) continue;\n const flip = queue.patch(items, it.id, (item) => ({\n ...item,\n status: \"pending\",\n startedAt: undefined,\n }));\n items = flip.next;\n changed = changed || flip.changed;\n }\n if (!changed) return [state, []];\n return [{ ...state, queue: items }, []];\n }\n\n /**\n * Claim the next pending queue item, flipping it `running` and stamping\n * `startedAt` to `at`. PURE — returns a NEW slice plus the claimed item, or\n * `null` if nothing claimable remains.\n *\n * \"Claimable\" is a pending item WHOSE KEY IS NOT ALREADY `done` in `seen`.\n * An item whose key completed before a crash (its `intake:process` ran, work\n * finished, result cached, but the item never left the queue) is skipped —\n * claiming it would re-run already-cached work. Such items are inert here;\n * the host drains them via `markDone`.\n *\n * This is the drain side of the intake, the mirror of `receive`'s enqueue.\n * It is a pure helper (not a verb that emits Cmds) because \"what to do with\n * the claimed item\" is the host's decision — typically: dispatch the worker,\n * await it, then call `complete`. Threading it through the slice keeps the\n * claim durable: the `running` stamp survives eviction, and `boot` re-arms\n * it if the worker died.\n */\n function claimNext(\n state: IntakeState<P, R>,\n at: number,\n ): {\n readonly state: IntakeState<P, R>;\n readonly claimed: QueueItem<P>;\n } | null {\n // Selection lives HERE (skip an item whose key already resolved `done`);\n // the status FLIP is delegated to the `QueueAdapter`. A blind `queue.claim`\n // would take the first PENDING item regardless of the done cache, which\n // would re-run already-cached work — so intake selects the eligible item and\n // `queue.patch` performs the running-stamp transition (the work-queue\n // lifecycle's own spread, not a re-rolled one).\n const target = state.queue.find(\n (it) => it.status === \"pending\" && !isAlreadyDone(state, it),\n );\n if (target === undefined) return null;\n // The patch closure is the single place the claimed item is materialized;\n // capture it here so the result needs no re-scan of the new queue.\n let claimed: QueueItem<P> = target;\n const { next } = queue.patch(state.queue, target.id, (item) => {\n claimed = { ...item, status: \"running\", startedAt: at };\n return claimed;\n });\n return { state: { ...state, queue: next }, claimed };\n }\n\n return { init, receive, complete, boot, claimNext };\n}\n"]}
@@ -1,272 +0,0 @@
1
- import { a as CmdOf, b as CmdDef, C as Cmd, I as Interpret } from './core-DnzqWy6u.js';
2
-
3
- /**
4
- * The snapshot knob.
5
- *
6
- * `every` is the cadence: a checkpoint is written once `every` units of
7
- * progress have accumulated since the last one (see `record`). It is the
8
- * `snapshotEvery` field `monitored-run` threads through to this module.
9
- *
10
- * `key` is the store key the checkpoint is written under. A single machine
11
- * keeps a single rolling checkpoint, so the key is fixed at config time rather
12
- * than passed per write — the latest checkpoint overwrites the previous one
13
- * (R2/KV are last-write-wins on a key). Defaults to `"@@snapshot"`, mirroring
14
- * `doStore`'s `@@state` default-key convention.
15
- */
16
- interface SnapshotConfig {
17
- /**
18
- * Write a checkpoint once this many progress units have accumulated since the
19
- * last write. `every: 1` checkpoints on every `record`; a value `<= 0` is
20
- * treated as `1` (every progress unit triggers a write) — a non-positive
21
- * cadence has no other sensible meaning, and clamping keeps `record` from
22
- * never checkpointing on a misconfigured `0`.
23
- */
24
- readonly every: number;
25
- /** Store key the rolling checkpoint is written under. Defaults to `"@@snapshot"`. */
26
- readonly key?: string;
27
- }
28
- /**
29
- * The snapshot bookkeeping slice. Plain data — JSON-serializable so it rides
30
- * inside the consumer's `Store<S>` blob and survives DO eviction / reload (the
31
- * "durable" non-negotiable). It carries NO payload: the thing being
32
- * checkpointed lives elsewhere in the machine's state; this slice only tracks
33
- * *when* to checkpoint and *what has been acknowledged durable*.
34
- *
35
- * - `sinceLast` — progress units accumulated since the last checkpoint
36
- * DECISION (reset to 0 the moment `record` decides to write, not when the
37
- * write lands). The cadence counter.
38
- * - `seq` — monotonic checkpoint sequence. Bumped each time a write is
39
- * emitted, so the write Cmd and its later acknowledgement can be matched
40
- * even if several are in flight. The "version" of the latest emitted
41
- * checkpoint.
42
- * - `lastSavedSeq` — the highest `seq` whose write the host has confirmed
43
- * durable (via `confirm`). `null` until the first confirmed write. The gap
44
- * `seq - lastSavedSeq` is the "writes emitted but not yet acknowledged"
45
- * depth — observable for backpressure / health.
46
- * - `lastSavedAt` — the `at` of the most recent confirmed write (epoch ms),
47
- * `null` until the first. Carried for observability, never compared inside
48
- * a verb (time is injected, never read here).
49
- */
50
- interface SnapshotState {
51
- readonly sinceLast: number;
52
- readonly seq: number;
53
- readonly lastSavedSeq: number | null;
54
- readonly lastSavedAt: number | null;
55
- }
56
- /**
57
- * The abstract checkpoint sink — the intersection of R2's and KV's surface,
58
- * narrowed to exactly the two methods a rolling checkpoint needs. Deliberately
59
- * NOT the substrate's `Store<S>` (`{ load, save, migrate }`): that is the
60
- * runtime's whole-state persistence boundary keyed at a fixed cell, whereas a
61
- * checkpoint store is keyed, value-typed, and host-supplied.
62
- *
63
- * - `put(key, value)` — write the checkpoint. Last-write-wins on `key`.
64
- * - `get(key)` — read the latest checkpoint back, or `null` if none. Used by
65
- * a recovery path to seed `init`'s `loaded` from the durable checkpoint
66
- * when the runtime's own `Store<S>` blob is gone.
67
- *
68
- * No `@cloudflare/workers-types` import — an R2 bucket, a KV namespace, a DO
69
- * sub-key, or an in-memory `Map` all satisfy this shape with a 3-line adapter
70
- * at the host.
71
- */
72
- interface SnapshotStore<V> {
73
- get(key: string): Promise<V | null>;
74
- put(key: string, value: V): Promise<void>;
75
- }
76
- /**
77
- * The ports `handlers` binds. Just the store today — named so a future port
78
- * (a metrics sink, a clock) can grow without breaking the `handlers(ports)`
79
- * call shape.
80
- */
81
- interface SnapshotPorts<V> {
82
- readonly store: SnapshotStore<V>;
83
- }
84
- /**
85
- * The checkpoint-write Cmd a `record` / `force` decision emits. Plain data
86
- * (invariant 1 — Cmds are data, not closures): it carries the `payload` to
87
- * write, the `key` to write it under, the `seq` it represents, and the `at` it
88
- * was decided. The handler performs the `put` and routes the result back to a
89
- * `confirm` / fail Msg.
90
- *
91
- * `payload` is `V` — the consumer's serializable checkpoint value (the run
92
- * state, the cursor, whatever the machine wants restartable). The verb takes it
93
- * as an argument so the snapshot slice never has to hold a copy of the
94
- * machine's domain state.
95
- *
96
- * `Cmd.define`d (ADR 0014), minted per `V` by a factory the way
97
- * `workflowActivityDef<A>()` is, since the payload type is the knob's own
98
- * parameter. The handler answers through this module's OWN Msgs
99
- * (`snapshot_saved` / `snapshot_failed`), never the minted `_ok` / `_err`,
100
- * hence `ok: z.void()` and an empty `err` list.
101
- */
102
- declare function snapshotWriteDef<V>(): CmdDef<"snapshot_write", {
103
- readonly key: string;
104
- readonly seq: number;
105
- readonly at: number;
106
- readonly payload: V;
107
- }, void, never, unknown>;
108
- type SnapshotWriteCmd<V> = CmdOf<ReturnType<typeof snapshotWriteDef<V>>>;
109
- /**
110
- * The Msg the write handler dispatches on a SUCCESSFUL `put`. Fold it back
111
- * through `confirm(state, msg)` to advance `lastSavedSeq` / `lastSavedAt`. The
112
- * `seq` matches the emitting Cmd so a late acknowledgement of an older write
113
- * can never regress a newer confirmed one.
114
- */
115
- type SnapshotSavedMsg = {
116
- readonly type: "snapshot_saved";
117
- readonly seq: number;
118
- readonly at: number;
119
- };
120
- /**
121
- * The Msg the write handler dispatches on a FAILED `put`. Errors are data
122
- * (never swallowed): the consumer's reducer decides whether a failed checkpoint
123
- * is fatal, retryable (compose `@demlik/tea/retry-backoff`), or merely logged.
124
- * `seq` identifies which checkpoint failed; `error` is carried, never
125
- * interpreted here.
126
- */
127
- type SnapshotFailedMsg = {
128
- readonly type: "snapshot_failed";
129
- readonly seq: number;
130
- readonly error: unknown;
131
- };
132
- /**
133
- * The checkpoint-READ Cmd a `requestLoad` decision emits — the recovery half of
134
- * the module. Plain data (invariant 1): it carries only the `key` to read. The
135
- * handler performs `store.get(key)` and routes the result back to a
136
- * `snapshot_loaded` (Ok, payload-or-null) / `snapshot_load_failed` (Err) Msg.
137
- *
138
- * Distinct from `snapshot_write`: a write carries a payload OUT to the store; a
139
- * load asks the store to hand a payload BACK. The consumer dispatches the
140
- * triggering Msg once on resume (a `boot` Msg, never `init`'s rehydrate branch —
141
- * invariant 2), folds the recovered payload into its run slice, then continues.
142
- */
143
- declare const snapshotLoad: CmdDef<"snapshot_load", {
144
- key: string;
145
- }, void, never, unknown>;
146
- type SnapshotLoadCmd = CmdOf<typeof snapshotLoad>;
147
- /**
148
- * The Msg the load handler dispatches when `store.get` RESOLVES. `payload` is
149
- * the recovered checkpoint value, or `null` when the store has no checkpoint
150
- * under `key` (fresh boot — never checkpointed, or a wiped store). The consumer
151
- * folds this into `init`'s loaded branch: `payload === null` → keep the fresh
152
- * `init` state; non-null → seed the run slice from the durable checkpoint and
153
- * `boot` the snapshot slice's cadence counter.
154
- *
155
- * A `null` payload is NOT an error — "no checkpoint yet" is a normal first-run
156
- * state, distinct from "the read failed" (`snapshot_load_failed`).
157
- */
158
- type SnapshotLoadedMsg<V> = {
159
- readonly type: "snapshot_loaded";
160
- readonly payload: V | null;
161
- };
162
- /**
163
- * The Msg the load handler dispatches when `store.get` REJECTS. Errors are data
164
- * (never swallowed — the field-guide rule): a failed recovery read is a visible
165
- * signal the consumer's reducer decides on (fatal-abort, retry via
166
- * `@demlik/tea/retry-backoff`, or boot-fresh-and-log). `error` is carried, never
167
- * interpreted here. Kept separate from `snapshot_loaded`'s `null` so "the store
168
- * has no checkpoint" can never masquerade as "the read failed".
169
- */
170
- type SnapshotLoadFailedMsg = {
171
- readonly type: "snapshot_load_failed";
172
- readonly error: unknown;
173
- };
174
- /** Construct a `snapshot_saved` Msg. Shared by the handler and tests so the wire shape can't drift. */
175
- declare function snapshotSaved(seq: number, at: number): SnapshotSavedMsg;
176
- /** Construct a `snapshot_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */
177
- declare function snapshotFailed(seq: number, error: unknown): SnapshotFailedMsg;
178
- /** Construct a `snapshot_loaded` Msg. Shared by the handler and tests so the wire shape can't drift. */
179
- declare function snapshotLoaded<V>(payload: V | null): SnapshotLoadedMsg<V>;
180
- /** Construct a `snapshot_load_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */
181
- declare function snapshotLoadFailed(error: unknown): SnapshotLoadFailedMsg;
182
- /**
183
- * The bundle `createSnapshot` returns — the uniform knob contract. `subs` is
184
- * omitted: a checkpoint is driven by progress (a `record` call from the
185
- * consumer's reducer), not by a timer, so this module has no subscriptions.
186
- */
187
- interface SnapshotKnob<V> {
188
- /** The initial slice — zero progress, no checkpoint emitted or confirmed yet. */
189
- init(): SnapshotState;
190
- /**
191
- * Account one unit of progress. PURE. Increments `sinceLast`; once the
192
- * accumulated count reaches the configured cadence, DECIDES to checkpoint:
193
- * resets `sinceLast` to 0, bumps `seq`, and emits a single `snapshot_write`
194
- * Cmd carrying `payload` + `key` + the new `seq` + `at`. Below the cadence it
195
- * returns the bumped slice and NO Cmd.
196
- *
197
- * `payload` is the checkpoint value to persist (the consumer's domain state);
198
- * `at` is the caller-stamped time the progress occurred (epoch ms) — injected,
199
- * never read from the clock here.
200
- */
201
- record(state: SnapshotState, payload: V, at: number): readonly [SnapshotState, readonly Cmd[]];
202
- /**
203
- * Force a checkpoint regardless of the cadence counter. PURE. Always bumps
204
- * `seq`, resets `sinceLast`, and emits the write Cmd. Use on a phase boundary
205
- * the cadence would otherwise miss — e.g. "the run just finished, checkpoint
206
- * the terminal state now" — so the last durable checkpoint is the final state,
207
- * not whatever the modulo happened to land on.
208
- */
209
- force(state: SnapshotState, payload: V, at: number): readonly [SnapshotState, readonly Cmd[]];
210
- /**
211
- * Record a confirmed durable write. PURE. Advances `lastSavedSeq` /
212
- * `lastSavedAt` to the acknowledged `seq` / `at`, but only forward: an
213
- * out-of-order acknowledgement of an OLDER `seq` (a slow write landing after a
214
- * newer one) is ignored, so the confirmed watermark is monotonic. Fold the
215
- * `snapshot_saved` Msg here.
216
- *
217
- * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`
218
- * (with `Cmd.none` — an ack emits nothing) for uniformity with every sibling
219
- * knob, so it drops straight into a `snapshot_saved` cell without the caller
220
- * re-wrapping a bare State.
221
- */
222
- confirm(state: SnapshotState, msg: SnapshotSavedMsg): readonly [SnapshotState, readonly Cmd[]];
223
- /**
224
- * Resume after a reload. PURE. Resets `sinceLast` to 0 because any
225
- * un-checkpointed progress accumulated before the crash is moot — recovery
226
- * resumes from the last DURABLE checkpoint (`get` from the store), so the
227
- * cadence counter starts fresh from there. `seq` / `lastSavedSeq` /
228
- * `lastSavedAt` are preserved (they describe what is durable, which a reload
229
- * does not change). Call from a `boot` Msg the host dispatches once after
230
- * `run(...)`, never from `init`'s rehydrate branch (invariant 2).
231
- *
232
- * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`
233
- * (with `Cmd.none` — a resume emits nothing) for uniformity with every sibling
234
- * knob, so it drops straight into a `snapshot_loaded` cell without the caller
235
- * re-wrapping a bare State.
236
- */
237
- boot(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];
238
- /**
239
- * Ask the store for the durable checkpoint. PURE. Emits a single
240
- * `snapshot_load` Cmd carrying the configured `key`; returns the slice
241
- * UNCHANGED (a read decides nothing about the cadence bookkeeping — only the
242
- * later `snapshot_loaded` fold, via `boot`, touches the slice). This is the
243
- * entry point of the recovery loop: the consumer dispatches a `boot` Msg once
244
- * after `run(...)`, folds `requestLoad` here, and the emitted Cmd's handler
245
- * reads `store.get(key)` and dispatches `snapshot_loaded` / `snapshot_load_failed`.
246
- *
247
- * Returns `readonly [SnapshotState, readonly Cmd[]]` (the reducer-cell shape)
248
- * so it drops straight into a `boot: (s) => snap.requestLoad(s)` cell.
249
- */
250
- requestLoad(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];
251
- /**
252
- * The `interpret` map for this knob's two effect Cmds:
253
- *
254
- * - `snapshot_write` — binds the store and performs the `put`, routing
255
- * Ok → `snapshot_saved` / Err → `snapshot_failed`.
256
- * - `snapshot_load` — binds the store and performs the `get`, routing
257
- * Ok → `snapshot_loaded` (payload-or-null) / Err → `snapshot_load_failed`.
258
- *
259
- * Railway throughout — neither effect throws into the runtime; the outcome is
260
- * always a Msg. Spread into your machine's `interpret`.
261
- */
262
- handlers(ports: SnapshotPorts<V>): Interpret<SnapshotSavedMsg | SnapshotFailedMsg | SnapshotLoadedMsg<V> | SnapshotLoadFailedMsg, SnapshotWriteCmd<V> | SnapshotLoadCmd, unknown>;
263
- }
264
- /**
265
- * Create a snapshot knob from a `SnapshotConfig`. The returned bundle is spliced
266
- * into a consumer machine: `init()` seeds the slice, `record` / `force` are
267
- * folded in reducer cells, `confirm` / `boot` handle the acknowledgement +
268
- * resume Msgs, and `handlers({ store })` supplies the write `interpret` cell.
269
- */
270
- declare function createSnapshot<V>(config: SnapshotConfig): SnapshotKnob<V>;
271
-
272
- export { type SnapshotWriteCmd as S, type SnapshotState as a, type SnapshotStore as b, type SnapshotSavedMsg as c, type SnapshotFailedMsg as d, type SnapshotLoadedMsg as e, type SnapshotLoadFailedMsg as f, type SnapshotLoadCmd as g, type SnapshotConfig as h, type SnapshotKnob as i, type SnapshotPorts as j, createSnapshot as k, snapshotLoad as l, snapshotLoadFailed as m, snapshotLoaded as n, snapshotSaved as o, snapshotWriteDef as p, snapshotFailed as s };