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.
- package/README.md +86 -76
- package/THIRD_PARTY_NOTICES.md +55 -0
- package/bin/iterate.js +18 -3
- package/dist/api-url-B6404M82.mjs +17 -0
- package/dist/api-url-B6404M82.mjs.map +1 -0
- package/dist/app-ref-BipL0feU.mjs +35 -0
- package/dist/app-ref-BipL0feU.mjs.map +1 -0
- package/dist/app-ref-C1CrgXqX.mjs +7 -0
- package/dist/app-ref-C1CrgXqX.mjs.map +1 -0
- package/dist/app-ref-DYai_om1.mjs +7 -0
- package/dist/app-ref-DYai_om1.mjs.map +1 -0
- package/dist/cli-D0c-pDL_.mjs +1010 -0
- package/dist/cli-D0c-pDL_.mjs.map +1 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.mjs +4 -0
- package/dist/cloudflare-BTm90gQ4.mjs +951 -0
- package/dist/cloudflare-BTm90gQ4.mjs.map +1 -0
- package/dist/contract-s4FW4eES.mjs +309 -0
- package/dist/contract-s4FW4eES.mjs.map +1 -0
- package/dist/document-review/index.d.ts +5 -0
- package/dist/document-review/types.d.ts +107 -0
- package/dist/document-review.mjs +7015 -0
- package/dist/document-review.mjs.map +1 -0
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs +205 -0
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +1 -0
- package/dist/idempotency-DleloJNt.mjs +28 -0
- package/dist/idempotency-DleloJNt.mjs.map +1 -0
- package/dist/index.mjs +1 -1
- package/dist/itx/api-url.d.ts +6 -0
- package/dist/itx/itx-node-client.d.ts +65 -0
- package/dist/itx/itx-session.d.ts +215 -0
- package/dist/itx/owned-rpc-session.d.ts +14 -0
- package/dist/itx/query-client.d.ts +10 -0
- package/dist/itx-api.generated.d.ts +6195 -0
- package/dist/itx-session-sjud8GiT.mjs +534 -0
- package/dist/itx-session-sjud8GiT.mjs.map +1 -0
- package/dist/live-state-BJNqOwFw.mjs +299 -0
- package/dist/live-state-BJNqOwFw.mjs.map +1 -0
- package/dist/next/api.d.ts +479 -0
- package/dist/next/api.mjs +0 -0
- package/dist/next/app-server.d.ts +44 -0
- package/dist/next/app-server.mjs +479 -0
- package/dist/next/app-server.mjs.map +1 -0
- package/dist/next/app-session.d.ts +49 -0
- package/dist/next/app-session.mjs +238 -0
- package/dist/next/app-session.mjs.map +1 -0
- package/dist/next/app.d.ts +29 -0
- package/dist/next/app.mjs +141 -0
- package/dist/next/app.mjs.map +1 -0
- package/dist/next/client/live-state.d.ts +63 -0
- package/dist/next/client/oauth.d.ts +12 -0
- package/dist/next/client/react.d.ts +109 -0
- package/dist/next/client/socket.d.ts +6 -0
- package/dist/next/client.mjs +156 -0
- package/dist/next/client.mjs.map +1 -0
- package/dist/next/expression.d.ts +146 -0
- package/dist/next/expression.mjs +399 -0
- package/dist/next/expression.mjs.map +1 -0
- package/dist/next/lib.d.ts +56 -0
- package/dist/next/lib.mjs +199 -0
- package/dist/next/lib.mjs.map +1 -0
- package/dist/next/oauth-scopes.d.ts +32 -0
- package/dist/next/oauth-scopes.mjs +40 -0
- package/dist/next/oauth-scopes.mjs.map +1 -0
- package/dist/next/oauth.mjs +29 -0
- package/dist/next/oauth.mjs.map +1 -0
- package/dist/next/principal.d.ts +64 -0
- package/dist/next/principal.mjs +98 -0
- package/dist/next/principal.mjs.map +1 -0
- package/dist/next/project-ingress.d.ts +37 -0
- package/dist/next/project-ingress.mjs +75 -0
- package/dist/next/project-ingress.mjs.map +1 -0
- package/dist/next/react.mjs +285 -0
- package/dist/next/react.mjs.map +1 -0
- package/dist/next/sdk/auth.d.ts +5 -0
- package/dist/next/sdk/index.d.ts +112 -0
- package/dist/next/sdk.mjs +139 -0
- package/dist/next/sdk.mjs.map +1 -0
- package/dist/next/stream/processor.d.ts +378 -0
- package/dist/next/stream/processor.mjs +582 -0
- package/dist/next/stream/processor.mjs.map +1 -0
- package/dist/next/stream/run.d.ts +58 -0
- package/dist/next/stream/run.mjs +40 -0
- package/dist/next/stream/run.mjs.map +1 -0
- package/dist/next-node.d.ts +15 -0
- package/dist/next-node.mjs +51 -0
- package/dist/next-node.mjs.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.mjs +185 -0
- package/dist/node.mjs.map +1 -0
- package/dist/processor-host-capabilities-BMFH3KTM.mjs +56 -0
- package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +1 -0
- package/dist/processors/cloudflare.d.ts +3 -0
- package/dist/processors/durable-object-processor-durability.d.ts +79 -0
- package/dist/processors/event-consumption-metrics.d.ts +82 -0
- package/dist/processors/idempotency.d.ts +13 -0
- package/dist/processors/index.d.ts +12 -0
- package/dist/processors/processor-contracts.d.ts +342 -0
- package/dist/processors/processor-facet.d.ts +186 -0
- package/dist/processors/processor-host-capabilities.d.ts +60 -0
- package/dist/processors/prompt-sections.d.ts +17 -0
- package/dist/processors/rpc-types.d.ts +515 -0
- package/dist/processors/schemas.d.ts +102 -0
- package/dist/processors/stream-handle.d.ts +45 -0
- package/dist/processors/stream-processor-keepalive.d.ts +95 -0
- package/dist/processors/stream-processor-registry.d.ts +233 -0
- package/dist/processors/stream-processor-runner.d.ts +289 -0
- package/dist/processors/stream-processor.d.ts +339 -0
- package/dist/processors/stream-runtime-metrics.d.ts +107 -0
- package/dist/processors/testing.d.ts +302 -0
- package/dist/processors-BoNyeBfQ.mjs +10 -0
- package/dist/processors-BoNyeBfQ.mjs.map +1 -0
- package/dist/processors-cloudflare.mjs +3 -0
- package/dist/processors-testing.mjs +435 -0
- package/dist/processors-testing.mjs.map +1 -0
- package/dist/processors.mjs +52 -0
- package/dist/processors.mjs.map +1 -0
- package/dist/protocol-DnK_f2m6.mjs +251 -0
- package/dist/protocol-DnK_f2m6.mjs.map +1 -0
- package/dist/sdk/capnweb/index.d.ts +2 -0
- package/dist/sdk/capnweb/live-state/compact.d.ts +5 -0
- package/dist/sdk/capnweb/live-state/diff.d.ts +41 -0
- package/dist/sdk/capnweb/live-state/engine.d.ts +44 -0
- package/dist/sdk/capnweb/live-state/index.d.ts +41 -0
- package/dist/sdk/capnweb/live-state/protocol.d.ts +87 -0
- package/dist/sdk/capnweb/live-state/retain.d.ts +23 -0
- package/dist/sdk/capnweb/live-state/store.d.ts +20 -0
- package/dist/sdk/capnweb/live-state/types.d.ts +11 -0
- package/dist/sdk/capnweb/react.d.ts +45 -0
- package/dist/sdk/capnweb/react.mjs +316 -0
- package/dist/sdk/capnweb/react.mjs.map +1 -0
- package/dist/sdk/capnweb.mjs +4 -0
- package/dist/sdk/itx/react.d.ts +191 -0
- package/dist/sdk/itx/react.mjs +383 -0
- package/dist/sdk/itx/react.mjs.map +1 -0
- package/dist/sdk-DMB-IM11.mjs +933 -0
- package/dist/sdk-DMB-IM11.mjs.map +1 -0
- package/dist/sdk.d.ts +339 -0
- package/dist/sdk.mjs +2 -0
- package/dist/serve-itx.d.ts +46 -0
- package/dist/starter-apps/flake-dashboard/app-ref.d.ts +31 -0
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs +1055 -0
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/flake-dashboard/contract.d.ts +4839 -0
- package/dist/starter-apps/flake-dashboard/contract.mjs +2 -0
- package/dist/starter-apps/flake-dashboard/index.d.ts +17 -0
- package/dist/starter-apps/flake-dashboard/index.mjs +56 -0
- package/dist/starter-apps/flake-dashboard/index.mjs.map +1 -0
- package/dist/starter-apps/flake-dashboard/worker.d.ts +4607 -0
- package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +8914 -0
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs +17987 -0
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/github-ai-linter/contract.d.ts +9193 -0
- package/dist/starter-apps/github-ai-linter/index.d.ts +10 -0
- package/dist/starter-apps/github-ai-linter/index.mjs +36 -0
- package/dist/starter-apps/github-ai-linter/index.mjs.map +1 -0
- package/dist/starter-apps/github-ai-linter/prompt.d.ts +13 -0
- package/dist/starter-apps/github-ai-linter/review-bot.d.ts +808 -0
- package/dist/starter-apps/github-ai-linter/rules.d.ts +34 -0
- package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +19 -0
- package/dist/starter-apps/github-ai-linter/worker.d.ts +19 -0
- package/dist/starter-apps/github-ai-linter/worker.mjs +947 -0
- package/dist/starter-apps/github-ai-linter/worker.mjs.map +1 -0
- package/dist/starter-apps/guestbook/app-ref.d.ts +27 -0
- package/dist/starter-apps/guestbook/client.d.ts +7 -0
- package/dist/starter-apps/guestbook/client.mjs +59 -0
- package/dist/starter-apps/guestbook/configured-worker.mjs +205 -0
- package/dist/starter-apps/guestbook/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/guestbook/index.d.ts +9 -0
- package/dist/starter-apps/guestbook/index.mjs +31 -0
- package/dist/starter-apps/guestbook/index.mjs.map +1 -0
- package/dist/starter-apps/guestbook/processor.d.ts +2267 -0
- package/dist/starter-apps/guestbook/worker.d.ts +26 -0
- package/dist/starter-apps/guestbook/worker.mjs +191 -0
- package/dist/starter-apps/guestbook/worker.mjs.map +1 -0
- package/dist/starter-apps/media/configured-worker.mjs +577 -0
- package/dist/starter-apps/media/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/media/index.mjs +36 -0
- package/dist/starter-apps/media/index.mjs.map +1 -0
- package/dist/starter-apps/media/ref.mjs +20 -0
- package/dist/starter-apps/media/ref.mjs.map +1 -0
- package/dist/starter-apps/media/worker.mjs +579 -0
- package/dist/starter-apps/media/worker.mjs.map +1 -0
- package/dist/starter-apps/notes/configured-worker.mjs +6134 -0
- package/dist/starter-apps/notes/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/notes/index.mjs +23 -0
- package/dist/starter-apps/notes/index.mjs.map +1 -0
- package/dist/starter-apps/notes/ref.mjs +21 -0
- package/dist/starter-apps/notes/ref.mjs.map +1 -0
- package/dist/starter-apps/notes/worker.mjs +427 -0
- package/dist/starter-apps/notes/worker.mjs.map +1 -0
- package/dist/starter-apps/todo/client.mjs +59 -0
- package/dist/starter-apps/todo/configured-worker.mjs +2864 -0
- package/dist/starter-apps/todo/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/todo/index.d.ts +8 -0
- package/dist/starter-apps/todo/index.mjs +29 -0
- package/dist/starter-apps/todo/index.mjs.map +1 -0
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs +2082 -0
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +1 -0
- package/dist/usingCtx-inzbY1Qz.mjs +57 -0
- package/dist/usingCtx-mZx5nsAW.mjs +11800 -0
- package/dist/usingCtx-mZx5nsAW.mjs.map +1 -0
- package/dist/worker-ref-DZxPDmb_.mjs +390 -0
- package/dist/worker-ref-DZxPDmb_.mjs.map +1 -0
- package/menubar/Iterate.entitlements +12 -0
- package/menubar/Iterate.swift +914 -0
- package/menubar/IterateIcon.swift +145 -0
- package/menubar/README.md +28 -0
- package/menubar/build-menubar-app.sh +59 -0
- package/package.json +235 -18
- package/dist/cli-DMS4kJph.mjs +0 -868
- package/dist/cli-DMS4kJph.mjs.map +0 -1
- package/dist/config-DtnR7Lv7.mjs +0 -170
- package/dist/config-DtnR7Lv7.mjs.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/stream-tui/agent-chat-terminal.d.mts +0 -1
- package/dist/stream-tui/agent-chat-terminal.mjs +0 -933
- 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 {};
|