iterate 0.2.6 → 0.3.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 (218) hide show
  1. package/README.md +86 -76
  2. package/THIRD_PARTY_NOTICES.md +55 -0
  3. package/bin/iterate.js +18 -3
  4. package/dist/api-url-B6404M82.mjs +17 -0
  5. package/dist/api-url-B6404M82.mjs.map +1 -0
  6. package/dist/app-ref-BipL0feU.mjs +35 -0
  7. package/dist/app-ref-BipL0feU.mjs.map +1 -0
  8. package/dist/app-ref-C1CrgXqX.mjs +7 -0
  9. package/dist/app-ref-C1CrgXqX.mjs.map +1 -0
  10. package/dist/app-ref-DYai_om1.mjs +7 -0
  11. package/dist/app-ref-DYai_om1.mjs.map +1 -0
  12. package/dist/cli-D0c-pDL_.mjs +1010 -0
  13. package/dist/cli-D0c-pDL_.mjs.map +1 -0
  14. package/dist/client.d.ts +3 -0
  15. package/dist/client.mjs +4 -0
  16. package/dist/cloudflare-BTm90gQ4.mjs +951 -0
  17. package/dist/cloudflare-BTm90gQ4.mjs.map +1 -0
  18. package/dist/contract-s4FW4eES.mjs +309 -0
  19. package/dist/contract-s4FW4eES.mjs.map +1 -0
  20. package/dist/document-review/index.d.ts +5 -0
  21. package/dist/document-review/types.d.ts +107 -0
  22. package/dist/document-review.mjs +7015 -0
  23. package/dist/document-review.mjs.map +1 -0
  24. package/dist/durable-object-processor-durability-CNsTjAJS.mjs +205 -0
  25. package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +1 -0
  26. package/dist/idempotency-DleloJNt.mjs +28 -0
  27. package/dist/idempotency-DleloJNt.mjs.map +1 -0
  28. package/dist/index.mjs +1 -1
  29. package/dist/itx/api-url.d.ts +6 -0
  30. package/dist/itx/itx-node-client.d.ts +65 -0
  31. package/dist/itx/itx-session.d.ts +215 -0
  32. package/dist/itx/owned-rpc-session.d.ts +14 -0
  33. package/dist/itx/query-client.d.ts +10 -0
  34. package/dist/itx-api.generated.d.ts +6195 -0
  35. package/dist/itx-session-sjud8GiT.mjs +534 -0
  36. package/dist/itx-session-sjud8GiT.mjs.map +1 -0
  37. package/dist/live-state-BJNqOwFw.mjs +299 -0
  38. package/dist/live-state-BJNqOwFw.mjs.map +1 -0
  39. package/dist/next/api.d.ts +479 -0
  40. package/dist/next/api.mjs +0 -0
  41. package/dist/next/app-server.d.ts +44 -0
  42. package/dist/next/app-server.mjs +479 -0
  43. package/dist/next/app-server.mjs.map +1 -0
  44. package/dist/next/app-session.d.ts +49 -0
  45. package/dist/next/app-session.mjs +238 -0
  46. package/dist/next/app-session.mjs.map +1 -0
  47. package/dist/next/app.d.ts +29 -0
  48. package/dist/next/app.mjs +141 -0
  49. package/dist/next/app.mjs.map +1 -0
  50. package/dist/next/client/live-state.d.ts +63 -0
  51. package/dist/next/client/oauth.d.ts +12 -0
  52. package/dist/next/client/react.d.ts +109 -0
  53. package/dist/next/client/socket.d.ts +6 -0
  54. package/dist/next/client.mjs +156 -0
  55. package/dist/next/client.mjs.map +1 -0
  56. package/dist/next/expression.d.ts +146 -0
  57. package/dist/next/expression.mjs +399 -0
  58. package/dist/next/expression.mjs.map +1 -0
  59. package/dist/next/lib.d.ts +56 -0
  60. package/dist/next/lib.mjs +199 -0
  61. package/dist/next/lib.mjs.map +1 -0
  62. package/dist/next/oauth-scopes.d.ts +32 -0
  63. package/dist/next/oauth-scopes.mjs +40 -0
  64. package/dist/next/oauth-scopes.mjs.map +1 -0
  65. package/dist/next/oauth.mjs +29 -0
  66. package/dist/next/oauth.mjs.map +1 -0
  67. package/dist/next/principal.d.ts +64 -0
  68. package/dist/next/principal.mjs +98 -0
  69. package/dist/next/principal.mjs.map +1 -0
  70. package/dist/next/project-ingress.d.ts +37 -0
  71. package/dist/next/project-ingress.mjs +75 -0
  72. package/dist/next/project-ingress.mjs.map +1 -0
  73. package/dist/next/react.mjs +285 -0
  74. package/dist/next/react.mjs.map +1 -0
  75. package/dist/next/sdk/auth.d.ts +5 -0
  76. package/dist/next/sdk/index.d.ts +112 -0
  77. package/dist/next/sdk.mjs +139 -0
  78. package/dist/next/sdk.mjs.map +1 -0
  79. package/dist/next/stream/processor.d.ts +378 -0
  80. package/dist/next/stream/processor.mjs +582 -0
  81. package/dist/next/stream/processor.mjs.map +1 -0
  82. package/dist/next/stream/run.d.ts +58 -0
  83. package/dist/next/stream/run.mjs +40 -0
  84. package/dist/next/stream/run.mjs.map +1 -0
  85. package/dist/next-node.d.ts +15 -0
  86. package/dist/next-node.mjs +51 -0
  87. package/dist/next-node.mjs.map +1 -0
  88. package/dist/node.d.ts +3 -0
  89. package/dist/node.mjs +185 -0
  90. package/dist/node.mjs.map +1 -0
  91. package/dist/processor-host-capabilities-BMFH3KTM.mjs +56 -0
  92. package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +1 -0
  93. package/dist/processors/cloudflare.d.ts +3 -0
  94. package/dist/processors/durable-object-processor-durability.d.ts +79 -0
  95. package/dist/processors/event-consumption-metrics.d.ts +82 -0
  96. package/dist/processors/idempotency.d.ts +13 -0
  97. package/dist/processors/index.d.ts +12 -0
  98. package/dist/processors/processor-contracts.d.ts +342 -0
  99. package/dist/processors/processor-facet.d.ts +186 -0
  100. package/dist/processors/processor-host-capabilities.d.ts +60 -0
  101. package/dist/processors/prompt-sections.d.ts +17 -0
  102. package/dist/processors/rpc-types.d.ts +515 -0
  103. package/dist/processors/schemas.d.ts +102 -0
  104. package/dist/processors/stream-handle.d.ts +45 -0
  105. package/dist/processors/stream-processor-keepalive.d.ts +95 -0
  106. package/dist/processors/stream-processor-registry.d.ts +233 -0
  107. package/dist/processors/stream-processor-runner.d.ts +289 -0
  108. package/dist/processors/stream-processor.d.ts +339 -0
  109. package/dist/processors/stream-runtime-metrics.d.ts +107 -0
  110. package/dist/processors/testing.d.ts +302 -0
  111. package/dist/processors-BoNyeBfQ.mjs +10 -0
  112. package/dist/processors-BoNyeBfQ.mjs.map +1 -0
  113. package/dist/processors-cloudflare.mjs +3 -0
  114. package/dist/processors-testing.mjs +435 -0
  115. package/dist/processors-testing.mjs.map +1 -0
  116. package/dist/processors.mjs +52 -0
  117. package/dist/processors.mjs.map +1 -0
  118. package/dist/protocol-DnK_f2m6.mjs +251 -0
  119. package/dist/protocol-DnK_f2m6.mjs.map +1 -0
  120. package/dist/sdk/capnweb/index.d.ts +2 -0
  121. package/dist/sdk/capnweb/live-state/compact.d.ts +5 -0
  122. package/dist/sdk/capnweb/live-state/diff.d.ts +41 -0
  123. package/dist/sdk/capnweb/live-state/engine.d.ts +44 -0
  124. package/dist/sdk/capnweb/live-state/index.d.ts +41 -0
  125. package/dist/sdk/capnweb/live-state/protocol.d.ts +87 -0
  126. package/dist/sdk/capnweb/live-state/retain.d.ts +23 -0
  127. package/dist/sdk/capnweb/live-state/store.d.ts +20 -0
  128. package/dist/sdk/capnweb/live-state/types.d.ts +11 -0
  129. package/dist/sdk/capnweb/react.d.ts +45 -0
  130. package/dist/sdk/capnweb/react.mjs +316 -0
  131. package/dist/sdk/capnweb/react.mjs.map +1 -0
  132. package/dist/sdk/capnweb.mjs +4 -0
  133. package/dist/sdk/itx/react.d.ts +191 -0
  134. package/dist/sdk/itx/react.mjs +383 -0
  135. package/dist/sdk/itx/react.mjs.map +1 -0
  136. package/dist/sdk-DMB-IM11.mjs +933 -0
  137. package/dist/sdk-DMB-IM11.mjs.map +1 -0
  138. package/dist/sdk.d.ts +339 -0
  139. package/dist/sdk.mjs +2 -0
  140. package/dist/serve-itx.d.ts +46 -0
  141. package/dist/starter-apps/flake-dashboard/app-ref.d.ts +31 -0
  142. package/dist/starter-apps/flake-dashboard/configured-worker.mjs +1055 -0
  143. package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +1 -0
  144. package/dist/starter-apps/flake-dashboard/contract.d.ts +4839 -0
  145. package/dist/starter-apps/flake-dashboard/contract.mjs +2 -0
  146. package/dist/starter-apps/flake-dashboard/index.d.ts +17 -0
  147. package/dist/starter-apps/flake-dashboard/index.mjs +56 -0
  148. package/dist/starter-apps/flake-dashboard/index.mjs.map +1 -0
  149. package/dist/starter-apps/flake-dashboard/worker.d.ts +4607 -0
  150. package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +8914 -0
  151. package/dist/starter-apps/github-ai-linter/configured-worker.mjs +17987 -0
  152. package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +1 -0
  153. package/dist/starter-apps/github-ai-linter/contract.d.ts +9193 -0
  154. package/dist/starter-apps/github-ai-linter/index.d.ts +10 -0
  155. package/dist/starter-apps/github-ai-linter/index.mjs +36 -0
  156. package/dist/starter-apps/github-ai-linter/index.mjs.map +1 -0
  157. package/dist/starter-apps/github-ai-linter/prompt.d.ts +13 -0
  158. package/dist/starter-apps/github-ai-linter/review-bot.d.ts +808 -0
  159. package/dist/starter-apps/github-ai-linter/rules.d.ts +34 -0
  160. package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +19 -0
  161. package/dist/starter-apps/github-ai-linter/worker.d.ts +19 -0
  162. package/dist/starter-apps/github-ai-linter/worker.mjs +947 -0
  163. package/dist/starter-apps/github-ai-linter/worker.mjs.map +1 -0
  164. package/dist/starter-apps/guestbook/app-ref.d.ts +27 -0
  165. package/dist/starter-apps/guestbook/client.d.ts +7 -0
  166. package/dist/starter-apps/guestbook/client.mjs +59 -0
  167. package/dist/starter-apps/guestbook/configured-worker.mjs +205 -0
  168. package/dist/starter-apps/guestbook/configured-worker.mjs.map +1 -0
  169. package/dist/starter-apps/guestbook/index.d.ts +9 -0
  170. package/dist/starter-apps/guestbook/index.mjs +31 -0
  171. package/dist/starter-apps/guestbook/index.mjs.map +1 -0
  172. package/dist/starter-apps/guestbook/processor.d.ts +2267 -0
  173. package/dist/starter-apps/guestbook/worker.d.ts +26 -0
  174. package/dist/starter-apps/guestbook/worker.mjs +191 -0
  175. package/dist/starter-apps/guestbook/worker.mjs.map +1 -0
  176. package/dist/starter-apps/media/configured-worker.mjs +577 -0
  177. package/dist/starter-apps/media/configured-worker.mjs.map +1 -0
  178. package/dist/starter-apps/media/index.mjs +36 -0
  179. package/dist/starter-apps/media/index.mjs.map +1 -0
  180. package/dist/starter-apps/media/ref.mjs +20 -0
  181. package/dist/starter-apps/media/ref.mjs.map +1 -0
  182. package/dist/starter-apps/media/worker.mjs +579 -0
  183. package/dist/starter-apps/media/worker.mjs.map +1 -0
  184. package/dist/starter-apps/notes/configured-worker.mjs +6134 -0
  185. package/dist/starter-apps/notes/configured-worker.mjs.map +1 -0
  186. package/dist/starter-apps/notes/index.mjs +23 -0
  187. package/dist/starter-apps/notes/index.mjs.map +1 -0
  188. package/dist/starter-apps/notes/ref.mjs +21 -0
  189. package/dist/starter-apps/notes/ref.mjs.map +1 -0
  190. package/dist/starter-apps/notes/worker.mjs +427 -0
  191. package/dist/starter-apps/notes/worker.mjs.map +1 -0
  192. package/dist/starter-apps/todo/client.mjs +59 -0
  193. package/dist/starter-apps/todo/configured-worker.mjs +2864 -0
  194. package/dist/starter-apps/todo/configured-worker.mjs.map +1 -0
  195. package/dist/starter-apps/todo/index.d.ts +8 -0
  196. package/dist/starter-apps/todo/index.mjs +29 -0
  197. package/dist/starter-apps/todo/index.mjs.map +1 -0
  198. package/dist/stream-processor-keepalive-DAQTP6m3.mjs +2082 -0
  199. package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +1 -0
  200. package/dist/usingCtx-inzbY1Qz.mjs +57 -0
  201. package/dist/usingCtx-mZx5nsAW.mjs +11800 -0
  202. package/dist/usingCtx-mZx5nsAW.mjs.map +1 -0
  203. package/dist/worker-ref-DZxPDmb_.mjs +390 -0
  204. package/dist/worker-ref-DZxPDmb_.mjs.map +1 -0
  205. package/menubar/Iterate.entitlements +12 -0
  206. package/menubar/Iterate.swift +914 -0
  207. package/menubar/IterateIcon.swift +145 -0
  208. package/menubar/README.md +28 -0
  209. package/menubar/build-menubar-app.sh +59 -0
  210. package/package.json +235 -18
  211. package/dist/cli-DMS4kJph.mjs +0 -868
  212. package/dist/cli-DMS4kJph.mjs.map +0 -1
  213. package/dist/config-DtnR7Lv7.mjs +0 -170
  214. package/dist/config-DtnR7Lv7.mjs.map +0 -1
  215. package/dist/index.d.mts.map +0 -1
  216. package/dist/stream-tui/agent-chat-terminal.d.mts +0 -1
  217. package/dist/stream-tui/agent-chat-terminal.mjs +0 -933
  218. package/dist/stream-tui/agent-chat-terminal.mjs.map +0 -1
@@ -0,0 +1,95 @@
1
+ import type { StreamEventInput } from "./schemas.ts";
2
+ /**
3
+ * The durable mark, stored in DO KV BELOW the journal/fold: the crash-loop
4
+ * breaker must live beneath the state reduction it protects (a failing fold
5
+ * cannot be asked to fold its own pause fact). KV is authoritative here;
6
+ * journal facts about revivals are evidence, not enforcement — the deliberate
7
+ * inversion of the usual rule.
8
+ */
9
+ export type KeepaliveRecord = {
10
+ /** Consecutive revival attempts without a quiet-clean confirmation. */
11
+ revivals: number;
12
+ /** Epoch ms of the most recent revival attempt (drives the backoff). */
13
+ lastRevivalAt: number;
14
+ /** Worker version at the last write; a different live version resets the budget. */
15
+ version: string;
16
+ /**
17
+ * The keepalive's own armed alarm time, or null when disarmed. Persisted so
18
+ * a fresh incarnation can tell "this fire is mine" from "another subsystem's
19
+ * slice of the shared DO alarm is due" (e.g. the scheduler's) — in-memory
20
+ * state does not survive the eviction that makes revival necessary.
21
+ */
22
+ armedAtMs: number | null;
23
+ };
24
+ type ProcessorKeepaliveHooks = {
25
+ /** Injected clock (epoch ms). */
26
+ now(): number;
27
+ /** Read the durable record. Synchronous DO KV in production. */
28
+ readRecord(): KeepaliveRecord | undefined;
29
+ /** Write the durable record. */
30
+ writeRecord(record: KeepaliveRecord): void;
31
+ /** Repoint (or clear) the keepalive's slice of the DO alarm. */
32
+ armAlarm(atMs: number | null): void;
33
+ /** Keep the DO alive while tracked work runs (ctx.waitUntil). */
34
+ keepAlive(work: Promise<unknown>): void;
35
+ /**
36
+ * The revival pass: append the journaled revival fact, then pull every
37
+ * hosted processor through its pending events so end-of-batch
38
+ * reconciliations run. Must throw on failure — the breaker owns the retry.
39
+ */
40
+ revive(record: KeepaliveRecord): Promise<void>;
41
+ /**
42
+ * Classify and dispose a revival that can never become valid on retry.
43
+ * Return true only after synchronously removing this attempt's durable
44
+ * desire (or proving a newer desire replaced it). The keepalive then stops
45
+ * without arming another retry.
46
+ */
47
+ discardFailedRevival?(error: unknown, record: KeepaliveRecord): boolean;
48
+ /** Best-effort journal evidence (crash-loop warnings). Must not throw. */
49
+ appendFact(event: StreamEventInput): void;
50
+ /** Current worker deploy version (antidote-deploy budget reset). */
51
+ version: string;
52
+ };
53
+ /** How far ahead of in-flight work the alarm is scheduled. Bounds post-eviction
54
+ * revival latency; a deploy mid-agent-turn recovers within roughly this. */
55
+ export declare const KEEPALIVE_ALARM_LEAD_MS = 10000;
56
+ export declare const REVIVAL_BACKOFF_PLATEAU_MS: number;
57
+ /**
58
+ * Consecutive busy fires with NO settlement in between before the window is
59
+ * treated as wedged (a hung promise nothing will ever settle — e.g. a socket
60
+ * with no deadline). 90 fires ≈ 15 minutes at the lead, comfortably past the
61
+ * longest legitimate tracked work (the providers' 10-minute deadlines), so
62
+ * legit work never trips it while a wedge decays into the revival backoff
63
+ * instead of re-arming every lead interval forever.
64
+ */
65
+ export declare const MAX_CONSECUTIVE_BUSY_REFIRES = 90;
66
+ /** The semantic outcome of one platform alarm reaching the keepalive. */
67
+ type ProcessorKeepaliveAlarmAction = "not_due" | "busy_rearmed" | "revival_hung_backoff" | "clean_disarmed" | "revived" | "revival_discarded" | "revival_failed";
68
+ export declare function revivalBackoffMs(revivals: number): number;
69
+ export declare class ProcessorKeepalive {
70
+ #private;
71
+ constructor(hooks: ProcessorKeepaliveHooks);
72
+ get armedAtMs(): number | null;
73
+ /**
74
+ * Register one unit of in-flight work. Every registered work closure —
75
+ * blocking and background alike — rides through here, so "the DO died owing
76
+ * work" is exactly "the DO died with the alarm armed".
77
+ */
78
+ track(work: Promise<unknown>): void;
79
+ /**
80
+ * The DO alarm handler body. The shared alarm may fire for another
81
+ * subsystem's slice (the scheduler's), so this self-gates on the persisted
82
+ * armed time and does nothing when the fire is not the keepalive's.
83
+ */
84
+ onAlarm(): Promise<ProcessorKeepaliveAlarmAction>;
85
+ /**
86
+ * The operator's no-deploy antidote: clear the crash-loop budget and, when
87
+ * a retry is owed (the record is armed), pull it in to the confirmation
88
+ * lead so the next fire revives promptly on the fresh budget. Without this
89
+ * the mark resets only on a quiet-clean confirmation or a version change —
90
+ * a 3-strikes plateau otherwise mutes a wedged processor for six hours at
91
+ * a time with a deploy as the only cure (the 2026-08-11 prod incident).
92
+ */
93
+ resetBackoff(): void;
94
+ }
95
+ export {};
@@ -0,0 +1,233 @@
1
+ import { LiveState } from "../sdk/capnweb/live-state/engine.ts";
2
+ import type { ProcessorStream } from "./stream-handle.ts";
3
+ import type { StreamEvent } from "./schemas.ts";
4
+ import type { StreamProcessorWakeRequest, StreamProcessorWakeResponse } from "./rpc-types.ts";
5
+ import type { ProcessorState } from "./processor-contracts.ts";
6
+ import type { ProcessorReads } from "./stream-processor.ts";
7
+ import { type AnyHostedProcessor } from "./processor-host-capabilities.ts";
8
+ /**
9
+ * What `register` accepts: a real {@link StreamProcessor} subclass instance
10
+ * that also carries the hosted-capability surface — contract description,
11
+ * runtime state, event-consumption metrics — the wake call shares with the
12
+ * browser host. The bound is STRUCTURAL ({@link AnyHostedProcessor})
13
+ * because the class itself cannot appear here: it is
14
+ * invariant in its contract parameter (private state storage holds `State` in
15
+ * both positions), so no single instantiation is a supertype of all
16
+ * processors. The "must be a real StreamProcessor" half is enforced at
17
+ * construction instead — the runner's `StreamProcessor.runnerHooks` reaches
18
+ * the class's own private hooks and throws on a structural impostor.
19
+ */
20
+ export type RegisterableProcessor = AnyHostedProcessor;
21
+ /**
22
+ * The folded-state type of a registered processor, derived from its
23
+ * contract's `stateSchema` — the class's contract parameter is invariant and
24
+ * cannot be named through {@link RegisterableProcessor}'s structural bound,
25
+ * but every concrete subclass's `contract` property already carries the
26
+ * schema whose output IS the state type.
27
+ */
28
+ export type RegisteredProcessorState<P extends RegisterableProcessor> = ProcessorState<P["contract"]>;
29
+ /**
30
+ * What {@link StreamProcessorRegistry.reads} returns: the RPC-facing
31
+ * {@link ProcessorReads} plus the two synchronous reads a DO's `getLiveState`
32
+ * closure needs (live-state assembly runs synchronously; see `refreshLive`),
33
+ * with `waitUntilEvent` widened to the runner's full waiter — the offset
34
+ * barrier {@link ProcessorReads} publishes over RPC PLUS the in-process-only
35
+ * predicate form (a function cannot cross the RPC facade; a DO wires it into
36
+ * processor deps that wait for a specific future event, e.g. the capability
37
+ * host's script-completion wait).
38
+ */
39
+ export type RegisteredProcessorReads<State> = Omit<ProcessorReads<State>, "waitUntilEvent"> & {
40
+ waitUntilEvent(input: {
41
+ offset: number;
42
+ timeoutMs?: number;
43
+ signal?: AbortSignal;
44
+ } | {
45
+ predicate: (event: StreamEvent) => boolean;
46
+ timeoutMs?: number;
47
+ signal?: AbortSignal;
48
+ }): Promise<void>;
49
+ /** The runner's committed fold, synchronously (schema default until loaded). */
50
+ readonly currentState: State;
51
+ /** Committed processing cursor, read atomically with currentState in live assembly. */
52
+ readonly currentAcknowledgedThroughOffset: number;
53
+ /** Source lifetime paired atomically with currentState and its cursor. */
54
+ readonly currentStreamId: string | undefined;
55
+ /** Whether `currentState` is a real fold — gate live publishing on it. */
56
+ readonly isLoaded: boolean;
57
+ /**
58
+ * Fold through the durable stream tail or throw. The registry and this
59
+ * processor-specific door have the same strict contract.
60
+ */
61
+ catchUp(): Promise<void>;
62
+ };
63
+ /**
64
+ * Options for {@link StreamProcessorRegistry.register}. A processor registers
65
+ * under its contract slug by default — which IS the subscription name under the
66
+ * identity doctrine (docs/stream-subscription-model-redesign.md): the same
67
+ * string as the stream's catalog key, the facet name under facet placement,
68
+ * and the progress-key component. Pass an explicit `name` to register a second
69
+ * instance of one contract under a distinct name; reads, wake routing, and
70
+ * progress storage all already key by that name (see `reads`, `resolveProcessorName`,
71
+ * and `durableObjectProgressStore`), so the instances stay independent.
72
+ */
73
+ export type RegisterProcessorOptions<State = unknown> = {
74
+ /** Clear this processor's related projections synchronously with source-lifetime replacement. */
75
+ resetForStream?: () => void;
76
+ /** Post-eviction keepalive recovery — REQUIRED for consequential
77
+ * `runInBackground` work (see the module doc). */
78
+ recovery?: boolean;
79
+ /** Registered/progress name for this instance. Defaults to the contract slug
80
+ * (name === slug, the identity-doctrine default). Supply a distinct name to
81
+ * host two instances of one contract on one registry without colliding. */
82
+ name?: string;
83
+ /** Persist a bounded reduction cache; a cold runner refolds when it is omitted. */
84
+ reductionCache?: {
85
+ shouldCacheReduction(state: State): boolean;
86
+ initialState(): State;
87
+ };
88
+ };
89
+ export type StreamProcessorRegistry<Live extends object = Record<string, unknown>> = {
90
+ readonly stream: ProcessorStream;
91
+ /** The node's live-state engine; a `.liveState` RpcTarget exposes getState()/subscribe() over it. */
92
+ readonly live: LiveState<Live>;
93
+ /**
94
+ * Reassemble the live state from current inputs — the ONE writer for the
95
+ * engine. Call it after mutating any non-runner live-state input (the
96
+ * streams index, the demo counter); a runner's own committed-state change
97
+ * calls it automatically via `observeStateChanges`. On a cold DO (a
98
+ * runner's progress not yet loaded) it does nothing instead of publishing
99
+ * the schema default over real facts; `loadAndRefreshLive` is the explicit
100
+ * asynchronous loading door.
101
+ */
102
+ refreshLive(): void;
103
+ /**
104
+ * `refreshLive`'s cold-start sibling: LOAD every runner's progress, THEN
105
+ * reassemble — so the first read or connection reflects committed writes
106
+ * even on a cold DO. (Distinct names because the difference — one awaits
107
+ * storage, one must not — is exactly what a call site gets wrong.)
108
+ */
109
+ loadAndRefreshLive(): Promise<void>;
110
+ /** Registered processor names, in registration order. */
111
+ readonly names: readonly string[];
112
+ /**
113
+ * Register a processor (constructed by the DO — the processor is the star,
114
+ * the registry is plumbing) under its contract slug (= subscription name —
115
+ * see {@link RegisterProcessorOptions}) and build its runner: durable
116
+ * two-cursor progress in DO KV keyed by the name, plus — WHEN THE
117
+ * DO PASSES `{ recovery: true }` — the per-runner recovery adapter
118
+ * (keepalive + the core `stream/processor-revived` fact). See the module
119
+ * doc: recovery is REQUIRED for any processor whose `runInBackground` work
120
+ * is consequential; the registry cannot infer that.
121
+ * Duplicate names (and re-registering the same instance) throw. Returns the
122
+ * processor, so DOs keep their `field = registry.register(new XProcessor(...))` shape.
123
+ */
124
+ register<P extends RegisterableProcessor>(processor: P, opts?: RegisterProcessorOptions<RegisteredProcessorState<P>>): P;
125
+ /**
126
+ * The runner-backed READ surface for one registered processor. The runner
127
+ * owns both cursors and the fold — the processor instance holds no
128
+ * readable state at all — so every read goes through here. Hand THIS to
129
+ * `new StreamProcessorRpcTarget(...)` and to every DO verb that reads its
130
+ * own fold: `snapshot`/`waitUntilEvent` come from the runner's committed
131
+ * progress; `getRuntimeState` assembles the processor's contributed
132
+ * runtime bag under the runner's snapshot; `currentState` / `isLoaded`
133
+ * serve `getLiveState` closures without an async hop. Takes the registered
134
+ * instance so the state type flows through, or a registered NAME for hosts
135
+ * that route doors by subscription name (the facet) — the name form cannot
136
+ * carry the state type, so its reads publish `unknown` state.
137
+ */
138
+ reads<P extends RegisterableProcessor>(processor: P): RegisteredProcessorReads<RegisteredProcessorState<P>>;
139
+ reads(name: string): RegisteredProcessorReads<unknown>;
140
+ /** Observe committed fold changes for one registered processor. */
141
+ observeStateChanges<P extends RegisterableProcessor>(processor: P, observer: (snapshot: {
142
+ offset: number;
143
+ state: RegisteredProcessorState<P>;
144
+ }) => void): () => void;
145
+ /**
146
+ * Wire this to the host DO's wakeStreamProcessor RPC method. Resolves the
147
+ * woken runner by the request's `name` (= contract slug) and answers with
148
+ * its acknowledged cursor and a fresh processEventBatch.
149
+ */
150
+ wakeStreamProcessor(args: StreamProcessorWakeRequest): Promise<StreamProcessorWakeResponse>;
151
+ /**
152
+ * Pull any events stream delivery has not (yet) brought this runner and
153
+ * drive them now. Call before serving a read that must reflect a write the
154
+ * caller just made (read-your-writes): push delivery is asynchronous. The
155
+ * pull is serialized with live frames on the runner's chain, so racing a
156
+ * sink is safe. A direct Durable Object lifecycle loss gets one replay on
157
+ * the runner's durable cursor; all application failures and a second
158
+ * availability failure throw. Stale state is never presented as success.
159
+ */
160
+ catchUp(name: string): Promise<void>;
161
+ /**
162
+ * Operator seam: clear one runner's revival crash-loop budget and pull its
163
+ * owed retry in to the confirmation lead — the no-deploy antidote for a
164
+ * 3-strikes plateau ("backing off (plateau 360m). A deploy resets the
165
+ * budget."). No-op for a recovery-less runner.
166
+ */
167
+ resetRecoveryBackoff(name: string): void;
168
+ /**
169
+ * Wire this to the host DO's `alarm()` handler — REQUIRED on every hosting
170
+ * class. The fire routes to EVERY runner (each keepalive self-gates on its
171
+ * own persisted armed time), so a DO sharing the alarm with its own
172
+ * scheduling (see {@link setAlarmSlice}) calls this unconditionally and
173
+ * then runs its own due work.
174
+ */
175
+ handleAlarm(alarmInfo?: AlarmInvocationInfo): Promise<void>;
176
+ /**
177
+ * Share the single DO alarm: each named slice states its own desired fire
178
+ * time (or null for none) and the registry arms the earliest across all
179
+ * slices (each runner's keepalive rides its own `keepalive:<slug>` slice).
180
+ * A slice owner must tolerate early fires (another slice's) and re-arm
181
+ * itself during its handler — in-memory desires do not survive eviction;
182
+ * the durable alarm plus each subsystem's re-derivation do. The returned
183
+ * promise settles when the platform alarm durably reflects the change;
184
+ * await it (and rethrow) where durability is load-bearing (error-path
185
+ * fallbacks), ignore it everywhere else.
186
+ */
187
+ setAlarmSlice(name: string, atMs: number | null): Promise<void>;
188
+ /** The slice's own current desire (NOT the merged alarm time). */
189
+ getAlarmSlice(name: string): number | null;
190
+ };
191
+ export declare function createStreamProcessorRegistry<Live extends object = Record<string, unknown>>(ctx: DurableObjectState, options: {
192
+ stream: ProcessorStream;
193
+ /** Path of the hosted stream. The registry fences every
194
+ * `wakeStreamProcessor` against this exact `(projectId, path)`: a wake
195
+ * carrying a matching processor slug but a DIFFERENT coordinate is
196
+ * rejected, so a stale or miswired subscription can never fold a foreign
197
+ * stream into this processor. (Provenance stamping still lives in the
198
+ * processors; this is the delivery-side isolation check.) */
199
+ path: string;
200
+ /** Owning project, or null on a global (deployment-root) stream. The other
201
+ * half of the wake coordinate fence (see `path`). */
202
+ projectId: string | null;
203
+ /** Worker deploy version; a change resets each keepalive's crash-loop
204
+ * budget (the antidote deploy). Pass `workerVersion(env)`. REQUIRED: a
205
+ * registry that silently defaulted this could never take the
206
+ * version-reset path, so a deterministic crash loop would wait out the
207
+ * full plateau even after the fixing deploy shipped. */
208
+ version: string;
209
+ /** Injected clock for the node test harness; production uses Date.now. */
210
+ now?: () => number;
211
+ /**
212
+ * Assemble this node's live state (see `LiveState`) — this is what TYPES
213
+ * the registry's `Live` parameter. Called on every registered runner's
214
+ * committed-state change and on `loadAndRefreshLive`. Omit and the live
215
+ * state is the primary (first-registered) runner's reduced state
216
+ * (untyped: `Live` stays the default record); provide it to project a
217
+ * redacted view or fold in extras (e.g. a streams index).
218
+ */
219
+ getLiveState?: () => Live;
220
+ /**
221
+ * Fires after every `assembleLive` pass — with `skippedUnloadedRunners:
222
+ * false` when the live state was actually reassembled, `true` when the
223
+ * pass no-oped on the unloaded-runner wall (a fresh incarnation woken by
224
+ * one runner's delivery while other runners are still cold). A host that
225
+ * pushes live state to external watchers (the liveState socket lane)
226
+ * needs the skipped signal: without it, a change committed behind the
227
+ * wall would never reach them. A dumb callback — policy lives with the
228
+ * caller.
229
+ */
230
+ onLiveAssembled?: (assembly: {
231
+ skippedUnloadedRunners: boolean;
232
+ }) => void;
233
+ }): StreamProcessorRegistry<Live>;
@@ -0,0 +1,289 @@
1
+ import type { ProcessorStream } from "./stream-handle.ts";
2
+ import type { ProcessorState } from "./processor-contracts.ts";
3
+ import type { StreamEvent } from "./schemas.ts";
4
+ import { type StreamEventBatch } from "./rpc-types.ts";
5
+ import { StreamProcessor, type MaybePromise, type StreamProcessorContract } from "./stream-processor.ts";
6
+ /**
7
+ * The reduction half of a processor's durable progress: a disposable CACHE of
8
+ * the fold (the journal is the authority). `reducerVersion` is the cache key —
9
+ * a deploy that changes it invalidates the cache and triggers an automatic
10
+ * reduce-only refold at load, which re-runs `reduce` ONLY. That is the whole point of splitting
11
+ * this from {@link ProcessingProgress}: today's single `{offset, state}` cursor
12
+ * makes a routine state-schema deploy refold history AND re-run `processEvent`
13
+ * across it, re-driving real vendor calls.
14
+ */
15
+ export type ReductionProgress<State> = {
16
+ /** Cache key for the fold; a mismatch discards `state` and refolds. */
17
+ reducerVersion: string;
18
+ /** The highest offset folded into `state`. */
19
+ reducedThroughOffset: number;
20
+ /** The fold through `reducedThroughOffset`, under `reducerVersion`. */
21
+ state: State;
22
+ };
23
+ /**
24
+ * The processing half of a processor's durable progress: the AUTHORITATIVE
25
+ * effect-acknowledgement cursor. Unlike the reduction cache it is never
26
+ * discarded — rewinding it re-runs side effects. `cursorRevision` is the CAS
27
+ * fence for exactly those rewinds: every commit asserts it, and a bump makes
28
+ * every in-flight continuation of the old cursor position stale.
29
+ */
30
+ export type ProcessingProgress = {
31
+ /** Every effect at or below this offset is acknowledged (durably settled). */
32
+ acknowledgedThroughOffset: number;
33
+ /** Monotonic fencing token; a bump is the only sanctioned way to move
34
+ * `acknowledgedThroughOffset` backward. */
35
+ cursorRevision: number;
36
+ };
37
+ /**
38
+ * A processor's two durable positions, persisted as one record. Invariant
39
+ * (when persisted): `reduction.reducedThroughOffset <=
40
+ * processing.acknowledgedThroughOffset` — the fold cache may lag the effect
41
+ * cursor (it is rebuildable), but a fold AHEAD of acknowledged effects would
42
+ * let `snapshot()` show state derived from events whose effects a cursor
43
+ * rewind is about to re-run. Core (Phase 2) is the graceful degradation:
44
+ * reduction only, no processing cursor — same structure, same reduce-only refold.
45
+ */
46
+ export type ProcessorProgress<State> = {
47
+ /** Random identity of the stream lifetime whose offsets and fold this record describes. */
48
+ streamId: string;
49
+ reduction: ReductionProgress<State>;
50
+ processing: ProcessingProgress;
51
+ };
52
+ /**
53
+ * Durable progress store, CAS-fenced by `cursorRevision`. The runner reads
54
+ * once at open, then commits once per delivered batch; `commit` rejects
55
+ * (throws) if `expectedCursorRevision` no longer matches the persisted
56
+ * revision — the fence that stops a stale incarnation (or a continuation
57
+ * outliving a cursor rewind) from clobbering the rewound cursor.
58
+ * An absent record reads as revision 0. Backends use DO KV or an in-memory
59
+ * store in tests. Related projections share the same commit boundary.
60
+ */
61
+ export type ProcessorProgressStore<State> = {
62
+ read(): MaybePromise<ProcessorProgress<State> | undefined>;
63
+ commit(progress: ProcessorProgress<State>, opts: {
64
+ expectedCursorRevision: number;
65
+ expectedStreamId: string | undefined;
66
+ }): MaybePromise<void>;
67
+ /**
68
+ * Atomically replace progress after the stream at this path is recreated.
69
+ * Backends with related durable projections must reset those in the same
70
+ * transaction; omitting this method makes recreation fail closed.
71
+ */
72
+ replaceForStream?(progress: ProcessorProgress<State>, opts: {
73
+ expectedCursorRevision: number;
74
+ expectedStreamId: string;
75
+ }): MaybePromise<void>;
76
+ };
77
+ /**
78
+ * Optional recovery capability. Present only for durable processors that own
79
+ * background obligations (`runInBackground` work whose OUTCOME matters).
80
+ *
81
+ * - `keepAliveWhile` schedules a durable alarm ahead of in-flight work, so an
82
+ * incarnation that dies owing work is revived by the alarm's fire. The
83
+ * production adapter is `(work) => keepalive.track(work())` over ONE
84
+ * ProcessorKeepalive (stream-processor-keepalive.ts) — the runner REUSES
85
+ * that machinery wholesale, it never reinvents mark/backoff/quiet-clean.
86
+ * - The adapter's private revival pass appends the core
87
+ * `stream/processor-revived` fact (the payload's `processorSlug` names the
88
+ * revived processor), guaranteeing at least one delivery turn even at zero
89
+ * lag. Consuming the fact is OPTIONAL: an unconsumed head-reaching frame
90
+ * still gets the runner's eventless
91
+ * `processEvent({ event: null, delivery: { caughtUp: true } })` pass.
92
+ * - `handleAlarm` services the durable timer (`ProcessorKeepalive.onAlarm`);
93
+ * the host DO multiplexes its single alarm across runners and routes fires
94
+ * to {@link StreamProcessorRunner.handleAlarm}, which delegates here.
95
+ */
96
+ export type ProcessorRecovery = {
97
+ keepAliveWhile(work: () => Promise<unknown>): void;
98
+ handleAlarm(info?: unknown): MaybePromise<void>;
99
+ /**
100
+ * Operator seam: clear the keepalive's crash-loop budget and pull an owed
101
+ * retry in to the confirmation lead — the no-deploy antidote for a
102
+ * 3-strikes revival plateau. Optional: in-memory/test recoveries without a
103
+ * durable budget have nothing to reset.
104
+ */
105
+ resetBackoff?(): void;
106
+ };
107
+ /**
108
+ * The ONE optional durability adapter a hosting runtime hands the runner:
109
+ * `progress` is required whenever the processor is durable at all (without the
110
+ * adapter the runner keeps progress in memory — tests, ephemeral
111
+ * views); `recovery` is orthogonal and present only when the processor owns
112
+ * background work that must survive eviction. This is deliberately where
113
+ * every runtime-specific concern lives — no Cloudflare `ctx` in the runner.
114
+ */
115
+ type ProcessorDurability<State> = {
116
+ progress: ProcessorProgressStore<State>;
117
+ recovery?: ProcessorRecovery;
118
+ };
119
+ /** Honest delivery information handed to `processEvent`. */
120
+ export type DeliveryContext = {
121
+ /** Random identity of the stream lifetime that delivered this turn. */
122
+ streamId: string;
123
+ /**
124
+ * The at-head signal: the scan has reached the highest raw stream offset
125
+ * the runner has observed, so `state` is the complete reduction of
126
+ * everything it has seen. It is true on the last consumed event of a
127
+ * head-reaching frame. If that frame contains no consumed event, the runner
128
+ * makes one eventless `processEvent` call (`event: null`) with this flag
129
+ * instead; an unconsumed tail must not strand obligations on an otherwise
130
+ * quiet stream.
131
+ */
132
+ caughtUp: boolean;
133
+ };
134
+ /**
135
+ * One transport scan as delivered to the runner. The scan coordinates are
136
+ * first-class rather than inferred from `events`: a delivery may deliberately
137
+ * omit ephemeral or selector-filtered rows, including an entirely empty
138
+ * interval, while still proving that every raw offset in the interval was
139
+ * examined. Advancing through that proof is what prevents filtered rows from
140
+ * leaving a processor cursor below the scanned-through offset.
141
+ */
142
+ export type StreamProcessorEventBatch = Pick<StreamEventBatch, "events" | "scannedAfterOffset" | "scannedThroughOffset" | "streamId" | "streamMaxOffset">;
143
+ /**
144
+ * Processes event batches for one processor on one stream. Runtime-neutral:
145
+ * the browser, the Durable Object registry, and the in-memory
146
+ * test harness all instantiate exactly this class and differ only in the
147
+ * `durability` / `keepAlive` adapters they pass. One runner per processor —
148
+ * the "host" of old survives only as a thin registry that builds adapters and
149
+ * routes wakes/alarms to the right runner.
150
+ *
151
+ * Serialization: batches and self-pulls share ONE in-memory chain, so a
152
+ * catch-up never interleaves with a half-processed batch. Cross-incarnation
153
+ * races (a stale runner outliving progress made elsewhere) are fenced durably
154
+ * instead, by the progress store's `cursorRevision` CAS + monotonic fence.
155
+ */
156
+ export declare class StreamProcessorRunner<Contract extends StreamProcessorContract, Deps extends object = object> {
157
+ #private;
158
+ private readonly processor;
159
+ private readonly hooks;
160
+ private readonly stream;
161
+ private readonly durability;
162
+ private readonly keepAlive;
163
+ private readonly now;
164
+ private readonly readPageSize;
165
+ constructor(args: {
166
+ /** The processor to run — passed in; the runner never constructs one. */
167
+ processor: StreamProcessor<Contract, Deps>;
168
+ /** The processor's home stream (replay reads, revival appends). */
169
+ stream: ProcessorStream;
170
+ /** Durable progress + optional recovery; omit for in-memory (tests, ephemeral views). */
171
+ durability?: ProcessorDurability<ProcessorState<Contract>>;
172
+ /** Keeps in-flight work alive with the hosting DO's `waitUntil`. */
173
+ keepAlive?: (work: () => Promise<unknown>) => void;
174
+ /** Injected clock for the test harness; production uses Date.now. */
175
+ now?: () => number;
176
+ /** Journal read page size (refold/catch-up paging); tests shrink it. */
177
+ readPageSize?: number;
178
+ });
179
+ /**
180
+ * Opens the processor's event-batch callback and returns its committed
181
+ * processing offset. A hosted processor wake returns this pair to a source
182
+ * stream; the browser database writer calls the same method directly.
183
+ *
184
+ * `checkpointOffset` is the PROCESSING cursor (`acknowledgedThroughOffset`),
185
+ * never the reduction offset: the caller resumes after this value, and
186
+ * resuming from a reduction-pinned snapshot
187
+ * offset could skip events whose effects were never acknowledged.
188
+ *
189
+ * `processEventBatch` is the only place transport batching enters the
190
+ * runner; inside it the runner reduces and processes one event at a time. A
191
+ * hosting transport may adapt how the promise is observed, but must not
192
+ * duplicate these semantics.
193
+ */
194
+ openEventBatchCallback(expectedStreamId?: string): Promise<{
195
+ checkpointOffset: number;
196
+ processEventBatch: (batch: StreamProcessorEventBatch) => Promise<void>;
197
+ }>;
198
+ /**
199
+ * Open the callback used by a trusted hosted source Stream.
200
+ *
201
+ * The request already carries that source's authoritative stream ID. Reading
202
+ * it back before returning would deadlock a colocated Processor Facet: the
203
+ * source alarm owns the wake RPC while the facet's identity/refold read waits
204
+ * for that same source turn. Return the durable processing cursor without a
205
+ * source read, then finish any reduction-cache load when the source invokes
206
+ * the independent one-way batch callback.
207
+ */
208
+ openHostedEventBatchCallback(streamId: string): Promise<{
209
+ checkpointOffset: number;
210
+ processEventBatch: (batch: StreamProcessorEventBatch) => Promise<void>;
211
+ }>;
212
+ /** Handle a durable recovery alarm routed here by the hosting registry. */
213
+ handleAlarm(info?: unknown): Promise<void>;
214
+ /** One consistent read of the fold, pinned to `reducedThroughOffset`. */
215
+ snapshot(): Promise<{
216
+ offset: number;
217
+ state: ProcessorState<Contract>;
218
+ }>;
219
+ /**
220
+ * Whether published state IS a real fold rather than the schema default —
221
+ * the legacy `isLoaded` gate. With the runner, the load itself performs any
222
+ * pending refold, so this is true whenever a load has completed and false
223
+ * only before the first successful load.
224
+ */
225
+ get isLoaded(): boolean;
226
+ /** Highest offset whose processing and blocking consequences have committed. */
227
+ get currentAcknowledgedThroughOffset(): number;
228
+ /** Source lifetime paired atomically with the current committed state and cursor. */
229
+ get currentStreamId(): string | undefined;
230
+ /**
231
+ * The current committed fold, synchronously (the schema default until the
232
+ * first load) — the legacy `StreamProcessor.currentState`,
233
+ * kept so a hosting registry can assemble its
234
+ * live state without an async hop. Gate on {@link isLoaded} first: a cold
235
+ * runner reports the default, and publishing that anywhere live would wipe
236
+ * real facts for state observers.
237
+ */
238
+ get currentState(): ProcessorState<Contract>;
239
+ /**
240
+ * Observe committed reduced-state changes IN-PROCESS: the observer is a
241
+ * local function (the hosting registry wires it to reassemble its
242
+ * live-state engine), never a retained RPC stub. It fires after a batch
243
+ * commit lands durably AND the committed state changed identity — the
244
+ * runner's home for the legacy `StreamProcessor.observeStateChanges` +
245
+ * post-persist notify. Returns a function that stops observing.
246
+ */
247
+ observeStateChanges(observer: (snapshot: {
248
+ offset: number;
249
+ state: ProcessorState<Contract>;
250
+ }) => void): () => void;
251
+ /**
252
+ * Read journal pages after the acknowledged cursor and process them until
253
+ * caught up — the public method for read-your-writes and a
254
+ * hosting registry's cold-load healing (the legacy host's `catchUpInternal`
255
+ * shape). One page of lookahead, so every non-final batch carries a
256
+ * `streamMaxOffset` past its own last event and only the genuinely final page is
257
+ * marked caught up. Serialized with delivered batches on the runner's chain; failures
258
+ * RETHROW — the caller owns any swallow-and-log policy.
259
+ */
260
+ catchUp(): Promise<void>;
261
+ /**
262
+ * Resolve once the ACKNOWLEDGED cursor reaches `offset` — the single
263
+ * wait-for-progress door (read-your-writes: append, then wait on the offset
264
+ * the append returned). The offset form never depends on stream delivery to
265
+ * reach an event that ALREADY EXISTS on the stream: when the cursor is
266
+ * behind, it starts a chain-serialized journal read ({@link catchUp}); the
267
+ * waiting promise covers only a genuinely
268
+ * FUTURE offset the pull cannot reach yet. The predicate form observes
269
+ * FUTURE deliveries only — an event not yet appended (e.g. runScript's
270
+ * completion, appended later by `runInBackground` work; that work runs OFF
271
+ * the runner chain and outside the awaiting handler, so the halted waiter
272
+ * never gates the append or the delivery that resolves it) — and resolves
273
+ * after the batch that delivered the matching event has durably committed,
274
+ * so state already reflects it.
275
+ */
276
+ waitUntilEvent(args: {
277
+ predicate: (event: StreamEvent) => boolean;
278
+ timeoutMs?: number;
279
+ signal?: AbortSignal;
280
+ }): Promise<void>;
281
+ waitUntilEvent(args: {
282
+ offset: number;
283
+ timeoutMs?: number;
284
+ signal?: AbortSignal;
285
+ }): Promise<void>;
286
+ /** Release processor resources. Idempotent; a disposed runner rejects new work. */
287
+ dispose(): void;
288
+ }
289
+ export {};