@demlik/tea 0.16.0 → 0.18.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 (119) hide show
  1. package/README.md +3 -2
  2. package/dist/agent/index.d.ts +182 -35
  3. package/dist/agent/index.js +11 -11
  4. package/dist/{chunk-5XU6ZJO2.js → chunk-AHW2CLPT.js} +80 -27
  5. package/dist/chunk-AHW2CLPT.js.map +1 -0
  6. package/dist/{chunk-CH5KTOEZ.js → chunk-AIIXV4XB.js} +65 -13
  7. package/dist/chunk-AIIXV4XB.js.map +1 -0
  8. package/dist/{chunk-BCTEOVOU.js → chunk-BRX3243I.js} +3 -3
  9. package/dist/{chunk-BCTEOVOU.js.map → chunk-BRX3243I.js.map} +1 -1
  10. package/dist/{chunk-IVJP3GKJ.js → chunk-C3G2QFEZ.js} +4 -4
  11. package/dist/{chunk-IVJP3GKJ.js.map → chunk-C3G2QFEZ.js.map} +1 -1
  12. package/dist/{chunk-GOX2CYIM.js → chunk-FNGZMVIT.js} +3 -3
  13. package/dist/chunk-FNGZMVIT.js.map +1 -0
  14. package/dist/{chunk-WT5PBPLB.js → chunk-H2ELKHI6.js} +16 -6
  15. package/dist/chunk-H2ELKHI6.js.map +1 -0
  16. package/dist/chunk-H3MWNH7A.js +119 -0
  17. package/dist/chunk-H3MWNH7A.js.map +1 -0
  18. package/dist/chunk-I7E7AD2S.js +213 -0
  19. package/dist/chunk-I7E7AD2S.js.map +1 -0
  20. package/dist/{chunk-NSIAORRY.js → chunk-IHUNKHDX.js} +3 -3
  21. package/dist/{chunk-NSIAORRY.js.map → chunk-IHUNKHDX.js.map} +1 -1
  22. package/dist/chunk-JVE2LBFJ.js +21 -0
  23. package/dist/chunk-JVE2LBFJ.js.map +1 -0
  24. package/dist/chunk-JVKU2UQC.js +34 -0
  25. package/dist/chunk-JVKU2UQC.js.map +1 -0
  26. package/dist/{chunk-MYNLY3HP.js → chunk-JYNYG5SF.js} +4 -4
  27. package/dist/{chunk-MYNLY3HP.js.map → chunk-JYNYG5SF.js.map} +1 -1
  28. package/dist/{chunk-EHUDXAKC.js → chunk-PCJA2HTZ.js} +437 -162
  29. package/dist/chunk-PCJA2HTZ.js.map +1 -0
  30. package/dist/{chunk-MVZS5FDM.js → chunk-PQSDUX6B.js} +3 -3
  31. package/dist/{chunk-MVZS5FDM.js.map → chunk-PQSDUX6B.js.map} +1 -1
  32. package/dist/{chunk-6NG4V5SB.js → chunk-WTSOW2MD.js} +5 -5
  33. package/dist/{chunk-6NG4V5SB.js.map → chunk-WTSOW2MD.js.map} +1 -1
  34. package/dist/{chunk-7MGXQZPW.js → chunk-XVIKSRCK.js} +4 -4
  35. package/dist/{chunk-7MGXQZPW.js.map → chunk-XVIKSRCK.js.map} +1 -1
  36. package/dist/{chunk-CIHSHLQX.js → chunk-YIMOFXST.js} +3 -3
  37. package/dist/{chunk-CIHSHLQX.js.map → chunk-YIMOFXST.js.map} +1 -1
  38. package/dist/{core-BbgtiXdg.d.ts → core-Ce9ncpuv.d.ts} +10 -5
  39. package/dist/do/index.d.ts +140 -64
  40. package/dist/do/index.js +17 -18
  41. package/dist/do/index.js.map +1 -1
  42. package/dist/drive-loop-CsUsW3Wo.d.ts +127 -0
  43. package/dist/{durable-effects-7MTK_jnj.d.ts → durable-effects-BmUrRA-h.d.ts} +1 -1
  44. package/dist/effect/index.d.ts +6 -150
  45. package/dist/effect/index.js +4 -96
  46. package/dist/effect/index.js.map +1 -1
  47. package/dist/extension/index.d.ts +7 -5
  48. package/dist/extension/index.js +2 -3
  49. package/dist/extension/index.js.map +1 -1
  50. package/dist/flow/index.d.ts +13 -13
  51. package/dist/flow/index.js +13 -12
  52. package/dist/flow/index.js.map +1 -1
  53. package/dist/idempotency/index.d.ts +3 -3
  54. package/dist/idempotency/index.js +4 -4
  55. package/dist/{index-Cou86-GX.d.ts → index-2kNxy6az.d.ts} +2 -2
  56. package/dist/{index-DU6JpQ8G.d.ts → index-B1Wzarkl.d.ts} +1 -1
  57. package/dist/{index-_dOR3xqA.d.ts → index-Bf9_bNql.d.ts} +2 -2
  58. package/dist/index-BnsAqn0-.d.ts +286 -0
  59. package/dist/{index-CkFYA9Y6.d.ts → index-Ce5GYAuI.d.ts} +2 -2
  60. package/dist/{index-CjFDUBeq.d.ts → index-D8sYVzCI.d.ts} +1 -1
  61. package/dist/{index-7vV8xmsD.d.ts → index-DWdg7R1l.d.ts} +1 -1
  62. package/dist/{index-Bz5Rh9Ga.d.ts → index-Dy3379ve.d.ts} +5 -5
  63. package/dist/{index-Xufl55W8.d.ts → index-e36uEyl4.d.ts} +3 -3
  64. package/dist/index.d.ts +109 -36
  65. package/dist/index.js +2 -2
  66. package/dist/jev/index.d.ts +8 -8
  67. package/dist/jev/index.js +6 -7
  68. package/dist/jev/index.js.map +1 -1
  69. package/dist/{machine-BQzTV8On.d.ts → machine-CuojfZup.d.ts} +355 -59
  70. package/dist/machine-viz/index.d.ts +1 -1
  71. package/dist/machine-viz/index.js +2 -2
  72. package/dist/mem/index.d.ts +6 -6
  73. package/dist/mem/index.js +6 -2
  74. package/dist/mem/index.js.map +1 -1
  75. package/dist/node/index.d.ts +16 -10
  76. package/dist/node/index.js +22 -3
  77. package/dist/node/index.js.map +1 -1
  78. package/dist/otel/index.d.ts +161 -0
  79. package/dist/otel/index.js +403 -0
  80. package/dist/otel/index.js.map +1 -0
  81. package/dist/paginate/index.d.ts +4 -4
  82. package/dist/paginate/index.js +5 -5
  83. package/dist/parity/index.d.ts +3 -3
  84. package/dist/parity/index.js +3 -3
  85. package/dist/pbt/index.d.ts +166 -53
  86. package/dist/pbt/index.js +2 -2
  87. package/dist/pbt/index.js.map +1 -1
  88. package/dist/persistence/index.d.ts +9 -10
  89. package/dist/persistence/index.js +5 -5
  90. package/dist/promise/index.d.ts +2 -2
  91. package/dist/promise/index.js +4 -3
  92. package/dist/react/index.d.ts +2 -2
  93. package/dist/resilience/index.d.ts +13 -9
  94. package/dist/resilience/index.js +5 -5
  95. package/dist/resilience/index.js.map +1 -1
  96. package/dist/{runtime-types-CiVpC0HN.d.ts → runtime-types-BKg2CGMx.d.ts} +89 -15
  97. package/dist/testing/effect/index.d.ts +65 -0
  98. package/dist/testing/effect/index.js +148 -0
  99. package/dist/testing/effect/index.js.map +1 -0
  100. package/dist/testing/index.d.ts +25 -257
  101. package/dist/testing/index.js +25 -301
  102. package/dist/testing/index.js.map +1 -1
  103. package/dist/testing/promise/index.d.ts +41 -0
  104. package/dist/testing/promise/index.js +37 -0
  105. package/dist/testing/promise/index.js.map +1 -0
  106. package/dist/timing/index.d.ts +4 -4
  107. package/dist/timing/index.js +4 -4
  108. package/dist/{types-LxZxGYda.d.ts → types-SzNSHKlV.d.ts} +1 -1
  109. package/dist/work-queue/index.d.ts +4 -4
  110. package/dist/work-queue/index.js +4 -3
  111. package/dist/work-queue/index.js.map +1 -1
  112. package/package.json +27 -15
  113. package/dist/chunk-5XU6ZJO2.js.map +0 -1
  114. package/dist/chunk-CH5KTOEZ.js.map +0 -1
  115. package/dist/chunk-EHUDXAKC.js.map +0 -1
  116. package/dist/chunk-G5E74IJB.js +0 -8
  117. package/dist/chunk-G5E74IJB.js.map +0 -1
  118. package/dist/chunk-GOX2CYIM.js.map +0 -1
  119. package/dist/chunk-WT5PBPLB.js.map +0 -1
@@ -0,0 +1,213 @@
1
+ import { defaultOnError, startLoop, builtinExtensions, liveWork, RuntimeStoppedError } from './chunk-AHW2CLPT.js';
2
+ import { Outcome, subEntriesOf, applyCell, StoreRefusedError, DispatchDiscardedError } from './chunk-AIIXV4XB.js';
3
+ import { Data, Stream, Effect, Exit, Cause, Result, Scope, Fiber } from 'effect';
4
+
5
+ var Stopped = class extends Data.TaggedError("Stopped") {
6
+ get message() {
7
+ return `@demlik/tea: Msg "${this.msgType}" was refused \u2014 the run is ${this.when}.`;
8
+ }
9
+ };
10
+ var StoreFailed = class extends Data.TaggedError("StoreFailed") {
11
+ get message() {
12
+ return `@demlik/tea: store ${this.operation} failed.`;
13
+ }
14
+ };
15
+ var CellFailure = class {
16
+ constructor(failure) {
17
+ this.failure = failure;
18
+ }
19
+ failure;
20
+ };
21
+ var SaveFailure = class {
22
+ constructor(cause) {
23
+ this.cause = cause;
24
+ }
25
+ cause;
26
+ };
27
+ function unwrapped(error) {
28
+ if (error instanceof CellFailure) return error.failure;
29
+ if (error instanceof SaveFailure) return error.cause;
30
+ return error;
31
+ }
32
+ function saveFailures() {
33
+ return () => ({
34
+ store: (store) => ({
35
+ load: () => store.load(),
36
+ async save(state) {
37
+ try {
38
+ await store.save(state);
39
+ } catch (cause) {
40
+ throw new SaveFailure(cause);
41
+ }
42
+ },
43
+ migrate: (raw) => store.migrate(raw)
44
+ })
45
+ });
46
+ }
47
+ function exitOf(error) {
48
+ if (error instanceof CellFailure) return Exit.fail(error.failure);
49
+ if (error instanceof SaveFailure) {
50
+ return Exit.fail(
51
+ new StoreFailed({ operation: "save", cause: error.cause })
52
+ );
53
+ }
54
+ if (error instanceof StoreRefusedError) {
55
+ return Exit.fail(new StoreFailed({ operation: "load", cause: error }));
56
+ }
57
+ if (error instanceof DispatchDiscardedError) {
58
+ return Exit.fail(new Stopped({ msgType: error.msgType, when: "stopping" }));
59
+ }
60
+ if (error instanceof RuntimeStoppedError) {
61
+ return Exit.fail(new Stopped({ msgType: error.msgType, when: "stopped" }));
62
+ }
63
+ return Exit.die(error);
64
+ }
65
+ function settled(call) {
66
+ return Effect.flatten(
67
+ Effect.promise(
68
+ () => call().then(
69
+ (value) => Effect.succeed(value),
70
+ (error) => exitOf(error)
71
+ )
72
+ )
73
+ );
74
+ }
75
+ function effectHandle(loop) {
76
+ const handle = {
77
+ [liveWork]: loop[liveWork],
78
+ dispatch: (msg, opts) => settled(() => loop.dispatch(msg, opts)),
79
+ dispatchOnce: (msg) => settled(() => loop.dispatchOnce(msg)),
80
+ subscribe: (listener) => loop.subscribe(listener),
81
+ observe: (observer) => loop.observe(observer),
82
+ onBoot: (handler) => loop.onBoot(handler),
83
+ on: (type, handler) => loop.on(type, handler),
84
+ subscribePort: (port, listener) => loop.subscribePort(port, listener),
85
+ emitPort: (port, value) => loop.emitPort(port, value),
86
+ getState: () => loop.getState(),
87
+ result: () => loop.result(),
88
+ // Boot never passes the dispatch gate, so `ready` never fails `Stopped`.
89
+ // Suspended: `handle` is not built yet when this line runs.
90
+ ready: Effect.suspend(
91
+ () => settled(() => loop.ready).pipe(Effect.as(handle))
92
+ ),
93
+ // `idle` rejects only on a livelock, and `done` and `stop` never do: a
94
+ // rejection here is a defect.
95
+ idle: () => Effect.promise(() => loop.idle()),
96
+ done: () => Effect.promise(() => loop.done()),
97
+ stop: () => Effect.promise(() => loop.stop())
98
+ };
99
+ return handle;
100
+ }
101
+
102
+ // src/effect/run.ts
103
+ function timer(sub) {
104
+ return Stream.fromEffect(Effect.as(Effect.sleep(sub.deps.ms), sub.deps.msg));
105
+ }
106
+ var builtinRunners = { timer };
107
+ function cellResult(effect) {
108
+ return Effect.result(effect);
109
+ }
110
+ function settleCellExit(exit, defined) {
111
+ if (Exit.isFailure(exit)) {
112
+ if (Cause.hasInterruptsOnly(exit.cause)) return void 0;
113
+ throw Cause.squash(exit.cause);
114
+ }
115
+ const result = exit.value;
116
+ if (defined) {
117
+ return Result.isSuccess(result) ? Outcome.ok(result.success) : Outcome.err(result.failure);
118
+ }
119
+ if (Result.isFailure(result)) throw new CellFailure(result.failure);
120
+ return result.success;
121
+ }
122
+ function run(machine, opts) {
123
+ return Effect.gen(function* () {
124
+ const services = yield* Effect.context();
125
+ const scope = yield* Scope.Scope;
126
+ const fork = Effect.runForkWith(services);
127
+ return yield* Effect.acquireRelease(
128
+ Effect.sync(
129
+ () => effectHandle(
130
+ start(
131
+ machine,
132
+ opts,
133
+ Effect.runPromiseExitWith(services),
134
+ fork,
135
+ // Closing the Scope runs the release below, which stops the run.
136
+ // A Scope that closes before that release is added runs it on add.
137
+ (exit) => fork(Scope.close(scope, exit))
138
+ )
139
+ )
140
+ ),
141
+ (runtime) => runtime.stop()
142
+ );
143
+ });
144
+ }
145
+ function start(machine, opts, runEffect, fork, closeScope) {
146
+ const interpret = opts.interpret ?? {};
147
+ const subscribe = opts.subscribe ?? {};
148
+ const builtins = builtinRunners;
149
+ const defined = new Set((machine.cmds ?? []).map((def) => def.cmdType));
150
+ const interruption = new AbortController();
151
+ async function settle(cell, cmd) {
152
+ const type = cmd.type;
153
+ const exit = await runEffect(cellResult(cell(cmd)), {
154
+ signal: interruption.signal
155
+ });
156
+ return settleCellExit(exit, defined.has(type));
157
+ }
158
+ function handlerFor(type) {
159
+ const cell = interpret[type];
160
+ if (cell === void 0) return void 0;
161
+ return (cmd) => settle(cell, cmd);
162
+ }
163
+ let subFailure;
164
+ function runnerFor(type) {
165
+ const make = subscribe[type] ?? builtins[type];
166
+ if (make === void 0) return void 0;
167
+ return (sub, _ctx, dispatch, fail) => {
168
+ const drain = Stream.runForEach(
169
+ make(sub),
170
+ (msg) => Effect.sync(() => dispatch(msg))
171
+ ).pipe(
172
+ Effect.catchCause(
173
+ (cause) => Cause.hasInterruptsOnly(cause) ? Effect.void : Effect.sync(() => {
174
+ subFailure ??= cause;
175
+ fail(Cause.squash(cause));
176
+ })
177
+ )
178
+ );
179
+ const fiber = fork(drain);
180
+ return () => Effect.runPromise(Fiber.interrupt(fiber));
181
+ };
182
+ }
183
+ const sink = opts.onError ?? defaultOnError;
184
+ const handle = startLoop({
185
+ init: machine.init,
186
+ reduce: (state, msg) => applyCell(machine, state, msg),
187
+ subs: subEntriesOf(machine),
188
+ runnerFor,
189
+ handlerFor,
190
+ store: opts.store,
191
+ ctx: opts.ctx ?? {},
192
+ onError: (error, context) => sink(unwrapped(error), context),
193
+ disposeTimeoutMs: opts.disposeTimeoutMs ?? 5e3,
194
+ idleCap: 1e5,
195
+ // The save marker wraps outermost, around the fence the built-ins add.
196
+ extensions: [
197
+ saveFailures(),
198
+ ...builtinExtensions(machine, opts)
199
+ ],
200
+ stopOnSubFailure: (error) => closeScope(Exit.failCause(subFailure ?? Cause.die(error)))
201
+ });
202
+ const stopLoop = handle.stop;
203
+ handle.stop = () => {
204
+ const stopped = stopLoop();
205
+ interruption.abort();
206
+ return stopped;
207
+ };
208
+ return handle;
209
+ }
210
+
211
+ export { CellFailure, Stopped, StoreFailed, builtinRunners, cellResult, run, settleCellExit };
212
+ //# sourceMappingURL=chunk-I7E7AD2S.js.map
213
+ //# sourceMappingURL=chunk-I7E7AD2S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/effect/failures.ts","../src/effect/handle.ts","../src/effect/run.ts"],"names":["Effect","Exit"],"mappings":"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","file":"chunk-I7E7AD2S.js","sourcesContent":["/**\n * The Effect engine's typed failures (#308, ruling R4.1 of #325), and the edge\n * that turns what the shared loop rejects with into them.\n *\n * The loop is engine-neutral: it rejects a dispatch with whatever was thrown.\n * Two throws it cannot tell apart from a bug — a hand-written cell's declared\n * failure and a store's failed save — are wrapped here on the way in, so the\n * edge can read them on the way out. The wrappers never reach the caller: a\n * handle verb fails with the typed value, and the `onError` sink is handed the\n * value it was always handed.\n */\n\nimport { Data, Exit } from \"effect\";\nimport {\n type ExtensionFactory,\n RuntimeStoppedError,\n} from \"../internal/engine/loop\";\nimport {\n DispatchDiscardedError,\n type Store,\n StoreRefusedError,\n} from \"../runtime-types\";\n\n/**\n * A dispatch the run refused because it is stopping or has stopped. The Msg\n * was never folded.\n *\n * `when` is `\"stopping\"` for a Msg that arrived while `stop()` (or a closing\n * Scope) drained the run's in-flight work, and `\"stopped\"` once it had.\n */\nexport class Stopped extends Data.TaggedError(\"Stopped\")<{\n readonly msgType: string;\n readonly when: \"stopping\" | \"stopped\";\n}> {\n override get message(): string {\n return (\n `@demlik/tea: Msg \"${this.msgType}\" was refused — the run is ` +\n `${this.when}.`\n );\n }\n}\n\n/**\n * The run's store failed. `operation` is `\"load\"` when the saved state could\n * not be restored at boot — `cause` is then the `StoreRefusedError` that says\n * why — and `\"save\"` when a transition's save threw, a fenced store's\n * `StoreConflictError` included; `cause` is then the store's own throw.\n */\nexport class StoreFailed extends Data.TaggedError(\"StoreFailed\")<{\n readonly operation: \"load\" | \"save\";\n readonly cause: unknown;\n}> {\n override get message(): string {\n return `@demlik/tea: store ${this.operation} failed.`;\n }\n}\n\n/** A hand-written cell's declared failure, on its way through the loop. */\nexport class CellFailure {\n constructor(readonly failure: unknown) {}\n}\n\n/** A store's failed save, on its way through the loop. */\nclass SaveFailure {\n constructor(readonly cause: unknown) {}\n}\n\n/** What was thrown, with the edge's wrapper taken off. */\nexport function unwrapped(error: unknown): unknown {\n if (error instanceof CellFailure) return error.failure;\n if (error instanceof SaveFailure) return error.cause;\n return error;\n}\n\n/**\n * The outermost store wrapper: every save that throws is marked as a store\n * failure. Outermost, so it wraps the fenced compare-and-swap too, and its\n * `StoreConflictError` fails as a `StoreFailed`.\n */\nexport function saveFailures<S, M, C>(): ExtensionFactory<S, M, C> {\n return () => ({\n store: (store: Store<S>): Store<S> => ({\n load: () => store.load(),\n async save(state) {\n try {\n await store.save(state);\n } catch (cause) {\n throw new SaveFailure(cause);\n }\n },\n migrate: (raw) => store.migrate(raw),\n }),\n });\n}\n\n/**\n * The Exit a handle verb ends with when the loop rejected with `error`: a\n * typed failure for a refused dispatch, a failed store and a cell's declared\n * failure, and a defect for anything else — a reducer throw, a Msg with no\n * cell, a livelock — which ADR 0011 keeps a contract breach.\n */\nexport function exitOf<Err>(\n error: unknown,\n): Exit.Exit<never, Err | Stopped | StoreFailed> {\n if (error instanceof CellFailure) return Exit.fail(error.failure as Err);\n if (error instanceof SaveFailure) {\n return Exit.fail(\n new StoreFailed({ operation: \"save\", cause: error.cause }),\n );\n }\n if (error instanceof StoreRefusedError) {\n return Exit.fail(new StoreFailed({ operation: \"load\", cause: error }));\n }\n if (error instanceof DispatchDiscardedError) {\n return Exit.fail(new Stopped({ msgType: error.msgType, when: \"stopping\" }));\n }\n if (error instanceof RuntimeStoppedError) {\n return Exit.fail(new Stopped({ msgType: error.msgType, when: \"stopped\" }));\n }\n return Exit.die(error);\n}\n","/**\n * The Effect engine's run handle (#308, ruling R4.1 of #325). Its members have\n * the Promise engine's names (the shared-handle ruling of #250); only the\n * return types differ. Each member that returns a Promise there returns an\n * Effect here, with a typed error channel a host reads with `Effect.catchTags`.\n * Members that neither wait nor fail — `getState`, `result` and the listener\n * registrations — are the same functions on both engines.\n */\n\nimport { Effect } from \"effect\";\nimport { type LiveWorkProbe, liveWork } from \"../internal/engine/loop\";\nimport type { Port } from \"../pure/core\";\nimport type { DispatchSettle, Runtime } from \"../runtime-types\";\nimport { exitOf, type Stopped, type StoreFailed } from \"./failures\";\n\n/**\n * What the Effect engine's `run` yields while boot is in flight. `Err` is the\n * union of the declared failures of the run's hand-written cells: a dispatch\n * whose Cmd's cell fails with one fails with it. Reading State and waiting for\n * quiescence need boot to have run, so they live on the {@link EffectRuntime}\n * that `ready` succeeds with.\n */\nexport interface EffectBootingRuntime<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n Err = never,\n> {\n /**\n * Fold `msg` and run to quiescence, as the Promise engine's `dispatch` does.\n * Fails with `Stopped` once the run is stopping or stopped, `StoreFailed`\n * when the transition's save fails, and a hand-written cell's declared\n * failure when the transition's Cmd fails with one.\n */\n dispatch(\n msg: M,\n opts?: { readonly settle?: DispatchSettle },\n ): Effect.Effect<void, Err | Stopped | StoreFailed>;\n /** `dispatch(msg, { settle: \"once\" })`: one transition, no follow-up drain. */\n dispatchOnce(msg: M): Effect.Effect<void, Err | Stopped | StoreFailed>;\n /** A zero-arg change notifier, fired after each applied transition. */\n subscribe(listener: () => void): () => void;\n /** A `(msg, state)` hook, fired after each applied transition. */\n observe(observer: (msg: M, state: S) => void): () => void;\n /** Fires once with the initial State — at once if boot already ran. */\n onBoot(handler: (state: S) => void): () => void;\n /** Subscribe to the semantic event of `type` the run's `events` projects. */\n on<K extends E[\"type\"]>(\n type: K,\n handler: (event: Extract<E, { type: K }>) => void,\n ): () => void;\n /** Subscribe to a typed Port. */\n subscribePort<T>(port: Port<T>, listener: (value: T) => void): () => void;\n /** Emit a value on a Port from outside a Cmd handler. */\n emitPort<T>(port: Port<T>, value: T): void;\n /**\n * Succeeds with the booted handle once boot completes. Fails with\n * `StoreFailed` when the saved state could not be restored (`\"load\"`) or the\n * initial save failed (`\"save\"`), and with a hand-written cell's declared\n * failure when a boot Cmd fails with one.\n */\n readonly ready: Effect.Effect<EffectRuntime<S, M, E, Err>, Err | StoreFailed>;\n /**\n * Stop the run: drain in-flight work, stop its Subs, flush the final State.\n * Closing the run's Scope does the same.\n */\n stop(): Effect.Effect<void>;\n}\n\n/** The Effect engine's booted handle — what `ready` succeeds with. */\nexport interface EffectRuntime<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n Err = never,\n> extends EffectBootingRuntime<S, M, E, Err> {\n /** The current State. Total. */\n getState(): S;\n readonly ready: Effect.Effect<EffectRuntime<S, M, E, Err>, Err | StoreFailed>;\n /** Succeeds once every dispatched Msg and its follow-ups are processed. */\n idle(): Effect.Effect<void>;\n /** The terminal State, or `undefined` while the run is in flight. */\n result(): S | undefined;\n /** Succeeds with the terminal State the first time the run reaches one. */\n done(): Effect.Effect<S>;\n}\n\n/** Run a loop call, ending with the typed Exit its rejection maps to. */\nfunction settled<A, Err>(\n call: () => Promise<A>,\n): Effect.Effect<A, Err | Stopped | StoreFailed> {\n return Effect.flatten(\n Effect.promise(() =>\n call().then(\n (value) => Effect.succeed(value),\n (error: unknown) => exitOf<Err>(error),\n ),\n ),\n );\n}\n\n/** Build the Effect handle over the loop's Promise one. */\nexport function effectHandle<\n S,\n M extends { type: string },\n E extends { type: string },\n Err,\n>(\n loop: Runtime<S, M, E> & LiveWorkProbe,\n): EffectRuntime<S, M, E, Err> & LiveWorkProbe {\n const handle: EffectRuntime<S, M, E, Err> & LiveWorkProbe = {\n [liveWork]: loop[liveWork],\n dispatch: (msg, opts) => settled(() => loop.dispatch(msg, opts)),\n dispatchOnce: (msg) => settled(() => loop.dispatchOnce(msg)),\n subscribe: (listener) => loop.subscribe(listener),\n observe: (observer) => loop.observe(observer),\n onBoot: (handler) => loop.onBoot(handler),\n on: (type, handler) => loop.on(type, handler),\n subscribePort: (port, listener) => loop.subscribePort(port, listener),\n emitPort: (port, value) => loop.emitPort(port, value),\n getState: () => loop.getState(),\n result: () => loop.result(),\n // Boot never passes the dispatch gate, so `ready` never fails `Stopped`.\n // Suspended: `handle` is not built yet when this line runs.\n ready: Effect.suspend(() =>\n settled<unknown, Err>(() => loop.ready).pipe(Effect.as(handle)),\n ) as Effect.Effect<EffectRuntime<S, M, E, Err>, Err | StoreFailed>,\n // `idle` rejects only on a livelock, and `done` and `stop` never do: a\n // rejection here is a defect.\n idle: () => Effect.promise(() => loop.idle()),\n done: () => Effect.promise(() => loop.done()),\n stop: () => Effect.promise(() => loop.stop()),\n };\n return handle;\n}\n","/**\n * The Effect engine's `run` (#283): the same machine the Promise engine runs,\n * with Effect handlers, Effect sub runners, the caller's Layers and\n * interruption on stop.\n *\n * It runs the one core loop both engines share (`src/internal/engine/loop.ts`)\n * with the same built-ins in the same order, so a machine folds the same way on\n * either engine. What this file adds is the Effect edge:\n *\n * - an `interpret` cell returns `Effect<Ok, E, R>`, read with\n * `Effect.result`: a success settles `_ok`, a declared failure settles\n * `_err`, a defect goes to the error sink;\n * - a `subscribe` runner returns a `Stream<Msg>`, drained on its own fiber;\n * - every handler and runner fiber runs with the services the caller\n * provided, so `R` reaches them from the caller's Layers;\n * - stopping interrupts every handler fiber still in flight;\n * - the handle's verbs are Effects with a typed error channel\n * (`./handle`, `./failures`).\n */\n\nimport { Cause, Effect, Exit, Fiber, Result, Scope, Stream } from \"effect\";\nimport { builtinExtensions } from \"../internal/engine/builtins\";\nimport {\n defaultOnError,\n type LiveWorkProbe,\n type LoopHandler,\n type LoopRunner,\n startLoop,\n} from \"../internal/engine/loop\";\nimport type {\n BuiltinSub,\n BuiltinSubType,\n Cmd,\n DeclaredErrorsOf,\n ErrorsOf,\n Machine,\n OkOfCmd,\n Sub,\n TimerSub,\n} from \"../pure/core\";\nimport { applyCell, Outcome, subEntriesOf } from \"../pure/core\";\nimport type {\n CtxArg,\n OnError,\n Runtime,\n Store,\n Supervision,\n TelemetrySink,\n} from \"../runtime-types\";\nimport { CellFailure, saveFailures, unwrapped } from \"./failures\";\nimport { type EffectBootingRuntime, effectHandle } from \"./handle\";\n\n// === The handler and runner shapes ===\n\n/**\n * One Effect `interpret` cell. For a `Cmd.define`d Cmd it succeeds with the\n * Cmd's `Ok` and fails with one of its declared tags, and the engine mints\n * `<name>_ok` / `<name>_err` from that (ADR 0021). For a hand-written Cmd it\n * succeeds with a follow-up Msg, a list of them (dispatched in order), or\n * nothing, and its failure fails the dispatch with that error, typed on the\n * handle (see {@link CellErrors}). `R` is whatever services it reads.\n */\nexport type EffectInterpretCell<\n M extends { type: string },\n C extends Cmd,\n R = unknown,\n> =\n unknown extends ErrorsOf<C>\n ? // biome-ignore lint/suspicious/noConfusingVoidType: a hand-written Cmd's handler may settle with nothing\n (cmd: C) => Effect.Effect<M | readonly M[] | void, unknown, R>\n : (cmd: C) => Effect.Effect<OkOfCmd<C>, DeclaredErrorsOf<C>, R>;\n\n/**\n * The Effect engine's `interpret` map: one cell per Cmd variant, each reading\n * services within `R`.\n */\nexport type EffectInterpret<\n M extends { type: string },\n C extends Cmd,\n R = unknown,\n> = {\n readonly [K in C[\"type\"]]: EffectInterpretCell<M, Extract<C, { type: K }>, R>;\n};\n\n/**\n * One Effect sub runner: the Sub in, a `Stream` of Msgs out. The engine drains\n * it on its own fiber and interrupts that fiber when the Sub stops.\n *\n * A Sub maps the errors it expects into Msgs itself, the way an Elm `Sub msg`\n * carries no error type: `Stream.catchTag(\"Closed\", () => Stream.make({ type:\n * \"disconnected\" }))`. A stream that still fails reaches the error sink under\n * `\"sub\"` and stops the run: the run's Scope closes with that failure (#309).\n */\nexport type EffectRunner<\n M extends { type: string },\n U extends Sub,\n R = unknown,\n> = (sub: U) => Stream.Stream<M, unknown, R>;\n\n/**\n * The Effect engine's `subscribe` map: a runner for every Sub type the machine\n * declares beyond the built-ins, and optionally one for a built-in, which\n * replaces the engine's own (#270 R2.1).\n */\nexport type EffectSubscribe<\n M extends { type: string },\n U extends Sub,\n R = unknown,\n> = {\n readonly [K in Exclude<U[\"type\"], BuiltinSubType>]: EffectRunner<\n M,\n Extract<U, { type: K }>,\n R\n >;\n} & {\n readonly [K in BuiltinSubType]?: EffectRunner<\n M,\n Extract<BuiltinSub<M>, { type: K }>,\n R\n >;\n};\n\n/** The services every cell of an `interpret` map reads. */\nexport type InterpretServices<I> = {\n [K in keyof I]: ServicesOfCell<I[K]>;\n}[keyof I];\n\ntype ServicesOfCell<F> = F extends (\n cmd: never,\n) => Effect.Effect<unknown, unknown, infer R>\n ? R\n : never;\n\n/**\n * The failures a dispatch on the handle can end with from an `interpret` map:\n * the error type of every hand-written Cmd's cell. A `Cmd.define`d Cmd's\n * declared failure settles its `_err` Msg instead, so it adds nothing here.\n */\nexport type CellErrors<C extends Cmd, I> = {\n [K in keyof I & C[\"type\"]]: unknown extends ErrorsOf<Extract<C, { type: K }>>\n ? ErrorOfCell<I[K]>\n : never;\n}[keyof I & C[\"type\"]];\n\ntype ErrorOfCell<F> = F extends (\n cmd: never,\n) => Effect.Effect<unknown, infer Err, unknown>\n ? Err\n : never;\n\n/** The services every runner of a `subscribe` map reads. */\nexport type SubscribeServices<B> = {\n [K in keyof B]-?: ServicesOfRunner<NonNullable<B[K]>>;\n}[keyof B];\n\ntype ServicesOfRunner<F> = F extends (\n sub: never,\n) => Stream.Stream<unknown, unknown, infer R>\n ? R\n : never;\n\n// `interpret` is optional for a machine that emits no Cmd, and `subscribe` for\n// one that declares only built-in Subs — the same requiredness the Promise\n// engine's `RunHandlers` carries.\ntype InterpretOption<C extends Cmd, I> = [C] extends [Cmd<never>]\n ? { readonly interpret?: I }\n : { readonly interpret: I };\n\nexport type SubscribeOption<U extends Sub, B> = [\n Exclude<U[\"type\"], BuiltinSubType>,\n] extends [never]\n ? { readonly subscribe?: B }\n : { readonly subscribe: B };\n\n/**\n * The options of the Effect engine's `run`. Beside `interpret` and\n * `subscribe`, they are the Promise engine's, with the same meaning; see `run`\n * in `@demlik/tea/promise` for each one's contract.\n */\nexport type EffectRunOptions<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n E extends { type: string },\n I,\n B,\n> = CtxArg<Ctx> &\n InterpretOption<C, I> &\n SubscribeOption<U, B> & {\n readonly store?: Store<S>;\n readonly onError?: OnError;\n /** Stamps `at` on minted Msgs and telemetry. Defaults to `Date.now`. */\n readonly clock?: () => number;\n /** Projects each applied transition to the public events `on` serves. */\n readonly events?: (msg: M, state: S) => readonly E[];\n /** The policy for a reducer throw. Defaults to `\"stop\"`. */\n readonly supervision?: Supervision<S, M>;\n /** The terminal predicate `result()` and `done()` read. */\n readonly terminal?: (state: S) => boolean;\n /** A fire-and-forget sink for every applied transition. */\n readonly telemetry?: TelemetrySink;\n /** How long `stop()` waits for async teardown. Defaults to 5_000ms. */\n readonly disposeTimeoutMs?: number;\n };\n\n// === Built-in runners ===\n\n/** `timer`: emit `deps.msg` once, `deps.ms` after the Sub starts. */\nfunction timer<M>(sub: TimerSub<M>): Stream.Stream<M> {\n return Stream.fromEffect(Effect.as(Effect.sleep(sub.deps.ms), sub.deps.msg));\n}\n\n/** The Effect engine's built-in runners, keyed by the Sub type each runs. */\nexport const builtinRunners = { timer } as const satisfies Record<\n BuiltinSubType,\n unknown\n>;\n\n// === Settling a cell ===\n\n/**\n * The Effect a cell's Effect is run as: `Effect.result` makes a declared\n * failure a value, so only a defect or an interruption is left in the Exit's\n * cause. Read the Exit back with {@link settleCellExit}.\n */\nexport function cellResult(\n effect: Effect.Effect<unknown, unknown>,\n): Effect.Effect<Result.Result<unknown, unknown>> {\n return Effect.result(effect);\n}\n\n/**\n * What one cell's run settles to, the rule the engine and the Effect `drive`\n * share so they cannot drift:\n *\n * - interrupted — `undefined`, nothing to dispatch;\n * - a defect — thrown;\n * - a `Cmd.define`d Cmd (`defined`) — its success as `Outcome.ok`, its\n * declared failure as `Outcome.err`, for the `Cmd.define` edge to mint;\n * - a hand-written Cmd — its success (the follow-up Msgs) as is, its failure\n * thrown as a {@link CellFailure}, so a caller types it.\n */\nexport function settleCellExit(\n exit: Exit.Exit<unknown, unknown>,\n defined: boolean,\n): unknown {\n if (Exit.isFailure(exit)) {\n if (Cause.hasInterruptsOnly(exit.cause)) return undefined;\n throw Cause.squash(exit.cause);\n }\n const result = exit.value as Result.Result<unknown, unknown>;\n if (defined) {\n return Result.isSuccess(result)\n ? Outcome.ok(result.success)\n : Outcome.err(result.failure);\n }\n if (Result.isFailure(result)) throw new CellFailure(result.failure);\n return result.success;\n}\n\n// === run ===\n\n/**\n * Run `machine` on the Effect engine. The returned Effect needs a `Scope` and\n * the services its handlers and runners read, and yields the run handle. The\n * handle's members have the Promise engine's names; the ones that return a\n * Promise there return an Effect here, failing with `Stopped`, `StoreFailed`\n * or a hand-written cell's declared failure, so a host sorts them with\n * `Effect.catchTags`.\n *\n * Closing the scope stops the run and its Subs: every handler still in flight\n * is interrupted (its finalizers run), its Msg is never dispatched, and a\n * dispatch after stop fails with `Stopped`. The handle's `stop()` does the\n * same.\n *\n * A Sub whose Stream fails with an error it did not map to a Msg stops the\n * run the other way round: the engine closes that Scope with the failure.\n *\n * A fenced store is fenced here exactly as on the Promise engine: the check is\n * one of the built-ins both engines run.\n *\n * ```ts\n * const program = Effect.gen(function* () {\n * const handle = yield* run(machine, {\n * interpret: {\n * fetch: (cmd) =>\n * Effect.gen(function* () {\n * const api = yield* Api;\n * return yield* api.get(cmd.id);\n * }),\n * },\n * });\n * const runtime = yield* handle.ready;\n * yield* runtime.dispatch({ type: \"go\", id: \"7\" });\n * return runtime.getState();\n * });\n * Effect.runPromise(program.pipe(Effect.scoped, Effect.provide(ApiLive)));\n * ```\n */\nexport function run<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n // The defaults read no services, so an omitted map adds none to `R`.\n I extends EffectInterpret<M, C> = EffectInterpret<M, C, never>,\n B extends EffectSubscribe<M, U> = EffectSubscribe<M, U, never>,\n E extends { type: string } = never,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: EffectRunOptions<S, M, C, U, Ctx, E, I, B>,\n): Effect.Effect<\n EffectBootingRuntime<S, M, E, CellErrors<C, I>>,\n never,\n Scope.Scope | InterpretServices<I> | SubscribeServices<B>\n> {\n return Effect.gen(function* () {\n const services = yield* Effect.context<\n InterpretServices<I> | SubscribeServices<B>\n >();\n const scope = yield* Scope.Scope;\n const fork = Effect.runForkWith(services) as ForkEffect;\n return yield* Effect.acquireRelease(\n Effect.sync(() =>\n effectHandle<S, M, E, CellErrors<C, I>>(\n start<S, M, C, U, Ctx, E>(\n machine,\n opts as EffectRunOptions<S, M, C, U, Ctx, E, unknown, unknown>,\n Effect.runPromiseExitWith(services) as RunEffect,\n fork,\n // Closing the Scope runs the release below, which stops the run.\n // A Scope that closes before that release is added runs it on add.\n (exit) => fork(Scope.close(scope, exit)),\n ),\n ),\n ),\n (runtime) => runtime.stop(),\n );\n });\n}\n\n/** Run an erased Effect to its Exit, interruptible through `signal`. */\ntype RunEffect = (\n effect: Effect.Effect<unknown, unknown>,\n options: { readonly signal: AbortSignal },\n) => Promise<Exit.Exit<unknown, unknown>>;\n\n/** Fork an erased Effect onto its own fiber. */\ntype ForkEffect = (\n effect: Effect.Effect<unknown, unknown>,\n) => Fiber.Fiber<unknown, unknown>;\n\ntype AnyCell = (cmd: unknown) => Effect.Effect<unknown, unknown>;\ntype AnyRunner = (sub: Sub) => Stream.Stream<unknown, unknown>;\n\nfunction start<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n E extends { type: string },\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: EffectRunOptions<S, M, C, U, Ctx, E, unknown, unknown>,\n runEffect: RunEffect,\n fork: ForkEffect,\n closeScope: (exit: Exit.Exit<never, unknown>) => void,\n): Runtime<S, M, E> & LiveWorkProbe {\n const interpret = ((opts as { interpret?: unknown }).interpret ??\n {}) as Readonly<Record<string, AnyCell | undefined>>;\n const subscribe = ((opts as { subscribe?: unknown }).subscribe ??\n {}) as Readonly<Record<string, AnyRunner | undefined>>;\n const builtins = builtinRunners as unknown as Readonly<\n Record<string, AnyRunner | undefined>\n >;\n const defined = new Set((machine.cmds ?? []).map((def) => def.cmdType));\n\n // Aborted on stop: every handler fiber runs under this signal, so aborting\n // it interrupts all of them at once.\n const interruption = new AbortController();\n\n // Interrupted by stop, a cell settles to nothing. A defect throws: for a\n // defined Cmd the `Cmd.define` edge routes it to the error sink, for a\n // hand-written one it rejects the dispatch. A hand-written cell's failure is\n // marked, so the handle fails the dispatch with it as a typed error.\n async function settle(cell: AnyCell, cmd: unknown): Promise<unknown> {\n const type = (cmd as { type: string }).type;\n const exit = await runEffect(cellResult(cell(cmd)), {\n signal: interruption.signal,\n });\n return settleCellExit(exit, defined.has(type));\n }\n\n function handlerFor(type: string): LoopHandler<M> | undefined {\n const cell = interpret[type];\n if (cell === undefined) return undefined;\n return (cmd) => settle(cell, cmd);\n }\n\n // The Cause the run's Scope closes with when a Sub fails: the Stream's own\n // when a Stream failed, else the runner's throw as a defect.\n let subFailure: Cause.Cause<unknown> | undefined;\n\n // A runner's Msgs reach the loop through its `dispatch`, which queues each\n // one behind the step in progress — never a transition on the stream's own\n // fiber (spike #260). A Stream that fails with anything but an interrupt\n // failed with an error the Sub did not map to a Msg, and the run stops\n // (#309).\n function runnerFor(type: string): LoopRunner<Ctx, M> | undefined {\n const make = subscribe[type] ?? builtins[type];\n if (make === undefined) return undefined;\n return (sub, _ctx, dispatch, fail) => {\n const drain = Stream.runForEach(make(sub), (msg) =>\n Effect.sync(() => dispatch(msg as M)),\n ).pipe(\n Effect.catchCause((cause) =>\n Cause.hasInterruptsOnly(cause)\n ? Effect.void\n : Effect.sync(() => {\n subFailure ??= cause;\n fail(Cause.squash(cause));\n }),\n ),\n );\n const fiber = fork(drain);\n return () => Effect.runPromise(Fiber.interrupt(fiber));\n };\n }\n\n // The sink is handed what was thrown, never the edge's marker around it.\n const sink = opts.onError ?? defaultOnError;\n\n const handle = startLoop<S, M, C, Ctx>({\n init: machine.init,\n reduce: (state, msg) => applyCell<S, M, C>(machine, state, msg),\n subs: subEntriesOf<S>(machine),\n runnerFor,\n handlerFor,\n store: opts.store,\n ctx: (opts.ctx ?? {}) as Ctx,\n onError: (error, context) => sink(unwrapped(error), context),\n disposeTimeoutMs: opts.disposeTimeoutMs ?? 5_000,\n idleCap: 100_000,\n // The save marker wraps outermost, around the fence the built-ins add.\n extensions: [\n saveFailures<S, M, C>(),\n ...builtinExtensions<S, M, C, E>(machine, opts),\n ],\n stopOnSubFailure: (error) =>\n closeScope(Exit.failCause(subFailure ?? Cause.die(error))),\n });\n\n // Stop closes the gate first, so nothing an interrupted handler leaves\n // behind is dispatched, then interrupts every handler still in flight so the\n // drain it waits on can finish.\n const stopLoop = handle.stop;\n handle.stop = () => {\n const stopped = stopLoop();\n interruption.abort();\n return stopped;\n };\n return handle as unknown as Runtime<S, M, E> & LiveWorkProbe;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { createResilientCall } from './chunk-6NG4V5SB.js';
1
+ import { createResilientCall } from './chunk-WTSOW2MD.js';
2
2
 
3
3
  // src/internal/paginate/paginator/index.ts
4
4
  var defaultPaginatorPolicy = {
@@ -166,5 +166,5 @@ function liftWalk(state, [slice, cmds]) {
166
166
  }
167
167
 
168
168
  export { PAGE_KEY, createPaginatedWalk, defaultPaginatorPolicy, drain, initPaginator, isDone, liftWalk, recordPage, resume, start };
169
- //# sourceMappingURL=chunk-NSIAORRY.js.map
170
- //# sourceMappingURL=chunk-NSIAORRY.js.map
169
+ //# sourceMappingURL=chunk-IHUNKHDX.js.map
170
+ //# sourceMappingURL=chunk-IHUNKHDX.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal/paginate/paginator/index.ts","../src/internal/paginate/paginated-walk/index.ts"],"names":["start","drain","resume"],"mappings":"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","file":"chunk-NSIAORRY.js","sourcesContent":["/**\n * internal/paginate/paginator — the cursor/offset/page-token walk loop as pure\n * state + ops.\n *\n * Same shape as `../../../retry-backoff`, `../../resilience/circuit-breaker`,\n * and `../../resilience/rate-limit`: a state type plus pure transition functions,\n * host-agnostic by construction. Nothing here reads the clock, the RNG, or any\n * I/O on its own behalf — the consumer fetches a page out-of-band and folds the\n * *result* back in through `recordPage`. That keeps every function inside TEA's\n * invariant 2 (transitions are pure: same inputs → same outputs) so a consumer\n * can thread the walk through `update` and exercise it with `replay` / the pbt\n * fold without a fake network.\n *\n * Every op returns a NEW state value and never mutates its input (invariant 1 —\n * state is a value, not a place). All fields are `readonly` and plain data, so\n * the whole walk is trivially JSON-serializable into a consumer's `Store<S>`\n * and survives a Durable-Object eviction / reload mid-walk: the cursor lives in\n * the Model, not a closure (the durability + replay non-negotiables every TEA\n * knob preserves).\n *\n * ## The cursor is opaque, and the consumer advances it\n *\n * A paginated API hands back a *next cursor* however it likes: an offset\n * (`?offset=40`), a page token (`?pageToken=ey…`), a \"next\" URL, or `null` at\n * the end. This module is generic in `Cursor` and never inspects it — it only\n * carries it forward. The consumer (who alone knows the API's shape) reads the\n * next cursor off the just-fetched page and passes it to `recordPage`:\n *\n * const next = page.nextPageToken ?? null; // consumer-specific extraction\n * const walk = recordPage(state.walk, page.items.length, next);\n *\n * Passing `next: null` (or a missing `undefined` cursor — the op compares with\n * loose `== null`) means \"the API said there is no next page\" → the walk\n * finishes (`done`). Passing a `Cursor` means \"fetch this one next\" → the walk\n * either keeps `fetching` or `pauses` for backpressure (below). Decoupling the\n * extraction from the op is the same discipline `circuit-breaker` uses for the\n * clock: the impure / domain-specific bit (`nextCursor(page)`) lives at the\n * call site; the op stays a pure fold over the extracted value.\n *\n * ## The phase machine\n *\n * Four phases, each a discriminated-union arm carrying ONLY the data that phase\n * needs so impossible states are unrepresentable (pattern 11):\n *\n * - **idle** — created, not started. No cursor is armed yet; `start` arms the\n * first one. Carries the running `seen` / `pages` counters (both 0 here, but\n * present on every phase so a consumer reads them uniformly).\n *\n * - **fetching** — a page request for `cursor` is outstanding. The walk sits\n * here while the consumer's fetch is in flight; `recordPage` resolves it.\n * Carries the `cursor` being fetched so a post-eviction resume re-issues the\n * SAME request (idempotent retry) rather than skipping a page.\n *\n * - **paused** — backpressure tripped. The API still has more pages (a non-null\n * next cursor), but `seen` has reached `highWaterMark` and the consumer has\n * not drained yet. Carries the `cursor` to resume from. No fetch is\n * outstanding — the loop is deliberately idle until `resume`. This is the\n * valve that stops a fast producer (the API) from outrunning a slow consumer\n * (whatever processes each page) and blowing memory.\n *\n * - **done** — the API returned a null next cursor; the walk is exhausted. A\n * terminal phase: `start` / `recordPage` / `resume` are all no-ops on it\n * (idempotent completion — a late duplicate result can't un-finish a walk).\n *\n * ### Transition table\n *\n * Rows are the current phase; columns are the op. `→` denotes the next phase;\n * `self` means unchanged (a no-op for that (phase × op) pair). `next` is the\n * cursor the consumer extracted from the page; `hwm` is `highWaterMark`.\n *\n * | from \\ op | start | recordPage(count, next) | resume |\n * | ----------- | -------------- | -------------------------------------------------------------------- | ------------------------------ |\n * | idle | → fetching(c0) | self *(¹)* | self |\n * | fetching | self | seen=max(0,seen+count), pages+=1; next==null → done; seen≥hwm → paused(next); else → fetching(next) | self |\n * | paused | self | self *(¹)* | seen<hwm → fetching(cursor) *(²)* |\n * | done | self | self | self |\n *\n * *(¹)* `recordPage` only makes sense while `fetching` (a page was outstanding).\n * On any other phase it is a no-op: a result that arrives when no fetch was in\n * flight (a late duplicate, a double-dispatch) must not advance the cursor or\n * double-count `seen`. The walk is a coordination aid, not a correctness gate —\n * it absorbs the stray rather than corrupting the count.\n *\n * *(²)* `resume` re-arms the paused cursor ONLY once `seen` has dropped below\n * `highWaterMark` (the consumer drained, lowering `seen` via `drain`). Calling\n * `resume` while still over the mark is a no-op — the valve stays shut until\n * there is real headroom, so a consumer that resumes optimistically can't\n * defeat the backpressure. Resuming a walk that isn't `paused` is likewise a\n * no-op.\n *\n * ## Backpressure\n *\n * `seen` is the count of items the API has produced that the consumer has not\n * yet acknowledged as processed. `recordPage` adds each page's item count to it;\n * `drain(n)` subtracts what the consumer finished. When `seen` reaches\n * `highWaterMark`, `recordPage` parks the walk in `paused` instead of issuing\n * the next fetch — the producer stops until `drain` + `resume` re-open the\n * valve. A `highWaterMark` of `0` (or any non-positive value) disables\n * backpressure: the comparison `seen >= hwm` is satisfied vacuously only when\n * `hwm <= 0` would pause immediately, so we treat `hwm <= 0` as \"unbounded\" and\n * never pause. The walk then runs flat-out, `fetching → fetching` until `done`.\n *\n * Internal since #47 — not published on any subpath; reached from inside the\n * package as `internal/paginate/paginator`. Same one-shape-per-package rule as `work-queue`, `retry-backoff`,\n * `circuit-breaker`, and `rate-limit`. It is the L1 escape hatch the\n * `paginated-walk` composition builds its walk loop on.\n */\n\n/**\n * Paginator policy — pure configuration, no mutable state. One policy is shared\n * across the whole walk (often a module-scope const). `PaginatorState` carries\n * the cursor + counters; this carries the knobs they're compared against.\n *\n * Generic in `Cursor` so `firstCursor` matches whatever the API's cursor shape\n * is — an offset `number`, a page-token `string`, a `{ after: string }` record,\n * etc. The module never inspects the cursor; the type just threads through.\n */\nexport interface PaginatorPolicy<Cursor> {\n /**\n * The cursor the walk fetches first, armed by `start`. For an offset API this\n * is typically `0`; for a token API, `null`-or-empty-meaning-\"first page\" is\n * the API's call, so pass whatever sentinel the API treats as page one.\n */\n readonly firstCursor: Cursor;\n /**\n * Backpressure threshold: the number of un-drained items at which the walk\n * pauses instead of fetching the next page. `recordPage` parks in `paused`\n * once `seen >= highWaterMark`; `drain` + `resume` re-open the valve.\n *\n * A non-positive value (`<= 0`) disables backpressure — the walk never pauses\n * and runs `fetching → fetching` until the API exhausts. Use that when the\n * consumer processes each page synchronously inside `recordPage` and there is\n * nothing to back up.\n */\n readonly highWaterMark: number;\n}\n\n/**\n * Sensible defaults: start from a numeric offset of `0`, pause after 1000\n * un-drained items. Tuned for an offset-paginated REST API whose consumer\n * processes pages asynchronously; override per call site (especially\n * `firstCursor`, which is API-specific) rather than mutating this shared const.\n *\n * Typed as `PaginatorPolicy<number>` because the only honest default cursor is\n * the numeric offset `0`; a token-based API supplies its own `firstCursor` and\n * its own `PaginatorPolicy<string>`.\n */\nexport const defaultPaginatorPolicy: PaginatorPolicy<number> = {\n firstCursor: 0,\n highWaterMark: 1000,\n};\n\n/**\n * The walk's phase. A discriminated union on `phase` so each phase carries ONLY\n * its own data and impossible combinations are unrepresentable (pattern 11):\n * there is no `done` state that still holds a pending cursor, no `idle` state\n * mid-fetch. Every field is `readonly` so the whole value is trivially\n * JSON-serializable into a consumer's `Store<S>` and safe to thread through a\n * pure reducer / a Durable-Object reload.\n *\n * Two counters ride on every phase so a consumer reads them uniformly without\n * narrowing first:\n * - `seen` — items produced by the API but not yet drained (backpressure gauge).\n * - `pages` — total pages recorded so far (progress / observability).\n *\n * - `idle` — not started; `start` arms `firstCursor`.\n * - `fetching` — a page request for `cursor` is outstanding.\n * - `paused` — backpressure tripped; `cursor` is the page to resume from.\n * - `done` — API exhausted (null next cursor); terminal.\n */\nexport type PaginatorState<Cursor> =\n | { readonly phase: \"idle\"; readonly seen: number; readonly pages: number }\n | {\n readonly phase: \"fetching\";\n readonly cursor: Cursor;\n readonly seen: number;\n readonly pages: number;\n }\n | {\n readonly phase: \"paused\";\n readonly cursor: Cursor;\n readonly seen: number;\n readonly pages: number;\n }\n | { readonly phase: \"done\"; readonly seen: number; readonly pages: number };\n\n/** The starting state: idle, nothing seen, no pages recorded. */\nexport function initPaginator<Cursor>(): PaginatorState<Cursor> {\n return { phase: \"idle\", seen: 0, pages: 0 };\n}\n\n/**\n * Arm the first fetch. PURE — returns a new state; the input is never mutated.\n *\n * - `idle` → `fetching(firstCursor)`. This is the only phase `start` acts on.\n * - any other phase → unchanged. Re-arming a walk that has already begun (or\n * finished) would skip back to page one and double-count; a stray `start`\n * after launch is absorbed, not honored. Use `initPaginator()` to begin a\n * genuinely fresh walk.\n */\nexport function start<Cursor>(\n state: PaginatorState<Cursor>,\n policy: PaginatorPolicy<Cursor>,\n): PaginatorState<Cursor> {\n if (state.phase !== \"idle\") return state;\n return {\n phase: \"fetching\",\n cursor: policy.firstCursor,\n seen: state.seen,\n pages: state.pages,\n };\n}\n\n/**\n * Record the result of the outstanding fetch and decide what comes next. PURE —\n * returns a new state; the input is never mutated.\n *\n * `count` is how many items the just-fetched page yielded (added to `seen` for\n * backpressure accounting). `next` is the cursor the consumer extracted from the\n * page — a `Cursor` to fetch next, or `null`/`undefined` (a missing optional\n * cursor field) if the API signalled the end.\n *\n * - `fetching` → `seen = max(0, seen + count)`, `pages += 1`, then branch on\n * `next`:\n * - `next == null` → `done` (API exhausted; loose-null so a\n * missing/`undefined` cursor also finishes).\n * - `seen >= highWaterMark` → `paused(next)` (backpressure; `hwm <= 0`\n * disables this branch — see policy docs).\n * - otherwise → `fetching(next)` (keep walking).\n * - any other phase → unchanged. A result with no fetch outstanding (a late\n * duplicate, a double-dispatch) must not advance the cursor or double-count;\n * it is absorbed. See the module header's note ¹.\n *\n * `count` is floored into the gauge: a non-finite `count` (`NaN`/`Infinity`) is\n * treated as `0`, and the additive sum is clamped at `0`. A record can only hold\n * or raise `seen`; only `drain` lowers it. This keeps a stray negative or\n * non-finite count from poisoning the `seen >= highWaterMark` comparison and\n * silently defeating backpressure.\n *\n * The new `seen` total — not the pre-page value — is what the backpressure check\n * reads, so a single page large enough to cross the mark pauses immediately\n * rather than fetching one more first.\n */\nexport function recordPage<Cursor>(\n state: PaginatorState<Cursor>,\n count: number,\n next: Cursor | null | undefined,\n policy: PaginatorPolicy<Cursor>,\n): PaginatorState<Cursor> {\n if (state.phase !== \"fetching\") return state;\n // A page's item count must be a finite, non-negative addition to `seen`. A\n // negative count would drive `seen` down (a record can never lower the gauge —\n // only `drain` does) and a non-finite count (`NaN` / `Infinity`) would poison\n // every later `seen >= highWaterMark` comparison, permanently defeating\n // backpressure (NaN compares false, so the valve never trips). Floor the\n // CONTRIBUTION (not just the sum) at 0: clamp a non-finite count to 0\n // (mirroring retry-backoff's `Number.isNaN` discipline at backoffDelay ~:119)\n // and a negative count to 0. Flooring the contribution — rather than the sum —\n // is what makes a record strictly monotonic: `Math.max(0, seen + count)` only\n // catches a count that drags the *sum* below zero, so a small negative count\n // (e.g. `seen=10, count=-3 → 7`) would still silently lower the gauge and\n // break \"only `drain` lowers `seen`\". Clamping `count` first guarantees the\n // additive step can only hold or raise the gauge.\n const safeCount = Number.isFinite(count) ? Math.max(0, count) : 0;\n const seen = state.seen + safeCount;\n const pages = state.pages + 1;\n // Loose `== null` so a *missing* next cursor (`undefined`, common for an\n // optional API field the consumer extracted as `page.nextPageToken`) finishes\n // the walk exactly like an explicit `null`. Strict `=== null` would route an\n // `undefined` cursor into the fetching/paused branch and carry a cursor-less\n // (malformed) state forward.\n if (next == null) {\n return { phase: \"done\", seen, pages };\n }\n // `hwm <= 0` disables backpressure entirely (unbounded walk); only a positive\n // mark can park the loop. `seen >= hwm` is the half-open edge — reaching the\n // mark exactly pauses, matching the `>=` window edges `rate-limit` /\n // `circuit-breaker` use.\n if (policy.highWaterMark > 0 && seen >= policy.highWaterMark) {\n return { phase: \"paused\", cursor: next, seen, pages };\n }\n return { phase: \"fetching\", cursor: next, seen, pages };\n}\n\n/**\n * Acknowledge that the consumer finished processing `n` items, lowering the\n * backpressure gauge. PURE — returns a new state; the input is never mutated.\n *\n * Subtracts `n` from `seen`, floored at 0 (a consumer that over-acks, or acks a\n * page recorded before a reload, can never drive `seen` negative). `n <= 0` is a\n * no-op on the count. `drain` does NOT change the phase — it only frees\n * headroom; `resume` is the separate, explicit decision to re-issue a fetch once\n * the headroom exists. Splitting \"I drained\" from \"I want to continue\" lets a\n * consumer drain in several steps and resume once.\n *\n * Works on every phase: a consumer may drain while still `fetching` (the gauge\n * just drops, no pause was reached) or while `paused` (freeing room for a later\n * `resume`). On `done` the gauge can still be drained for accurate final\n * accounting, though it no longer gates anything.\n */\nexport function drain<Cursor>(\n state: PaginatorState<Cursor>,\n n: number,\n): PaginatorState<Cursor> {\n if (n <= 0) return state;\n const seen = Math.max(0, state.seen - n);\n if (seen === state.seen) return state;\n return { ...state, seen };\n}\n\n/**\n * Re-open the backpressure valve after a `drain`. PURE — returns a new state;\n * the input is never mutated.\n *\n * - `paused` AND `seen < highWaterMark` → `fetching(cursor)`: the consumer has\n * drained below the mark, so re-issue the parked fetch.\n * - `paused` but `seen >= highWaterMark` → unchanged. There is still no\n * headroom; the valve stays shut until a `drain` lowers `seen`. A consumer\n * that resumes optimistically can't defeat the backpressure. (When `hwm <=\n * 0` backpressure is disabled, so the walk never reaches `paused` and this\n * branch is unreachable — but the guard reads `< hwm` consistently, and a\n * non-positive `hwm` makes `seen < hwm` false for any non-negative `seen`,\n * correctly refusing to resume a state that should never have paused.)\n * - any other phase → unchanged. Resuming a walk that isn't paused (idle,\n * fetching, done) is a no-op.\n *\n * See the module header's note ².\n */\nexport function resume<Cursor>(\n state: PaginatorState<Cursor>,\n policy: PaginatorPolicy<Cursor>,\n): PaginatorState<Cursor> {\n if (state.phase !== \"paused\") return state;\n if (state.seen < policy.highWaterMark) {\n return {\n phase: \"fetching\",\n cursor: state.cursor,\n seen: state.seen,\n pages: state.pages,\n };\n }\n return state;\n}\n\n/**\n * Whether the walk has finished (the API returned a null next cursor). PURE,\n * read-only. A derived predicate so a consumer's reducer can branch\n * (`if (isDone(walk)) return [doneState, [joinCmd]]`) without re-checking the\n * `phase` discriminant by hand at every call site.\n */\nexport function isDone<Cursor>(state: PaginatorState<Cursor>): boolean {\n return state.phase === \"done\";\n}\n","/**\n * internal/paginate/paginated-walk — traverse a paginated API / sitemap end to end\n * WITHOUT a fake clock, without 429s, and resumable across a Durable-Object\n * eviction. The L2 composition that bolts `../resilient-call`'s resilience onto\n * `../paginator`'s cursor walk: each page is fetched as ONE keyed resilient\n * call (retry + backoff + rate-limit gate), and the cursor advances only once a\n * page settles `ok`.\n *\n * ## The recipe (the two siblings it composes)\n *\n * - **`../paginator`** owns the walk loop: the opaque `Cursor`, the `seen` /\n * `pages` counters, and the `idle → fetching → … → done` phase machine.\n * `paginated-walk` never re-implements cursor advancement — it folds each\n * settled page through `recordPage`, letting the paginator decide\n * `done` / `fetching(next)` / `paused`. Backpressure is a TWO-way valve:\n * `recordPage` parks the walk in `paused` once `seen >= highWaterMark`, and\n * the `drain` / `resume` verbs (delegating to the paginator's own\n * `drain` / `resume`) lower the gauge and re-open the parked fetch — without\n * them `highWaterMark` would be a one-way valve that strands a paused walk.\n * - **`../resilient-call`** owns the page fetch: the single `resilient_run`\n * effect per page is gated through that knob's cache → circuit → rate-limit\n * → retry pipeline. A transient page failure backs off and re-issues the\n * SAME cursor (idempotent retry); the walk does not advance until the page\n * genuinely settles.\n *\n * Those are the only two siblings this module calls into. The resilience knobs\n * (`retry` / `circuit` / `rateLimit` / `deadline`) are NOT separate bricks\n * `paginated-walk` composes — they belong to `resilient-call`, which owns the\n * rate-limit gate internally. This module merely passes them through to\n * `createResilientCall` and re-uses two config TYPES at the boundary:\n * `RateLimitConfig` (re-exported by `resilient-call`) and `RetryPolicy` (from\n * `../retry-backoff`). Pass a `rateLimit` and `resilient-call`'s token bucket\n * throttles page fetches (\"don't 429 the upstream\"); omit it and the walk runs\n * as fast as the upstream answers.\n *\n * Because the page-fetch resilience IS a `resilient-call` slice, this knob\n * **inherits that knob's contract verbatim**: the slice is a flat record, every\n * verb returns `readonly [State, Cmd[]]`, time is always an `at` parameter, and\n * the jitter RNG is injected once at `createPaginatedWalk(config, rng)` — never\n * read inside a verb (per the spec, `paginated-walk` *extends* `resilient-call`).\n *\n * ## The two non-negotiables (canon)\n *\n * - **Durable** — the slice is plain data: the paginator phase + counters, and\n * the resilient-call brick states (all JSON-serializable). The cursor lives\n * in the Model, not a closure, so a walk survives eviction mid-traversal and\n * resumes from the exact page it was fetching.\n * - **Replayable** — every transition is a verb returning new state + Cmds.\n * `nextCursor` / `onPage` are pure config functions applied at the verb\n * boundary; the Cmds they yield are plain data. `replay` reconstructs the\n * whole walk exactly.\n *\n * ## Where the clock / RNG live\n *\n * Inside the verbs: nowhere. `at` arrives on `start` / `resume`, on the settled\n * Msg `pageOk` / `pageErr` read (stamped by the engine), and on the timer Msg\n * `onTimer` reads, and is threaded straight into the underlying resilient-call\n * gate (so a rate-limited or retried page measures backoff from that `at`). The\n * jitter RNG is injected at construction. The knob ships no I/O: the page\n * fetch is resilient-call's `Cmd.define`d run Cmd, and its handler is yours.\n *\n * ## The fixed fetch key\n *\n * A walk fetches one page at a time, so the resilient-call layer tracks exactly\n * one logical call under the constant key `PAGE_KEY`. The cursor being fetched\n * is the resilient call's `input`, so a post-eviction resume re-issues the same\n * page request. (`resilient-call` is keyed to support N concurrent calls; a walk\n * deliberately uses a single key — the paginator is the sequencer.)\n *\n * ## Typical wiring\n *\n * const walk = createPaginatedWalk<PageToken, ApiPage>({\n * firstPage: null, // the API's \"page one\" sentinel\n * nextCursor: (page) => page.nextToken ?? null,\n * onPage: (page) => [{ type: \"index_rows\", rows: page.items }],\n * rateLimit: { capacity: 10, refillPerSec: 5 },\n * retry: defaultRetryPolicy,\n * });\n *\n * // in the machine:\n * cmds: [walk.fetch],\n * init: () => [{ walk: walk.init() }, []],\n * update: {\n * start: (s, m) => lift(s, walk.start(s.walk, m.at)),\n * resilient_run_ok: (s, m) => lift(s, walk.pageOk(s.walk, m)),\n * resilient_run_err: (s, m) => lift(s, walk.pageErr(s.walk, m)),\n * deadline_exceeded: (s, m) => lift(s, walk.onTimer(s.walk, m)),\n * drained: (s, m) => lift(s, walk.drain(s.walk, m.n)),\n * resume: (s, m) => lift(s, walk.resume(s.walk, m.at)),\n * },\n * subs: [{ type: \"timer\", deps: (s) => walk.timer(s.walk) }],\n *\n * // and where it runs — the page fetch is the handler you write:\n * run(machine, {\n * interpret: {\n * resilient_run: async (cmd, { ok, err }) => {\n * try { return ok(await api.fetchPage(cmd.input)); }\n * catch (cause) { return err({ _tag: \"port_rejected\", cause }); }\n * },\n * },\n * });\n */\n\nimport type { Cmd } from \"../../../index\";\nimport type { RetryPolicy } from \"../../../retry-backoff\";\nimport type { DeadlineSub } from \"../../resilience/deadline\";\nimport {\n type CircuitConfig,\n createResilientCall,\n type DeadlineConfig,\n type FailMsg,\n type RateLimitConfig,\n type ResilientState,\n type ResilientTimerMsg,\n type RunCmd,\n type SucceedMsg,\n} from \"../../resilience/resilient-call\";\nimport {\n drain as drainWalk,\n initPaginator,\n isDone,\n type PaginatorPolicy,\n type PaginatorState,\n recordPage,\n resume as resumeWalk,\n start as startWalk,\n} from \"../paginator\";\n\n// ===========================================================================\n// Config — the knob. `firstPage` / `nextCursor` / `onPage` describe the walk;\n// the resilience bricks are inherited from resilient-call (every one optional —\n// omit a brick, omit its gate).\n// ===========================================================================\n\n/**\n * The paginated-walk knob. Generic in `Cursor` (the opaque page handle the\n * upstream API hands back — offset, page token, \"next\" URL, …) and `Page` (the\n * shape your fetch port resolves with).\n *\n * `nextCursor` and `onPage` are PURE config functions applied at the verb\n * boundary, never stored in the slice or in a Cmd (invariant 3 — Cmds are\n * plain data, not closures). `nextCursor(page)` extracts the API's next-page\n * handle (or `null` at the end); `onPage(page)` yields the Cmds the consumer\n * wants emitted for each successfully fetched page (index rows, write to R2,\n * dispatch a downstream Msg — whatever; an empty array is fine).\n *\n * The resilience bricks (`retry` / `circuit` / `rateLimit` / `deadline`) are\n * the SAME optional knobs `resilient-call` exposes — paginated-walk extends it.\n * `rateLimit` is the brick that keeps the walk from 429-ing the upstream.\n */\nexport interface PaginatedWalkConfig<Cursor, Page, EmittedCmd extends Cmd> {\n /**\n * The cursor the walk fetches first. For an offset API this is typically `0`;\n * for a token API, whatever sentinel the API treats as \"page one\" (often\n * `null` or `\"\"`). Threaded into `paginator`'s `firstCursor`.\n */\n readonly firstPage: Cursor;\n /**\n * Extract the next cursor from a just-fetched page. Return a `Cursor` to keep\n * walking, or `null` when the API signalled the end (the walk finishes). PURE\n * + consumer-specific (only the consumer knows the API's pagination shape).\n */\n readonly nextCursor: (page: Page) => Cursor | null;\n /**\n * The Cmds to emit for each successfully fetched page — the consumer's work\n * per page (index, persist, fan out, …). Returns plain-data Cmds; `[]` is a\n * valid \"I process the page synchronously elsewhere\" answer. PURE.\n */\n readonly onPage: (page: Page) => readonly EmittedCmd[];\n /**\n * How many items a page contributes to the paginator's backpressure gauge\n * (`seen`). Defaults to `1` per page (count pages, not items) when omitted; a\n * consumer that wants per-item backpressure returns `page.items.length`. PURE.\n */\n readonly pageSize?: (page: Page) => number;\n /**\n * Backpressure threshold handed to the paginator. `seen >= highWaterMark`\n * parks the walk in `paused` instead of fetching the next page. Non-positive\n * (the default `0`) disables backpressure — the walk runs flat out until the\n * API exhausts. See `../paginator`'s `highWaterMark` docs.\n */\n readonly highWaterMark?: number;\n\n // --- resilience bricks, inherited from resilient-call (all optional) ---\n /** Exponential-backoff retry policy for a transient page-fetch failure. */\n readonly retry?: RetryPolicy;\n /** Circuit breaker around the page fetch (one upstream → one breaker). */\n readonly circuit?: CircuitConfig;\n /** Token-bucket rate limit — the \"don't 429 the upstream\" knob. */\n readonly rateLimit?: RateLimitConfig;\n /** Overall wall-clock deadline per individual page fetch. */\n readonly deadline?: DeadlineConfig;\n}\n\n// ===========================================================================\n// Slice — the Model field this knob owns. The paginator walk state + the\n// resilient-call slice for the page fetch. Both are flat plain data.\n// ===========================================================================\n\n/**\n * The slice. `walk` is the cursor/phase/counters from `../paginator`;\n * `resilience` is the `../resilient-call` slice that gates the page fetch.\n * Every field is JSON-serializable, so the whole walk survives a reload.\n *\n * The two move in lockstep: `walk` decides WHICH cursor to fetch next, while\n * `resilience` decides HOW that fetch is attempted (gated, retried, backed\n * off). `pageOk` advances `walk`; `pageErr` defers to `resilience` and leaves\n * `walk` parked on the same cursor until the page truly settles.\n */\nexport interface PaginatedWalkState<Cursor, Page> {\n readonly walk: PaginatorState<Cursor>;\n readonly resilience: ResilientState<Cursor, Page>;\n}\n\n// ===========================================================================\n// Cmds + Msgs the knob speaks.\n// ===========================================================================\n\n/**\n * The page-fetch effect this knob emits: the inherited `resilient_run` Cmd from\n * resilient-call, whose `input` is the `Cursor` to fetch and whose `key` is the\n * fixed `PAGE_KEY`. It is `Cmd.define`d; the handler you write for it fetches\n * one page and returns `ok(page)` or `err({ _tag: \"port_rejected\", … })`.\n */\nexport type FetchPageCmd<Cursor> = RunCmd<Cursor>;\n\n/** The page-settled Msgs the engine mints from that handler's outcome. */\nexport type PageOkMsg<Page> = SucceedMsg<Page>;\nexport type PageErrMsg = FailMsg;\n\n/** The retry / deadline timer Msg — inherited from resilient-call. */\nexport type PaginatedWalkTimerMsg = ResilientTimerMsg;\n\n// ===========================================================================\n// The fixed key. A walk fetches one page at a time → exactly one logical\n// resilient call. The paginator is the sequencer; resilient-call's keying is\n// pinned to this single key.\n// ===========================================================================\n\n/** The single resilient-call key every page fetch runs under. */\nexport const PAGE_KEY = \"page\";\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a paginated-walk knob from `config`. `rng` is injected for the\n * underlying retry jitter — pass a fixed `() => 0` in tests to pin backoff;\n * defaults to `Math.random` (read at the verb boundary inside resilient-call,\n * never in a `paginated-walk` verb). Inherited straight from resilient-call.\n *\n * Returns plain functions: `init()`, the verbs `start` / `pageOk` / `pageErr`\n * / `onTimer`, `deadlines(state)` / `timer(state)`, and the page-fetch Cmd def\n * (`fetch`) to list in the machine's `cmds`. `Cursor` is\n * the page handle, `Page` the fetch result, `EmittedCmd` the consumer's\n * per-page Cmds plus the inherited `FetchPageCmd`.\n */\nexport function createPaginatedWalk<Cursor, Page, EmittedCmd extends Cmd = Cmd>(\n config: PaginatedWalkConfig<Cursor, Page, EmittedCmd>,\n rng: () => number = Math.random,\n) {\n // The page fetch is a resilient call keyed by Cursor input → Page result.\n // We feed it only the resilience bricks; the walk-loop knobs are ours.\n const rc = createResilientCall<Cursor, Page>(\n {\n retry: config.retry,\n circuit: config.circuit,\n rateLimit: config.rateLimit,\n deadline: config.deadline,\n },\n rng,\n );\n\n // The paginator policy: our firstPage is the paginator's firstCursor; the\n // high-water mark gates backpressure (0 / omitted → unbounded).\n const paginatorPolicy: PaginatorPolicy<Cursor> = {\n firstCursor: config.firstPage,\n highWaterMark: config.highWaterMark ?? 0,\n };\n\n // The combined Cmd type the verbs can emit: the inherited page-fetch effect\n // plus whatever `onPage` yields.\n type OutCmd = FetchPageCmd<Cursor> | EmittedCmd;\n\n /** Replace the resilience slice, leaving `walk` untouched. */\n function withSlice(\n s: PaginatedWalkState<Cursor, Page>,\n resilience: ResilientState<Cursor, Page>,\n ): PaginatedWalkState<Cursor, Page> {\n return { ...s, resilience };\n }\n\n /** The starting slice: an idle paginator + a fresh resilient-call slice. */\n function init(): PaginatedWalkState<Cursor, Page> {\n return {\n walk: initPaginator<Cursor>(),\n resilience: rc.init(),\n };\n }\n\n /**\n * Issue a resilient page fetch for `cursor` at `at`. The cursor is the\n * resilient call's `input` (so a retry / resume re-fetches the same page) and\n * `PAGE_KEY` is the fixed key. Returns the advanced resilience slice + the\n * `resilient_run` Cmd (or none, if the gate cached / fast-failed / backed off).\n * PURE — `at` is the only clock. Shared by `start` and `pageOk`'s advance.\n */\n function fetch(\n s: PaginatedWalkState<Cursor, Page>,\n cursor: Cursor,\n at: number,\n ): readonly [\n PaginatedWalkState<Cursor, Page>,\n readonly FetchPageCmd<Cursor>[],\n ] {\n const [resilience, cmds] = rc.attempt(s.resilience, PAGE_KEY, cursor, at);\n return [withSlice(s, resilience), cmds];\n }\n\n /**\n * Issue the page fetch for a freshly-armed paginator transition, else a pure\n * no-op. `walk` is the result of a paginator verb (`startWalk` / `resumeWalk`)\n * applied to `s.walk`; this states the arm-and-fetch contract ONCE for the two\n * verbs that share it (`start`, `resume`).\n *\n * A paginator verb is a pure no-op on any phase it does not act on — it returns\n * the SAME state object. Reference identity (not phase) is the honest \"did the\n * verb arm a fresh fetch?\" check: a walk already `fetching` would also satisfy\n * `phase === \"fetching\"` yet must NOT re-issue a fetch (page-one skip /\n * double-fetch). So we proceed ONLY when the verb produced a fresh state. The\n * `phase === \"fetching\"` narrow that follows is therefore always true here\n * (both verbs only ever produce a `fetching` arm), and also narrows `.cursor`\n * for the type checker. PURE — `at` threads into the fetch gate.\n */\n function armAndFetch(\n s: PaginatedWalkState<Cursor, Page>,\n walk: PaginatorState<Cursor>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n if (walk === s.walk || walk.phase !== \"fetching\") {\n return [s, []];\n }\n return fetch({ ...s, walk }, walk.cursor, at);\n }\n\n // === Verb: start =========================================================\n\n /**\n * Begin the walk: arm the paginator's first cursor and issue the first page\n * fetch (gated through resilience). On any non-`idle` paginator phase this is\n * a no-op — re-starting a walk in flight would skip back to page one and\n * double-count, so a stray `start` after launch is absorbed (the paginator's\n * own `start` guard). PURE — `at` threads into the fetch gate.\n */\n function start(\n s: PaginatedWalkState<Cursor, Page>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // `startWalk` is a pure no-op on any non-`idle` phase (same reference); a\n // fresh `fetching(firstCursor)` arm → issue the first page fetch.\n return armAndFetch(s, startWalk(s.walk, paginatorPolicy), at);\n }\n\n // === Verb: pageOk ========================================================\n\n /**\n * Record a successfully fetched page (the engine-minted `msg.value`) and\n * decide what comes next. PURE — `msg.at` is the cache / next-fetch clock.\n *\n * 1. Settle the resilient call OK (closes the breaker, fills the cache,\n * resets the page-fetch retry counter) — inherited from resilient-call.\n * 2. Fold the page through the paginator: add `pageSize(page)` to `seen`,\n * bump `pages`, and branch on `nextCursor(page)`:\n * - `null` → the API is exhausted → paginator goes `done`.\n * - a cursor → advance, then either fetch it (backpressure room) or\n * park in `paused` (high-water mark reached).\n * 3. Emit the consumer's `onPage(page)` Cmds either way, PLUS the next\n * page-fetch effect when the paginator stays `fetching`.\n *\n * A `pageOk` that arrives when the paginator is not `fetching` (a late\n * duplicate, a double-dispatch) is absorbed by `recordPage` — the cursor does\n * not advance and `seen` is not double-counted; only the resilient settle +\n * `onPage` Cmds happen. The walk is a coordination aid, not a correctness\n * gate.\n */\n function pageOk(\n s: PaginatedWalkState<Cursor, Page>,\n msg: PageOkMsg<Page>,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n const { value: page, at } = msg;\n // 1) Settle the underlying resilient call as a success.\n const { call: resilience } = rc.settle(s.resilience, msg);\n\n // 2) Advance the paginator with the page's contribution + the next cursor.\n const count = config.pageSize ? config.pageSize(page) : 1;\n const next = config.nextCursor(page);\n const walk = recordPage(s.walk, count, next, paginatorPolicy);\n\n const advanced: PaginatedWalkState<Cursor, Page> = { walk, resilience };\n\n // 3) The consumer's per-page work, always emitted on a successful page.\n const pageCmds: readonly EmittedCmd[] = config.onPage(page);\n\n // If the paginator stayed `fetching`, issue the next page fetch (gated).\n if (walk.phase === \"fetching\") {\n const [withFetch, fetchCmds] = fetch(advanced, walk.cursor, at);\n return [withFetch, [...pageCmds, ...fetchCmds]];\n }\n\n // `done` (exhausted) or `paused` (backpressure) → no further fetch now.\n return [advanced, pageCmds];\n }\n\n // === Verb: pageErr =======================================================\n\n /**\n * Record a failed page fetch and back off via resilience. PURE — `at` stamps\n * the breaker trip + the retry-delay base.\n *\n * Defers to the resilient-call `settle` verb: it trips the breaker and either\n * schedules a retry (paginator stays parked on the SAME cursor — no advance)\n * or settles the resilient call `failed` once retries are exhausted. The\n * paginator is deliberately untouched: a failed fetch must NOT advance the\n * cursor or skip a page. When the retry timer later fires, `onTimer` re-issues\n * the same page fetch.\n *\n * GUARDED by the paginator phase, exactly as `pageOk` is guarded by\n * `recordPage`'s `fetching`-only check. A `pageErr` only makes sense while a\n * page fetch is outstanding (`walk.phase === \"fetching\"`). A stray `pageErr`\n * that arrives after the walk has settled — `done` (exhausted), `paused`\n * (backpressure), or `idle` (not started) — has NO outstanding fetch to fail.\n * Without this guard it would still call `rc.settle`, which trips the SHARED\n * circuit breaker (dinging a healthy upstream) and overwrites the settled\n * `PAGE_KEY` resilient slot with `failed` (clobbering the succeeded result a\n * resume would otherwise observe). So a stray `pageErr` is absorbed as a pure\n * no-op — identity preserved — matching `pageOk`'s \"the walk is a coordination\n * aid, not a correctness gate\" stance.\n */\n function pageErr(\n s: PaginatedWalkState<Cursor, Page>,\n msg: PageErrMsg,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // Only a `fetching` paginator has an outstanding page fetch to fail. On any\n // other phase the fail would corrupt shared resilience state for a fetch\n // that is not in flight — absorb it as a pure no-op.\n if (s.walk.phase !== \"fetching\") {\n return [s, []];\n }\n const { call: resilience, cmds } = rc.settle(s.resilience, msg);\n return [withSlice(s, resilience), cmds];\n }\n\n // === Verb: onTimer =======================================================\n\n /**\n * A retry / deadline timer fired. Defers to resilient-call's `onTimer`: a\n * retry timer re-runs the gate for the page-fetch's remembered cursor (re-\n * issuing the SAME page); a deadline timer settles the resilient call failed.\n * The paginator stays parked on its cursor throughout — only `pageOk` advances\n * it. A stale fire is a no-op (inherited). PURE.\n *\n * GUARDED by the paginator phase, for the same reason as `pageErr`. A timer is\n * only meaningful while a page fetch is outstanding (`walk.phase ===\n * \"fetching\"`). A stray timer that fires after the walk has settled (`done` /\n * `paused` / `idle`) would otherwise re-enter `rc.onTimer` — re-issuing a fetch\n * the paginator no longer wants (a deadline timer could even overwrite the\n * settled `PAGE_KEY` slot with a deadline `failed`). On any non-`fetching`\n * phase the timer is absorbed as a pure no-op.\n */\n function onTimer(\n s: PaginatedWalkState<Cursor, Page>,\n msg: PaginatedWalkTimerMsg,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n if (s.walk.phase !== \"fetching\") {\n return [s, []];\n }\n const [resilience, cmds] = rc.onTimer(s.resilience, msg);\n return [withSlice(s, resilience), cmds];\n }\n\n // === Verb: drain =========================================================\n\n /**\n * Acknowledge that the consumer finished processing `n` items, lowering the\n * paginator's backpressure gauge (`seen`). PURE — no clock. Delegates to\n * `paginator.drain`: subtracts `n` (floored at 0), leaves the phase untouched.\n *\n * The OTHER half of the backpressure valve `highWaterMark` opens: `recordPage`\n * parks the walk in `paused` once `seen >= highWaterMark`, and `drain` is how a\n * consumer lowers `seen` back below the mark so `resume` can re-open the fetch.\n * Without it `highWaterMark` would be a one-way valve — a walk could pause but\n * never continue. Emits no Cmds: draining only frees headroom; `resume` is the\n * separate, explicit \"continue now\" decision.\n */\n function drain(\n s: PaginatedWalkState<Cursor, Page>,\n n: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n const walk = drainWalk(s.walk, n);\n if (walk === s.walk) {\n // Pure no-op identity (n <= 0, or seen already at floor).\n return [s, []];\n }\n return [{ ...s, walk }, []];\n }\n\n // === Verb: resume ========================================================\n\n /**\n * Re-open the backpressure valve after a `drain`: if the walk is `paused` and\n * `seen` has dropped below `highWaterMark`, re-arm the parked cursor and issue\n * its page fetch (gated through resilience). PURE — `at` threads into the fetch\n * gate. Delegates the phase decision to `paginator.resume`:\n *\n * - `paused` AND `seen < highWaterMark` → `fetching(cursor)` → emit the fetch.\n * - `paused` but still over the mark → no-op (the valve stays shut until a\n * further `drain` frees real headroom).\n * - any other phase → no-op (nothing to resume).\n *\n * Together with `drain` this completes the valve: `recordPage` shuts it\n * (`paused`), `drain` lowers the gauge, `resume` re-opens it and fires the next\n * page fetch — the same `fetch` helper `start` / `pageOk` use, so a resumed\n * fetch is gated identically (cache / circuit / rate-limit / retry).\n */\n function resume(\n s: PaginatedWalkState<Cursor, Page>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // `resumeWalk` is a pure no-op (same reference) on any phase but `paused`, or\n // when `seen` is still at/over the mark. Only a fresh `fetching` arm means\n // the valve actually re-opened — issue the page fetch only then.\n return armAndFetch(s, resumeWalk(s.walk, paginatorPolicy), at);\n }\n\n // === Derived predicates ==================================================\n\n /**\n * Whether the walk has finished — the API returned a null next cursor and the\n * paginator is `done`. PURE, read-only. Lets a consumer's reducer fire a\n * \"walk complete\" Cmd (`if (walk.isDone(s.walk)) …`) without re-checking the\n * paginator phase by hand.\n */\n function isComplete(s: PaginatedWalkState<Cursor, Page>): boolean {\n return isDone(s.walk);\n }\n\n /**\n * The terminal page-fetch failure that stranded the walk, or `undefined` if no\n * page has terminally failed. PURE, read-only. Errors are data: a page that\n * exhausts its retries (or trips the deadline) settles the `PAGE_KEY` resilient\n * slot to `failed` while the paginator stays parked on its cursor — the walk is\n * stuck and will never advance on its own. Without a way to read that phase the\n * failure is unobservable: the walk simply stops emitting fetches with no\n * signal. `failure(s)` surfaces the settled error (the same plain-data sentinel\n * resilient-call records) so a consumer can fire a \"walk dead\" Cmd, alert, or\n * re-`start` a fresh walk.\n */\n function failure(s: PaginatedWalkState<Cursor, Page>): unknown {\n const call = s.resilience.calls[PAGE_KEY];\n return call?.phase === \"failed\" ? call.error : undefined;\n }\n\n /**\n * Whether the walk is STUCK: a page fetch has terminally `failed` yet the\n * paginator is still parked on a cursor (not `done`), so no further fetch will\n * ever be issued without intervention. PURE, read-only.\n *\n * This is the dead-walk predicate the consumer polls (`if (walk.isStuck(s)) …`)\n * to distinguish a healthy finish (`isComplete`) from a silent death. A walk is\n * stuck precisely when the resilient slot settled `failed` AND the paginator\n * has NOT reached `done` — a terminal failure on the final page that also\n * finished the walk is `isComplete`, not stuck. Pairs with `failure(s)`, which\n * hands back the error that stuck it.\n */\n function isStuck(s: PaginatedWalkState<Cursor, Page>): boolean {\n return s.resilience.calls[PAGE_KEY]?.phase === \"failed\" && !isDone(s.walk);\n }\n\n // === Timers ==============================================================\n\n /**\n * The page fetch's deadlines — exactly resilient-call's: a retry timer while\n * the fetch is `waiting_retry`, and (with the `deadline` brick) a per-fetch\n * deadline timer while it is active.\n */\n function deadlines(\n s: PaginatedWalkState<Cursor, Page>,\n ): readonly DeadlineSub[] {\n return rc.deadlines(s.resilience);\n }\n\n /**\n * The built-in `timer` Sub's deps: resilient-call's `timer` over the fetch's\n * slice. Declare `{ type: \"timer\", deps: (s) => walk.timer(s.walk) }`.\n */\n function timer(s: PaginatedWalkState<Cursor, Page>) {\n return rc.timer(s.resilience);\n }\n\n return {\n /** The page-fetch Cmd def — list it in the machine's `cmds`. */\n fetch: rc.run,\n init,\n start,\n pageOk,\n pageErr,\n onTimer,\n drain,\n resume,\n isComplete,\n failure,\n isStuck,\n deadlines,\n timer,\n };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the slice\n * lives at `state.walk`. A convenience for the common single-slice host;\n * consumers with a differently-named field spread by hand. Pure — a thin record\n * rebuild, no clock / RNG.\n */\nexport function liftWalk<\n S extends { walk: PaginatedWalkState<Cursor, Page> },\n Cursor,\n Page,\n C extends Cmd,\n>(\n state: S,\n [slice, cmds]: readonly [PaginatedWalkState<Cursor, Page>, readonly C[]],\n): readonly [S, readonly C[]] {\n return [{ ...state, walk: slice }, cmds];\n}\n"]}
1
+ {"version":3,"sources":["../src/internal/paginate/paginator/index.ts","../src/internal/paginate/paginated-walk/index.ts"],"names":["start","drain","resume"],"mappings":"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","file":"chunk-IHUNKHDX.js","sourcesContent":["/**\n * internal/paginate/paginator — the cursor/offset/page-token walk loop as pure\n * state + ops.\n *\n * Same shape as `../../../retry-backoff`, `../../resilience/circuit-breaker`,\n * and `../../resilience/rate-limit`: a state type plus pure transition functions,\n * host-agnostic by construction. Nothing here reads the clock, the RNG, or any\n * I/O on its own behalf — the consumer fetches a page out-of-band and folds the\n * *result* back in through `recordPage`. That keeps every function inside TEA's\n * invariant 2 (transitions are pure: same inputs → same outputs) so a consumer\n * can thread the walk through `update` and exercise it with `replay` / the pbt\n * fold without a fake network.\n *\n * Every op returns a NEW state value and never mutates its input (invariant 1 —\n * state is a value, not a place). All fields are `readonly` and plain data, so\n * the whole walk is trivially JSON-serializable into a consumer's `Store<S>`\n * and survives a Durable-Object eviction / reload mid-walk: the cursor lives in\n * the Model, not a closure (the durability + replay non-negotiables every TEA\n * knob preserves).\n *\n * ## The cursor is opaque, and the consumer advances it\n *\n * A paginated API hands back a *next cursor* however it likes: an offset\n * (`?offset=40`), a page token (`?pageToken=ey…`), a \"next\" URL, or `null` at\n * the end. This module is generic in `Cursor` and never inspects it — it only\n * carries it forward. The consumer (who alone knows the API's shape) reads the\n * next cursor off the just-fetched page and passes it to `recordPage`:\n *\n * const next = page.nextPageToken ?? null; // consumer-specific extraction\n * const walk = recordPage(state.walk, page.items.length, next);\n *\n * Passing `next: null` (or a missing `undefined` cursor — the op compares with\n * loose `== null`) means \"the API said there is no next page\" → the walk\n * finishes (`done`). Passing a `Cursor` means \"fetch this one next\" → the walk\n * either keeps `fetching` or `pauses` for backpressure (below). Decoupling the\n * extraction from the op is the same discipline `circuit-breaker` uses for the\n * clock: the impure / domain-specific bit (`nextCursor(page)`) lives at the\n * call site; the op stays a pure fold over the extracted value.\n *\n * ## The phase machine\n *\n * Four phases, each a discriminated-union arm carrying ONLY the data that phase\n * needs so impossible states are unrepresentable (pattern 11):\n *\n * - **idle** — created, not started. No cursor is armed yet; `start` arms the\n * first one. Carries the running `seen` / `pages` counters (both 0 here, but\n * present on every phase so a consumer reads them uniformly).\n *\n * - **fetching** — a page request for `cursor` is outstanding. The walk sits\n * here while the consumer's fetch is in flight; `recordPage` resolves it.\n * Carries the `cursor` being fetched so a post-eviction resume re-issues the\n * SAME request (idempotent retry) rather than skipping a page.\n *\n * - **paused** — backpressure tripped. The API still has more pages (a non-null\n * next cursor), but `seen` has reached `highWaterMark` and the consumer has\n * not drained yet. Carries the `cursor` to resume from. No fetch is\n * outstanding — the loop is deliberately idle until `resume`. This is the\n * valve that stops a fast producer (the API) from outrunning a slow consumer\n * (whatever processes each page) and blowing memory.\n *\n * - **done** — the API returned a null next cursor; the walk is exhausted. A\n * terminal phase: `start` / `recordPage` / `resume` are all no-ops on it\n * (idempotent completion — a late duplicate result can't un-finish a walk).\n *\n * ### Transition table\n *\n * Rows are the current phase; columns are the op. `→` denotes the next phase;\n * `self` means unchanged (a no-op for that (phase × op) pair). `next` is the\n * cursor the consumer extracted from the page; `hwm` is `highWaterMark`.\n *\n * | from \\ op | start | recordPage(count, next) | resume |\n * | ----------- | -------------- | -------------------------------------------------------------------- | ------------------------------ |\n * | idle | → fetching(c0) | self *(¹)* | self |\n * | fetching | self | seen=max(0,seen+count), pages+=1; next==null → done; seen≥hwm → paused(next); else → fetching(next) | self |\n * | paused | self | self *(¹)* | seen<hwm → fetching(cursor) *(²)* |\n * | done | self | self | self |\n *\n * *(¹)* `recordPage` only makes sense while `fetching` (a page was outstanding).\n * On any other phase it is a no-op: a result that arrives when no fetch was in\n * flight (a late duplicate, a double-dispatch) must not advance the cursor or\n * double-count `seen`. The walk is a coordination aid, not a correctness gate —\n * it absorbs the stray rather than corrupting the count.\n *\n * *(²)* `resume` re-arms the paused cursor ONLY once `seen` has dropped below\n * `highWaterMark` (the consumer drained, lowering `seen` via `drain`). Calling\n * `resume` while still over the mark is a no-op — the valve stays shut until\n * there is real headroom, so a consumer that resumes optimistically can't\n * defeat the backpressure. Resuming a walk that isn't `paused` is likewise a\n * no-op.\n *\n * ## Backpressure\n *\n * `seen` is the count of items the API has produced that the consumer has not\n * yet acknowledged as processed. `recordPage` adds each page's item count to it;\n * `drain(n)` subtracts what the consumer finished. When `seen` reaches\n * `highWaterMark`, `recordPage` parks the walk in `paused` instead of issuing\n * the next fetch — the producer stops until `drain` + `resume` re-open the\n * valve. A `highWaterMark` of `0` (or any non-positive value) disables\n * backpressure: the comparison `seen >= hwm` is satisfied vacuously only when\n * `hwm <= 0` would pause immediately, so we treat `hwm <= 0` as \"unbounded\" and\n * never pause. The walk then runs flat-out, `fetching → fetching` until `done`.\n *\n * Internal since #47 — not published on any subpath; reached from inside the\n * package as `internal/paginate/paginator`. Same one-shape-per-package rule as `work-queue`, `retry-backoff`,\n * `circuit-breaker`, and `rate-limit`. It is the L1 escape hatch the\n * `paginated-walk` composition builds its walk loop on.\n */\n\n/**\n * Paginator policy — pure configuration, no mutable state. One policy is shared\n * across the whole walk (often a module-scope const). `PaginatorState` carries\n * the cursor + counters; this carries the knobs they're compared against.\n *\n * Generic in `Cursor` so `firstCursor` matches whatever the API's cursor shape\n * is — an offset `number`, a page-token `string`, a `{ after: string }` record,\n * etc. The module never inspects the cursor; the type just threads through.\n */\nexport interface PaginatorPolicy<Cursor> {\n /**\n * The cursor the walk fetches first, armed by `start`. For an offset API this\n * is typically `0`; for a token API, `null`-or-empty-meaning-\"first page\" is\n * the API's call, so pass whatever sentinel the API treats as page one.\n */\n readonly firstCursor: Cursor;\n /**\n * Backpressure threshold: the number of un-drained items at which the walk\n * pauses instead of fetching the next page. `recordPage` parks in `paused`\n * once `seen >= highWaterMark`; `drain` + `resume` re-open the valve.\n *\n * A non-positive value (`<= 0`) disables backpressure — the walk never pauses\n * and runs `fetching → fetching` until the API exhausts. Use that when the\n * consumer processes each page synchronously inside `recordPage` and there is\n * nothing to back up.\n */\n readonly highWaterMark: number;\n}\n\n/**\n * Sensible defaults: start from a numeric offset of `0`, pause after 1000\n * un-drained items. Tuned for an offset-paginated REST API whose consumer\n * processes pages asynchronously; override per call site (especially\n * `firstCursor`, which is API-specific) rather than mutating this shared const.\n *\n * Typed as `PaginatorPolicy<number>` because the only honest default cursor is\n * the numeric offset `0`; a token-based API supplies its own `firstCursor` and\n * its own `PaginatorPolicy<string>`.\n */\nexport const defaultPaginatorPolicy: PaginatorPolicy<number> = {\n firstCursor: 0,\n highWaterMark: 1000,\n};\n\n/**\n * The walk's phase. A discriminated union on `phase` so each phase carries ONLY\n * its own data and impossible combinations are unrepresentable (pattern 11):\n * there is no `done` state that still holds a pending cursor, no `idle` state\n * mid-fetch. Every field is `readonly` so the whole value is trivially\n * JSON-serializable into a consumer's `Store<S>` and safe to thread through a\n * pure reducer / a Durable-Object reload.\n *\n * Two counters ride on every phase so a consumer reads them uniformly without\n * narrowing first:\n * - `seen` — items produced by the API but not yet drained (backpressure gauge).\n * - `pages` — total pages recorded so far (progress / observability).\n *\n * - `idle` — not started; `start` arms `firstCursor`.\n * - `fetching` — a page request for `cursor` is outstanding.\n * - `paused` — backpressure tripped; `cursor` is the page to resume from.\n * - `done` — API exhausted (null next cursor); terminal.\n */\nexport type PaginatorState<Cursor> =\n | { readonly phase: \"idle\"; readonly seen: number; readonly pages: number }\n | {\n readonly phase: \"fetching\";\n readonly cursor: Cursor;\n readonly seen: number;\n readonly pages: number;\n }\n | {\n readonly phase: \"paused\";\n readonly cursor: Cursor;\n readonly seen: number;\n readonly pages: number;\n }\n | { readonly phase: \"done\"; readonly seen: number; readonly pages: number };\n\n/** The starting state: idle, nothing seen, no pages recorded. */\nexport function initPaginator<Cursor>(): PaginatorState<Cursor> {\n return { phase: \"idle\", seen: 0, pages: 0 };\n}\n\n/**\n * Arm the first fetch. PURE — returns a new state; the input is never mutated.\n *\n * - `idle` → `fetching(firstCursor)`. This is the only phase `start` acts on.\n * - any other phase → unchanged. Re-arming a walk that has already begun (or\n * finished) would skip back to page one and double-count; a stray `start`\n * after launch is absorbed, not honored. Use `initPaginator()` to begin a\n * genuinely fresh walk.\n */\nexport function start<Cursor>(\n state: PaginatorState<Cursor>,\n policy: PaginatorPolicy<Cursor>,\n): PaginatorState<Cursor> {\n if (state.phase !== \"idle\") return state;\n return {\n phase: \"fetching\",\n cursor: policy.firstCursor,\n seen: state.seen,\n pages: state.pages,\n };\n}\n\n/**\n * Record the result of the outstanding fetch and decide what comes next. PURE —\n * returns a new state; the input is never mutated.\n *\n * `count` is how many items the just-fetched page yielded (added to `seen` for\n * backpressure accounting). `next` is the cursor the consumer extracted from the\n * page — a `Cursor` to fetch next, or `null`/`undefined` (a missing optional\n * cursor field) if the API signalled the end.\n *\n * - `fetching` → `seen = max(0, seen + count)`, `pages += 1`, then branch on\n * `next`:\n * - `next == null` → `done` (API exhausted; loose-null so a\n * missing/`undefined` cursor also finishes).\n * - `seen >= highWaterMark` → `paused(next)` (backpressure; `hwm <= 0`\n * disables this branch — see policy docs).\n * - otherwise → `fetching(next)` (keep walking).\n * - any other phase → unchanged. A result with no fetch outstanding (a late\n * duplicate, a double-dispatch) must not advance the cursor or double-count;\n * it is absorbed. See the module header's note ¹.\n *\n * `count` is floored into the gauge: a non-finite `count` (`NaN`/`Infinity`) is\n * treated as `0`, and the additive sum is clamped at `0`. A record can only hold\n * or raise `seen`; only `drain` lowers it. This keeps a stray negative or\n * non-finite count from poisoning the `seen >= highWaterMark` comparison and\n * silently defeating backpressure.\n *\n * The new `seen` total — not the pre-page value — is what the backpressure check\n * reads, so a single page large enough to cross the mark pauses immediately\n * rather than fetching one more first.\n */\nexport function recordPage<Cursor>(\n state: PaginatorState<Cursor>,\n count: number,\n next: Cursor | null | undefined,\n policy: PaginatorPolicy<Cursor>,\n): PaginatorState<Cursor> {\n if (state.phase !== \"fetching\") return state;\n // A page's item count must be a finite, non-negative addition to `seen`. A\n // negative count would drive `seen` down (a record can never lower the gauge —\n // only `drain` does) and a non-finite count (`NaN` / `Infinity`) would poison\n // every later `seen >= highWaterMark` comparison, permanently defeating\n // backpressure (NaN compares false, so the valve never trips). Floor the\n // CONTRIBUTION (not just the sum) at 0: clamp a non-finite count to 0\n // (mirroring retry-backoff's `Number.isNaN` discipline at backoffDelay ~:119)\n // and a negative count to 0. Flooring the contribution — rather than the sum —\n // is what makes a record strictly monotonic: `Math.max(0, seen + count)` only\n // catches a count that drags the *sum* below zero, so a small negative count\n // (e.g. `seen=10, count=-3 → 7`) would still silently lower the gauge and\n // break \"only `drain` lowers `seen`\". Clamping `count` first guarantees the\n // additive step can only hold or raise the gauge.\n const safeCount = Number.isFinite(count) ? Math.max(0, count) : 0;\n const seen = state.seen + safeCount;\n const pages = state.pages + 1;\n // Loose `== null` so a *missing* next cursor (`undefined`, common for an\n // optional API field the consumer extracted as `page.nextPageToken`) finishes\n // the walk exactly like an explicit `null`. Strict `=== null` would route an\n // `undefined` cursor into the fetching/paused branch and carry a cursor-less\n // (malformed) state forward.\n if (next == null) {\n return { phase: \"done\", seen, pages };\n }\n // `hwm <= 0` disables backpressure entirely (unbounded walk); only a positive\n // mark can park the loop. `seen >= hwm` is the half-open edge — reaching the\n // mark exactly pauses, matching the `>=` window edges `rate-limit` /\n // `circuit-breaker` use.\n if (policy.highWaterMark > 0 && seen >= policy.highWaterMark) {\n return { phase: \"paused\", cursor: next, seen, pages };\n }\n return { phase: \"fetching\", cursor: next, seen, pages };\n}\n\n/**\n * Acknowledge that the consumer finished processing `n` items, lowering the\n * backpressure gauge. PURE — returns a new state; the input is never mutated.\n *\n * Subtracts `n` from `seen`, floored at 0 (a consumer that over-acks, or acks a\n * page recorded before a reload, can never drive `seen` negative). `n <= 0` is a\n * no-op on the count. `drain` does NOT change the phase — it only frees\n * headroom; `resume` is the separate, explicit decision to re-issue a fetch once\n * the headroom exists. Splitting \"I drained\" from \"I want to continue\" lets a\n * consumer drain in several steps and resume once.\n *\n * Works on every phase: a consumer may drain while still `fetching` (the gauge\n * just drops, no pause was reached) or while `paused` (freeing room for a later\n * `resume`). On `done` the gauge can still be drained for accurate final\n * accounting, though it no longer gates anything.\n */\nexport function drain<Cursor>(\n state: PaginatorState<Cursor>,\n n: number,\n): PaginatorState<Cursor> {\n if (n <= 0) return state;\n const seen = Math.max(0, state.seen - n);\n if (seen === state.seen) return state;\n return { ...state, seen };\n}\n\n/**\n * Re-open the backpressure valve after a `drain`. PURE — returns a new state;\n * the input is never mutated.\n *\n * - `paused` AND `seen < highWaterMark` → `fetching(cursor)`: the consumer has\n * drained below the mark, so re-issue the parked fetch.\n * - `paused` but `seen >= highWaterMark` → unchanged. There is still no\n * headroom; the valve stays shut until a `drain` lowers `seen`. A consumer\n * that resumes optimistically can't defeat the backpressure. (When `hwm <=\n * 0` backpressure is disabled, so the walk never reaches `paused` and this\n * branch is unreachable — but the guard reads `< hwm` consistently, and a\n * non-positive `hwm` makes `seen < hwm` false for any non-negative `seen`,\n * correctly refusing to resume a state that should never have paused.)\n * - any other phase → unchanged. Resuming a walk that isn't paused (idle,\n * fetching, done) is a no-op.\n *\n * See the module header's note ².\n */\nexport function resume<Cursor>(\n state: PaginatorState<Cursor>,\n policy: PaginatorPolicy<Cursor>,\n): PaginatorState<Cursor> {\n if (state.phase !== \"paused\") return state;\n if (state.seen < policy.highWaterMark) {\n return {\n phase: \"fetching\",\n cursor: state.cursor,\n seen: state.seen,\n pages: state.pages,\n };\n }\n return state;\n}\n\n/**\n * Whether the walk has finished (the API returned a null next cursor). PURE,\n * read-only. A derived predicate so a consumer's reducer can branch\n * (`if (isDone(walk)) return [doneState, [joinCmd]]`) without re-checking the\n * `phase` discriminant by hand at every call site.\n */\nexport function isDone<Cursor>(state: PaginatorState<Cursor>): boolean {\n return state.phase === \"done\";\n}\n","/**\n * internal/paginate/paginated-walk — traverse a paginated API / sitemap end to end\n * WITHOUT a fake clock, without 429s, and resumable across a Durable-Object\n * eviction. The L2 composition that bolts `../resilient-call`'s resilience onto\n * `../paginator`'s cursor walk: each page is fetched as ONE keyed resilient\n * call (retry + backoff + rate-limit gate), and the cursor advances only once a\n * page settles `ok`.\n *\n * ## The recipe (the two siblings it composes)\n *\n * - **`../paginator`** owns the walk loop: the opaque `Cursor`, the `seen` /\n * `pages` counters, and the `idle → fetching → … → done` phase machine.\n * `paginated-walk` never re-implements cursor advancement — it folds each\n * settled page through `recordPage`, letting the paginator decide\n * `done` / `fetching(next)` / `paused`. Backpressure is a TWO-way valve:\n * `recordPage` parks the walk in `paused` once `seen >= highWaterMark`, and\n * the `drain` / `resume` verbs (delegating to the paginator's own\n * `drain` / `resume`) lower the gauge and re-open the parked fetch — without\n * them `highWaterMark` would be a one-way valve that strands a paused walk.\n * - **`../resilient-call`** owns the page fetch: the single `resilient_run`\n * effect per page is gated through that knob's cache → circuit → rate-limit\n * → retry pipeline. A transient page failure backs off and re-issues the\n * SAME cursor (idempotent retry); the walk does not advance until the page\n * genuinely settles.\n *\n * Those are the only two siblings this module calls into. The resilience knobs\n * (`retry` / `circuit` / `rateLimit` / `deadline`) are NOT separate bricks\n * `paginated-walk` composes — they belong to `resilient-call`, which owns the\n * rate-limit gate internally. This module merely passes them through to\n * `createResilientCall` and re-uses two config TYPES at the boundary:\n * `RateLimitConfig` (re-exported by `resilient-call`) and `RetryPolicy` (from\n * `../retry-backoff`). Pass a `rateLimit` and `resilient-call`'s token bucket\n * throttles page fetches (\"don't 429 the upstream\"); omit it and the walk runs\n * as fast as the upstream answers.\n *\n * Because the page-fetch resilience IS a `resilient-call` slice, this knob\n * **inherits that knob's contract verbatim**: the slice is a flat record, every\n * verb returns `readonly [State, Cmd[]]`, time is always an `at` parameter, and\n * the jitter RNG is injected once at `createPaginatedWalk(config, rng)` — never\n * read inside a verb (per the spec, `paginated-walk` *extends* `resilient-call`).\n *\n * ## The two non-negotiables (canon)\n *\n * - **Durable** — the slice is plain data: the paginator phase + counters, and\n * the resilient-call brick states (all JSON-serializable). The cursor lives\n * in the Model, not a closure, so a walk survives eviction mid-traversal and\n * resumes from the exact page it was fetching.\n * - **Replayable** — every transition is a verb returning new state + Cmds.\n * `nextCursor` / `onPage` are pure config functions applied at the verb\n * boundary; the Cmds they yield are plain data. `replay` reconstructs the\n * whole walk exactly.\n *\n * ## Where the clock / RNG live\n *\n * Inside the verbs: nowhere. `at` arrives on `start` / `resume`, on the settled\n * Msg `pageOk` / `pageErr` read (stamped by the engine), and on the timer Msg\n * `onTimer` reads, and is threaded straight into the underlying resilient-call\n * gate (so a rate-limited or retried page measures backoff from that `at`). The\n * jitter RNG is injected at construction. The knob ships no I/O: the page\n * fetch is resilient-call's `Cmd.define`d run Cmd, and its handler is yours.\n *\n * ## The fixed fetch key\n *\n * A walk fetches one page at a time, so the resilient-call layer tracks exactly\n * one logical call under the constant key `PAGE_KEY`. The cursor being fetched\n * is the resilient call's `input`, so a post-eviction resume re-issues the same\n * page request. (`resilient-call` is keyed to support N concurrent calls; a walk\n * deliberately uses a single key — the paginator is the sequencer.)\n *\n * ## Typical wiring\n *\n * const walk = createPaginatedWalk<PageToken, ApiPage>({\n * firstPage: null, // the API's \"page one\" sentinel\n * nextCursor: (page) => page.nextToken ?? null,\n * onPage: (page) => [{ type: \"index_rows\", rows: page.items }],\n * rateLimit: { capacity: 10, refillPerSec: 5 },\n * retry: defaultRetryPolicy,\n * });\n *\n * // in the machine:\n * cmds: [walk.fetch],\n * init: () => [{ walk: walk.init() }, []],\n * update: {\n * start: (s, m) => lift(s, walk.start(s.walk, m.at)),\n * resilient_run_ok: (s, m) => lift(s, walk.pageOk(s.walk, m)),\n * resilient_run_err: (s, m) => lift(s, walk.pageErr(s.walk, m)),\n * deadline_exceeded: (s, m) => lift(s, walk.onTimer(s.walk, m)),\n * drained: (s, m) => lift(s, walk.drain(s.walk, m.n)),\n * resume: (s, m) => lift(s, walk.resume(s.walk, m.at)),\n * },\n * subs: [{ type: \"timer\", deps: (s) => walk.timer(s.walk) }],\n *\n * // and where it runs — the page fetch is the handler you write:\n * run(machine, {\n * interpret: {\n * resilient_run: async (cmd, { ok, err }) => {\n * try { return ok(await api.fetchPage(cmd.input)); }\n * catch (cause) { return err({ _tag: \"port_rejected\", cause }); }\n * },\n * },\n * });\n */\n\nimport type { Cmd } from \"../../../index\";\nimport type { RetryPolicy } from \"../../../retry-backoff\";\nimport type { DeadlineSub } from \"../../resilience/deadline\";\nimport {\n type CircuitConfig,\n createResilientCall,\n type DeadlineConfig,\n type FailMsg,\n type RateLimitConfig,\n type ResilientState,\n type ResilientTimerMsg,\n type RunCmd,\n type SucceedMsg,\n} from \"../../resilience/resilient-call\";\nimport {\n drain as drainWalk,\n initPaginator,\n isDone,\n type PaginatorPolicy,\n type PaginatorState,\n recordPage,\n resume as resumeWalk,\n start as startWalk,\n} from \"../paginator\";\n\n// ===========================================================================\n// Config — the knob. `firstPage` / `nextCursor` / `onPage` describe the walk;\n// the resilience bricks are inherited from resilient-call (every one optional —\n// omit a brick, omit its gate).\n// ===========================================================================\n\n/**\n * The paginated-walk knob. Generic in `Cursor` (the opaque page handle the\n * upstream API hands back — offset, page token, \"next\" URL, …) and `Page` (the\n * shape your fetch port resolves with).\n *\n * `nextCursor` and `onPage` are PURE config functions applied at the verb\n * boundary, never stored in the slice or in a Cmd (invariant 3 — Cmds are\n * plain data, not closures). `nextCursor(page)` extracts the API's next-page\n * handle (or `null` at the end); `onPage(page)` yields the Cmds the consumer\n * wants emitted for each successfully fetched page (index rows, write to R2,\n * dispatch a downstream Msg — whatever; an empty array is fine).\n *\n * The resilience bricks (`retry` / `circuit` / `rateLimit` / `deadline`) are\n * the SAME optional knobs `resilient-call` exposes — paginated-walk extends it.\n * `rateLimit` is the brick that keeps the walk from 429-ing the upstream.\n */\nexport interface PaginatedWalkConfig<Cursor, Page, EmittedCmd extends Cmd> {\n /**\n * The cursor the walk fetches first. For an offset API this is typically `0`;\n * for a token API, whatever sentinel the API treats as \"page one\" (often\n * `null` or `\"\"`). Threaded into `paginator`'s `firstCursor`.\n */\n readonly firstPage: Cursor;\n /**\n * Extract the next cursor from a just-fetched page. Return a `Cursor` to keep\n * walking, or `null` when the API signalled the end (the walk finishes). PURE\n * + consumer-specific (only the consumer knows the API's pagination shape).\n */\n readonly nextCursor: (page: Page) => Cursor | null;\n /**\n * The Cmds to emit for each successfully fetched page — the consumer's work\n * per page (index, persist, fan out, …). Returns plain-data Cmds; `[]` is a\n * valid \"I process the page synchronously elsewhere\" answer. PURE.\n */\n readonly onPage: (page: Page) => readonly EmittedCmd[];\n /**\n * How many items a page contributes to the paginator's backpressure gauge\n * (`seen`). Defaults to `1` per page (count pages, not items) when omitted; a\n * consumer that wants per-item backpressure returns `page.items.length`. PURE.\n */\n readonly pageSize?: (page: Page) => number;\n /**\n * Backpressure threshold handed to the paginator. `seen >= highWaterMark`\n * parks the walk in `paused` instead of fetching the next page. Non-positive\n * (the default `0`) disables backpressure — the walk runs flat out until the\n * API exhausts. See `../paginator`'s `highWaterMark` docs.\n */\n readonly highWaterMark?: number;\n\n // --- resilience bricks, inherited from resilient-call (all optional) ---\n /** Exponential-backoff retry policy for a transient page-fetch failure. */\n readonly retry?: RetryPolicy;\n /** Circuit breaker around the page fetch (one upstream → one breaker). */\n readonly circuit?: CircuitConfig;\n /** Token-bucket rate limit — the \"don't 429 the upstream\" knob. */\n readonly rateLimit?: RateLimitConfig;\n /** Overall wall-clock deadline per individual page fetch. */\n readonly deadline?: DeadlineConfig;\n}\n\n// ===========================================================================\n// Slice — the Model field this knob owns. The paginator walk state + the\n// resilient-call slice for the page fetch. Both are flat plain data.\n// ===========================================================================\n\n/**\n * The slice. `walk` is the cursor/phase/counters from `../paginator`;\n * `resilience` is the `../resilient-call` slice that gates the page fetch.\n * Every field is JSON-serializable, so the whole walk survives a reload.\n *\n * The two move in lockstep: `walk` decides WHICH cursor to fetch next, while\n * `resilience` decides HOW that fetch is attempted (gated, retried, backed\n * off). `pageOk` advances `walk`; `pageErr` defers to `resilience` and leaves\n * `walk` parked on the same cursor until the page truly settles.\n */\nexport interface PaginatedWalkState<Cursor, Page> {\n readonly walk: PaginatorState<Cursor>;\n readonly resilience: ResilientState<Cursor, Page>;\n}\n\n// ===========================================================================\n// Cmds + Msgs the knob speaks.\n// ===========================================================================\n\n/**\n * The page-fetch effect this knob emits: the inherited `resilient_run` Cmd from\n * resilient-call, whose `input` is the `Cursor` to fetch and whose `key` is the\n * fixed `PAGE_KEY`. It is `Cmd.define`d; the handler you write for it fetches\n * one page and returns `ok(page)` or `err({ _tag: \"port_rejected\", … })`.\n */\nexport type FetchPageCmd<Cursor> = RunCmd<Cursor>;\n\n/** The page-settled Msgs the engine mints from that handler's outcome. */\nexport type PageOkMsg<Page> = SucceedMsg<Page>;\nexport type PageErrMsg = FailMsg;\n\n/** The retry / deadline timer Msg — inherited from resilient-call. */\nexport type PaginatedWalkTimerMsg = ResilientTimerMsg;\n\n// ===========================================================================\n// The fixed key. A walk fetches one page at a time → exactly one logical\n// resilient call. The paginator is the sequencer; resilient-call's keying is\n// pinned to this single key.\n// ===========================================================================\n\n/** The single resilient-call key every page fetch runs under. */\nexport const PAGE_KEY = \"page\";\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a paginated-walk knob from `config`. `rng` is injected for the\n * underlying retry jitter — pass a fixed `() => 0` in tests to pin backoff;\n * defaults to `Math.random` (read at the verb boundary inside resilient-call,\n * never in a `paginated-walk` verb). Inherited straight from resilient-call.\n *\n * Returns plain functions: `init()`, the verbs `start` / `pageOk` / `pageErr`\n * / `onTimer`, `deadlines(state)` / `timer(state)`, and the page-fetch Cmd def\n * (`fetch`) to list in the machine's `cmds`. `Cursor` is\n * the page handle, `Page` the fetch result, `EmittedCmd` the consumer's\n * per-page Cmds plus the inherited `FetchPageCmd`.\n */\nexport function createPaginatedWalk<Cursor, Page, EmittedCmd extends Cmd = Cmd>(\n config: PaginatedWalkConfig<Cursor, Page, EmittedCmd>,\n rng: () => number = Math.random,\n) {\n // The page fetch is a resilient call keyed by Cursor input → Page result.\n // We feed it only the resilience bricks; the walk-loop knobs are ours.\n const rc = createResilientCall<Cursor, Page>(\n {\n retry: config.retry,\n circuit: config.circuit,\n rateLimit: config.rateLimit,\n deadline: config.deadline,\n },\n rng,\n );\n\n // The paginator policy: our firstPage is the paginator's firstCursor; the\n // high-water mark gates backpressure (0 / omitted → unbounded).\n const paginatorPolicy: PaginatorPolicy<Cursor> = {\n firstCursor: config.firstPage,\n highWaterMark: config.highWaterMark ?? 0,\n };\n\n // The combined Cmd type the verbs can emit: the inherited page-fetch effect\n // plus whatever `onPage` yields.\n type OutCmd = FetchPageCmd<Cursor> | EmittedCmd;\n\n /** Replace the resilience slice, leaving `walk` untouched. */\n function withSlice(\n s: PaginatedWalkState<Cursor, Page>,\n resilience: ResilientState<Cursor, Page>,\n ): PaginatedWalkState<Cursor, Page> {\n return { ...s, resilience };\n }\n\n /** The starting slice: an idle paginator + a fresh resilient-call slice. */\n function init(): PaginatedWalkState<Cursor, Page> {\n return {\n walk: initPaginator<Cursor>(),\n resilience: rc.init(),\n };\n }\n\n /**\n * Issue a resilient page fetch for `cursor` at `at`. The cursor is the\n * resilient call's `input` (so a retry / resume re-fetches the same page) and\n * `PAGE_KEY` is the fixed key. Returns the advanced resilience slice + the\n * `resilient_run` Cmd (or none, if the gate cached / fast-failed / backed off).\n * PURE — `at` is the only clock. Shared by `start` and `pageOk`'s advance.\n */\n function fetch(\n s: PaginatedWalkState<Cursor, Page>,\n cursor: Cursor,\n at: number,\n ): readonly [\n PaginatedWalkState<Cursor, Page>,\n readonly FetchPageCmd<Cursor>[],\n ] {\n const [resilience, cmds] = rc.attempt(s.resilience, PAGE_KEY, cursor, at);\n return [withSlice(s, resilience), cmds];\n }\n\n /**\n * Issue the page fetch for a freshly-armed paginator transition, else a pure\n * no-op. `walk` is the result of a paginator verb (`startWalk` / `resumeWalk`)\n * applied to `s.walk`; this states the arm-and-fetch contract ONCE for the two\n * verbs that share it (`start`, `resume`).\n *\n * A paginator verb is a pure no-op on any phase it does not act on — it returns\n * the SAME state object. Reference identity (not phase) is the honest \"did the\n * verb arm a fresh fetch?\" check: a walk already `fetching` would also satisfy\n * `phase === \"fetching\"` yet must NOT re-issue a fetch (page-one skip /\n * double-fetch). So we proceed ONLY when the verb produced a fresh state. The\n * `phase === \"fetching\"` narrow that follows is therefore always true here\n * (both verbs only ever produce a `fetching` arm), and also narrows `.cursor`\n * for the type checker. PURE — `at` threads into the fetch gate.\n */\n function armAndFetch(\n s: PaginatedWalkState<Cursor, Page>,\n walk: PaginatorState<Cursor>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n if (walk === s.walk || walk.phase !== \"fetching\") {\n return [s, []];\n }\n return fetch({ ...s, walk }, walk.cursor, at);\n }\n\n // === Verb: start =========================================================\n\n /**\n * Begin the walk: arm the paginator's first cursor and issue the first page\n * fetch (gated through resilience). On any non-`idle` paginator phase this is\n * a no-op — re-starting a walk in flight would skip back to page one and\n * double-count, so a stray `start` after launch is absorbed (the paginator's\n * own `start` guard). PURE — `at` threads into the fetch gate.\n */\n function start(\n s: PaginatedWalkState<Cursor, Page>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // `startWalk` is a pure no-op on any non-`idle` phase (same reference); a\n // fresh `fetching(firstCursor)` arm → issue the first page fetch.\n return armAndFetch(s, startWalk(s.walk, paginatorPolicy), at);\n }\n\n // === Verb: pageOk ========================================================\n\n /**\n * Record a successfully fetched page (the engine-minted `msg.value`) and\n * decide what comes next. PURE — `msg.at` is the cache / next-fetch clock.\n *\n * 1. Settle the resilient call OK (closes the breaker, fills the cache,\n * resets the page-fetch retry counter) — inherited from resilient-call.\n * 2. Fold the page through the paginator: add `pageSize(page)` to `seen`,\n * bump `pages`, and branch on `nextCursor(page)`:\n * - `null` → the API is exhausted → paginator goes `done`.\n * - a cursor → advance, then either fetch it (backpressure room) or\n * park in `paused` (high-water mark reached).\n * 3. Emit the consumer's `onPage(page)` Cmds either way, PLUS the next\n * page-fetch effect when the paginator stays `fetching`.\n *\n * A `pageOk` that arrives when the paginator is not `fetching` (a late\n * duplicate, a double-dispatch) is absorbed by `recordPage` — the cursor does\n * not advance and `seen` is not double-counted; only the resilient settle +\n * `onPage` Cmds happen. The walk is a coordination aid, not a correctness\n * gate.\n */\n function pageOk(\n s: PaginatedWalkState<Cursor, Page>,\n msg: PageOkMsg<Page>,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n const { value: page, at } = msg;\n // 1) Settle the underlying resilient call as a success.\n const { call: resilience } = rc.settle(s.resilience, msg);\n\n // 2) Advance the paginator with the page's contribution + the next cursor.\n const count = config.pageSize ? config.pageSize(page) : 1;\n const next = config.nextCursor(page);\n const walk = recordPage(s.walk, count, next, paginatorPolicy);\n\n const advanced: PaginatedWalkState<Cursor, Page> = { walk, resilience };\n\n // 3) The consumer's per-page work, always emitted on a successful page.\n const pageCmds: readonly EmittedCmd[] = config.onPage(page);\n\n // If the paginator stayed `fetching`, issue the next page fetch (gated).\n if (walk.phase === \"fetching\") {\n const [withFetch, fetchCmds] = fetch(advanced, walk.cursor, at);\n return [withFetch, [...pageCmds, ...fetchCmds]];\n }\n\n // `done` (exhausted) or `paused` (backpressure) → no further fetch now.\n return [advanced, pageCmds];\n }\n\n // === Verb: pageErr =======================================================\n\n /**\n * Record a failed page fetch and back off via resilience. PURE — `at` stamps\n * the breaker trip + the retry-delay base.\n *\n * Defers to the resilient-call `settle` verb: it trips the breaker and either\n * schedules a retry (paginator stays parked on the SAME cursor — no advance)\n * or settles the resilient call `failed` once retries are exhausted. The\n * paginator is deliberately untouched: a failed fetch must NOT advance the\n * cursor or skip a page. When the retry timer later fires, `onTimer` re-issues\n * the same page fetch.\n *\n * GUARDED by the paginator phase, exactly as `pageOk` is guarded by\n * `recordPage`'s `fetching`-only check. A `pageErr` only makes sense while a\n * page fetch is outstanding (`walk.phase === \"fetching\"`). A stray `pageErr`\n * that arrives after the walk has settled — `done` (exhausted), `paused`\n * (backpressure), or `idle` (not started) — has NO outstanding fetch to fail.\n * Without this guard it would still call `rc.settle`, which trips the SHARED\n * circuit breaker (dinging a healthy upstream) and overwrites the settled\n * `PAGE_KEY` resilient slot with `failed` (clobbering the succeeded result a\n * resume would otherwise observe). So a stray `pageErr` is absorbed as a pure\n * no-op — identity preserved — matching `pageOk`'s \"the walk is a coordination\n * aid, not a correctness gate\" stance.\n */\n function pageErr(\n s: PaginatedWalkState<Cursor, Page>,\n msg: PageErrMsg,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // Only a `fetching` paginator has an outstanding page fetch to fail. On any\n // other phase the fail would corrupt shared resilience state for a fetch\n // that is not in flight — absorb it as a pure no-op.\n if (s.walk.phase !== \"fetching\") {\n return [s, []];\n }\n const { call: resilience, cmds } = rc.settle(s.resilience, msg);\n return [withSlice(s, resilience), cmds];\n }\n\n // === Verb: onTimer =======================================================\n\n /**\n * A retry / deadline timer fired. Defers to resilient-call's `onTimer`: a\n * retry timer re-runs the gate for the page-fetch's remembered cursor (re-\n * issuing the SAME page); a deadline timer settles the resilient call failed.\n * The paginator stays parked on its cursor throughout — only `pageOk` advances\n * it. A stale fire is a no-op (inherited). PURE.\n *\n * GUARDED by the paginator phase, for the same reason as `pageErr`. A timer is\n * only meaningful while a page fetch is outstanding (`walk.phase ===\n * \"fetching\"`). A stray timer that fires after the walk has settled (`done` /\n * `paused` / `idle`) would otherwise re-enter `rc.onTimer` — re-issuing a fetch\n * the paginator no longer wants (a deadline timer could even overwrite the\n * settled `PAGE_KEY` slot with a deadline `failed`). On any non-`fetching`\n * phase the timer is absorbed as a pure no-op.\n */\n function onTimer(\n s: PaginatedWalkState<Cursor, Page>,\n msg: PaginatedWalkTimerMsg,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n if (s.walk.phase !== \"fetching\") {\n return [s, []];\n }\n const [resilience, cmds] = rc.onTimer(s.resilience, msg);\n return [withSlice(s, resilience), cmds];\n }\n\n // === Verb: drain =========================================================\n\n /**\n * Acknowledge that the consumer finished processing `n` items, lowering the\n * paginator's backpressure gauge (`seen`). PURE — no clock. Delegates to\n * `paginator.drain`: subtracts `n` (floored at 0), leaves the phase untouched.\n *\n * The OTHER half of the backpressure valve `highWaterMark` opens: `recordPage`\n * parks the walk in `paused` once `seen >= highWaterMark`, and `drain` is how a\n * consumer lowers `seen` back below the mark so `resume` can re-open the fetch.\n * Without it `highWaterMark` would be a one-way valve — a walk could pause but\n * never continue. Emits no Cmds: draining only frees headroom; `resume` is the\n * separate, explicit \"continue now\" decision.\n */\n function drain(\n s: PaginatedWalkState<Cursor, Page>,\n n: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n const walk = drainWalk(s.walk, n);\n if (walk === s.walk) {\n // Pure no-op identity (n <= 0, or seen already at floor).\n return [s, []];\n }\n return [{ ...s, walk }, []];\n }\n\n // === Verb: resume ========================================================\n\n /**\n * Re-open the backpressure valve after a `drain`: if the walk is `paused` and\n * `seen` has dropped below `highWaterMark`, re-arm the parked cursor and issue\n * its page fetch (gated through resilience). PURE — `at` threads into the fetch\n * gate. Delegates the phase decision to `paginator.resume`:\n *\n * - `paused` AND `seen < highWaterMark` → `fetching(cursor)` → emit the fetch.\n * - `paused` but still over the mark → no-op (the valve stays shut until a\n * further `drain` frees real headroom).\n * - any other phase → no-op (nothing to resume).\n *\n * Together with `drain` this completes the valve: `recordPage` shuts it\n * (`paused`), `drain` lowers the gauge, `resume` re-opens it and fires the next\n * page fetch — the same `fetch` helper `start` / `pageOk` use, so a resumed\n * fetch is gated identically (cache / circuit / rate-limit / retry).\n */\n function resume(\n s: PaginatedWalkState<Cursor, Page>,\n at: number,\n ): readonly [PaginatedWalkState<Cursor, Page>, readonly OutCmd[]] {\n // `resumeWalk` is a pure no-op (same reference) on any phase but `paused`, or\n // when `seen` is still at/over the mark. Only a fresh `fetching` arm means\n // the valve actually re-opened — issue the page fetch only then.\n return armAndFetch(s, resumeWalk(s.walk, paginatorPolicy), at);\n }\n\n // === Derived predicates ==================================================\n\n /**\n * Whether the walk has finished — the API returned a null next cursor and the\n * paginator is `done`. PURE, read-only. Lets a consumer's reducer fire a\n * \"walk complete\" Cmd (`if (walk.isDone(s.walk)) …`) without re-checking the\n * paginator phase by hand.\n */\n function isComplete(s: PaginatedWalkState<Cursor, Page>): boolean {\n return isDone(s.walk);\n }\n\n /**\n * The terminal page-fetch failure that stranded the walk, or `undefined` if no\n * page has terminally failed. PURE, read-only. Errors are data: a page that\n * exhausts its retries (or trips the deadline) settles the `PAGE_KEY` resilient\n * slot to `failed` while the paginator stays parked on its cursor — the walk is\n * stuck and will never advance on its own. Without a way to read that phase the\n * failure is unobservable: the walk simply stops emitting fetches with no\n * signal. `failure(s)` surfaces the settled error (the same plain-data sentinel\n * resilient-call records) so a consumer can fire a \"walk dead\" Cmd, alert, or\n * re-`start` a fresh walk.\n */\n function failure(s: PaginatedWalkState<Cursor, Page>): unknown {\n const call = s.resilience.calls[PAGE_KEY];\n return call?.phase === \"failed\" ? call.error : undefined;\n }\n\n /**\n * Whether the walk is STUCK: a page fetch has terminally `failed` yet the\n * paginator is still parked on a cursor (not `done`), so no further fetch will\n * ever be issued without intervention. PURE, read-only.\n *\n * This is the dead-walk predicate the consumer polls (`if (walk.isStuck(s)) …`)\n * to distinguish a healthy finish (`isComplete`) from a silent death. A walk is\n * stuck precisely when the resilient slot settled `failed` AND the paginator\n * has NOT reached `done` — a terminal failure on the final page that also\n * finished the walk is `isComplete`, not stuck. Pairs with `failure(s)`, which\n * hands back the error that stuck it.\n */\n function isStuck(s: PaginatedWalkState<Cursor, Page>): boolean {\n return s.resilience.calls[PAGE_KEY]?.phase === \"failed\" && !isDone(s.walk);\n }\n\n // === Timers ==============================================================\n\n /**\n * The page fetch's deadlines — exactly resilient-call's: a retry timer while\n * the fetch is `waiting_retry`, and (with the `deadline` brick) a per-fetch\n * deadline timer while it is active.\n */\n function deadlines(\n s: PaginatedWalkState<Cursor, Page>,\n ): readonly DeadlineSub[] {\n return rc.deadlines(s.resilience);\n }\n\n /**\n * The built-in `timer` Sub's deps: resilient-call's `timer` over the fetch's\n * slice. Declare `{ type: \"timer\", deps: (s) => walk.timer(s.walk) }`.\n */\n function timer(s: PaginatedWalkState<Cursor, Page>) {\n return rc.timer(s.resilience);\n }\n\n return {\n /** The page-fetch Cmd def — list it in the machine's `cmds`. */\n fetch: rc.run,\n init,\n start,\n pageOk,\n pageErr,\n onTimer,\n drain,\n resume,\n isComplete,\n failure,\n isStuck,\n deadlines,\n timer,\n };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the slice\n * lives at `state.walk`. A convenience for the common single-slice host;\n * consumers with a differently-named field spread by hand. Pure — a thin record\n * rebuild, no clock / RNG.\n */\nexport function liftWalk<\n S extends { walk: PaginatedWalkState<Cursor, Page> },\n Cursor,\n Page,\n C extends Cmd,\n>(\n state: S,\n [slice, cmds]: readonly [PaginatedWalkState<Cursor, Page>, readonly C[]],\n): readonly [S, readonly C[]] {\n return [{ ...state, walk: slice }, cmds];\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import { StoreRefusedError, describeError, Refusal } from './chunk-AIIXV4XB.js';
2
+
3
+ // src/internal/engine/restore.ts
4
+ async function restore(store) {
5
+ let migrated;
6
+ try {
7
+ migrated = store.migrate(await store.load());
8
+ } catch (cause) {
9
+ if (cause instanceof StoreRefusedError) throw cause;
10
+ throw new StoreRefusedError(
11
+ `the saved state could not be read (${describeError(cause)})`,
12
+ { cause }
13
+ );
14
+ }
15
+ if (migrated instanceof Refusal) throw new StoreRefusedError(migrated.reason);
16
+ return migrated;
17
+ }
18
+
19
+ export { restore };
20
+ //# sourceMappingURL=chunk-JVE2LBFJ.js.map
21
+ //# sourceMappingURL=chunk-JVE2LBFJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/engine/restore.ts"],"names":[],"mappings":";;;AAeA,eAAsB,QAAW,KAAA,EAAoC;AACnE,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,KAAA,CAAM,OAAA,CAAQ,MAAM,KAAA,CAAM,MAAM,CAAA;AAAA,EAC7C,SAAS,KAAA,EAAO;AACd,IAAA,IAAI,KAAA,YAAiB,mBAAmB,MAAM,KAAA;AAC9C,IAAA,MAAM,IAAI,iBAAA;AAAA,MACR,CAAA,mCAAA,EAAsC,aAAA,CAAc,KAAK,CAAC,CAAA,CAAA,CAAA;AAAA,MAC1D,EAAE,KAAA;AAAM,KACV;AAAA,EACF;AACA,EAAA,IAAI,oBAAoB,OAAA,EAAS,MAAM,IAAI,iBAAA,CAAkB,SAAS,MAAM,CAAA;AAC5E,EAAA,OAAO,QAAA;AACT","file":"chunk-JVE2LBFJ.js","sourcesContent":["import { describeError } from \"../../describe-error\";\nimport {\n type Migrated,\n Refusal,\n type Store,\n StoreRefusedError,\n} from \"../../runtime-types\";\n\n/**\n * Read a store's saved state through its `migrate`: the `S` it holds, or `null`\n * when nothing was saved. Throws {@link StoreRefusedError} when `migrate`\n * refuses, and when `load` or `migrate` throws (#316) — the caller then writes\n * nothing, so bytes it could not read are never overwritten. Boot and the work\n * queue both read a store through this.\n */\nexport async function restore<S>(store: Store<S>): Promise<S | null> {\n let migrated: Migrated<S>;\n try {\n migrated = store.migrate(await store.load());\n } catch (cause) {\n if (cause instanceof StoreRefusedError) throw cause;\n throw new StoreRefusedError(\n `the saved state could not be read (${describeError(cause)})`,\n { cause },\n );\n }\n if (migrated instanceof Refusal) throw new StoreRefusedError(migrated.reason);\n return migrated;\n}\n"]}