iterate 0.2.7 → 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 +11 -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,139 @@
|
|
|
1
|
+
import { applyPatch, diff, jsonEqual } from "./lib.mjs";
|
|
2
|
+
import { LiveState, ProcessorEngine, ReduceCheckpointTable, StreamProcessor, defineProcessorContract } from "./stream/processor.mjs";
|
|
3
|
+
import { ITX_PRINCIPAL_HEADER } from "./principal.mjs";
|
|
4
|
+
import { RunContract, RunRequested, RunSettled } from "./stream/run.mjs";
|
|
5
|
+
import { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
|
|
6
|
+
import { z, z as z$1 } from "zod";
|
|
7
|
+
import { newHttpBatchRpcSession, newWebSocketRpcSession, newWorkersRpcResponse } from "capnweb";
|
|
8
|
+
//#region src/next/sdk/auth.ts
|
|
9
|
+
const Principal = z$1.object({
|
|
10
|
+
actor: z$1.string().min(1),
|
|
11
|
+
email: z$1.string().optional()
|
|
12
|
+
});
|
|
13
|
+
/** Project ingress strips public identity headers and stamps the verified caller.
|
|
14
|
+
* This guard runs locally in the config worker, before it proxies an app. */
|
|
15
|
+
const auth = { require(request) {
|
|
16
|
+
const url = new URL(request.url);
|
|
17
|
+
if (![
|
|
18
|
+
"GET",
|
|
19
|
+
"HEAD",
|
|
20
|
+
"OPTIONS"
|
|
21
|
+
].includes(request.method)) {
|
|
22
|
+
const origin = request.headers.get("origin");
|
|
23
|
+
if (origin && origin !== url.origin) return new Response("Cross-site request refused", { status: 403 });
|
|
24
|
+
}
|
|
25
|
+
const principal = request.headers.get(ITX_PRINCIPAL_HEADER);
|
|
26
|
+
if (principal) {
|
|
27
|
+
Principal.parse(JSON.parse(principal));
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (!["GET", "HEAD"].includes(request.method)) return new Response("Sign in first", { status: 401 });
|
|
31
|
+
return new Response(null, {
|
|
32
|
+
status: 302,
|
|
33
|
+
headers: {
|
|
34
|
+
Location: `/.auth/login?next=${encodeURIComponent(url.pathname + url.search)}`,
|
|
35
|
+
"Cache-Control": "no-store"
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
} };
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/next/sdk/index.ts
|
|
41
|
+
var StreamProcessorDurableObject = class extends DurableObject {
|
|
42
|
+
/** After a runtime field on the processor moved OUTSIDE a batch (an RPC method on this object);
|
|
43
|
+
* inside `processEvent` the engine re-projects on its own. */
|
|
44
|
+
publishLiveState() {
|
|
45
|
+
this.#engine.publishLiveState();
|
|
46
|
+
}
|
|
47
|
+
/** THE push door: the context hands over each committed batch with its scanned-range proof. */
|
|
48
|
+
processEventBatch(events, range) {
|
|
49
|
+
return this.#engine.processEventBatch(events, range);
|
|
50
|
+
}
|
|
51
|
+
/** Catch up from the log (the read-your-writes entry after an eviction). */
|
|
52
|
+
catchUpFromLog() {
|
|
53
|
+
return this.#engine.catchUpFromLog();
|
|
54
|
+
}
|
|
55
|
+
/** THE REVIVE: the context's alarm pass calls it for a due claim — catch up, then run the
|
|
56
|
+
* at-head pass, so an attempt the last incarnation was running is started again from state. */
|
|
57
|
+
revive() {
|
|
58
|
+
return this.#engine.revive();
|
|
59
|
+
}
|
|
60
|
+
/** Caught up through the log, then `{ offset, state }`. */
|
|
61
|
+
snapshot() {
|
|
62
|
+
return this.#engine.snapshot();
|
|
63
|
+
}
|
|
64
|
+
/** The live-state seed door: `{ rev, state: projectLiveState(reduced) }`. */
|
|
65
|
+
liveSnapshot() {
|
|
66
|
+
return this.#engine.liveSnapshot();
|
|
67
|
+
}
|
|
68
|
+
/** The barrier: resolves once processed at least through `offset` (default timeout 10s). */
|
|
69
|
+
waitUntilProcessed(input) {
|
|
70
|
+
return this.#engine.waitUntilProcessed(input);
|
|
71
|
+
}
|
|
72
|
+
/** The loopback to this facet's context: a LOADED class gets it as `env.ITX` (the loader bakes the
|
|
73
|
+
* stub in, worker-loader.ts); a class of THIS worker hosted through `ctx.exports` has the
|
|
74
|
+
* worker's real env and mints the same stub itself from its props — `ctx.exports` is populated
|
|
75
|
+
* inside a facet (__workers-tests__/facet-props.test.ts). */
|
|
76
|
+
#itxEntrypoint() {
|
|
77
|
+
return this.env.ITX ?? this.ctx.exports.ItxEntrypoint({ props: {
|
|
78
|
+
iterateContextName: this.ctx.props.iterateContextName,
|
|
79
|
+
platform: true
|
|
80
|
+
} });
|
|
81
|
+
}
|
|
82
|
+
#engineBuiltOnFirstUse;
|
|
83
|
+
get #engine() {
|
|
84
|
+
return this.#engineBuiltOnFirstUse ??= new ProcessorEngine(this.processor, {
|
|
85
|
+
stream: {
|
|
86
|
+
append: (...events) => this.withItx((itx) => itx.append(...events)),
|
|
87
|
+
appendTo: (path, ...events) => this.withItx((itx) => itx.cd(path).append(...events)),
|
|
88
|
+
read: (after, limit) => this.withItx((itx) => itx.readEvents(after, limit)),
|
|
89
|
+
claim: (at) => this.withItx((itx) => itx.processors.claim(this.ctx.props.name, at))
|
|
90
|
+
},
|
|
91
|
+
storage: new ReduceCheckpointTable(this.ctx.storage.sql)
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** ONE pipelined round trip on the itx scope, then RELEASE it: `env.ITX.get()` and the call
|
|
95
|
+
* pipelined on it PIN THE PARENT DO until GC (the "GC is too late" defect the DO's facet door
|
|
96
|
+
* fixes in the other direction). Await the answer — plain data, the wire already copied it —
|
|
97
|
+
* then dispose the call AND the get. Protected: a host with methods of its own (the workspace,
|
|
98
|
+
* src/workspace/durable-object.ts) reaches its context the same way. */
|
|
99
|
+
async withItx(call) {
|
|
100
|
+
const itx = this.#itxEntrypoint().get();
|
|
101
|
+
const result = call(itx);
|
|
102
|
+
try {
|
|
103
|
+
return await result;
|
|
104
|
+
} finally {
|
|
105
|
+
result[Symbol.dispose]?.();
|
|
106
|
+
itx[Symbol.dispose]?.();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var ConfigWorker = class extends WorkerEntrypoint {
|
|
111
|
+
/** At fetch entry: `const denied = this.auth.require(request); if (denied) return denied;` */
|
|
112
|
+
auth = auth;
|
|
113
|
+
/** Process an explicitly subscribed batch with this worker's context scope. */
|
|
114
|
+
async processEventBatch(events, range) {
|
|
115
|
+
const itx = this.env.ITX.get();
|
|
116
|
+
try {
|
|
117
|
+
for (const event of events) await this.processEvent({
|
|
118
|
+
event,
|
|
119
|
+
range,
|
|
120
|
+
itx
|
|
121
|
+
});
|
|
122
|
+
} finally {
|
|
123
|
+
itx[Symbol.dispose]?.();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** THE AUTHOR HOOK — one event at a time, in offset order. Append reactions through the itx scope;
|
|
127
|
+
* make them idempotent (a redelivery must be a no-op). Default: ignore the event. */
|
|
128
|
+
processEvent(_args) {}
|
|
129
|
+
/** THE WEB ROOT — the Request a project host with no app label rode in on (`x-iterate-app` absent;
|
|
130
|
+
* the project's configured ingress target). Route by hostname to `this.env.ITX.get().apps.<x>.fetch(request)`
|
|
131
|
+
* or answer it here. Default: not found. */
|
|
132
|
+
fetch(_request) {
|
|
133
|
+
return new Response("Not found\n", { status: 404 });
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
//#endregion
|
|
137
|
+
export { ConfigWorker, LiveState, RunContract, RunRequested, RunSettled, StreamProcessor, StreamProcessorDurableObject, applyPatch, auth, defineProcessorContract, diff, jsonEqual, newHttpBatchRpcSession, newWebSocketRpcSession, newWorkersRpcResponse, z };
|
|
138
|
+
|
|
139
|
+
//# sourceMappingURL=sdk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk.mjs","names":["z","#engine","#engineBuiltOnFirstUse","#itxEntrypoint"],"sources":["../../src/next/sdk/auth.ts","../../src/next/sdk/index.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { ITX_PRINCIPAL_HEADER } from \"../principal.ts\";\n\nconst Principal = z.object({ actor: z.string().min(1), email: z.string().optional() });\n\n/** Project ingress strips public identity headers and stamps the verified caller.\n * This guard runs locally in the config worker, before it proxies an app. */\nexport const auth = {\n require(request: Request): Response | null {\n const url = new URL(request.url);\n if (![\"GET\", \"HEAD\", \"OPTIONS\"].includes(request.method)) {\n const origin = request.headers.get(\"origin\");\n if (origin && origin !== url.origin)\n return new Response(\"Cross-site request refused\", { status: 403 });\n }\n const principal = request.headers.get(ITX_PRINCIPAL_HEADER);\n if (principal) {\n Principal.parse(JSON.parse(principal)); // Platform-owned stamp; malformed means a defect.\n return null;\n }\n if (![\"GET\", \"HEAD\"].includes(request.method))\n return new Response(\"Sign in first\", { status: 401 });\n return new Response(null, {\n status: 302,\n headers: {\n Location: `/.auth/login?next=${encodeURIComponent(url.pathname + url.search)}`,\n \"Cache-Control\": \"no-store\",\n },\n });\n },\n};\n","// sdk/index.ts — THE userspace SDK surface, bundled (zod included — the owner's call) into every\n// loaded isolate as `processor.js` (os-next's scripts/vite-plugin-processor-sdk.ts bundles it):\n//\n// import { StreamProcessor, StreamProcessorDurableObject, defineProcessorContract, z } from \"./processor.js\";\n//\n// The two workerd HOSTS live here too (this file imports cloudflare:workers; the node lane never imports it):\n// StreamProcessorDurableObject — the `DurableObject` shell that hosts ONE `StreamProcessor` as a facet\n// ConfigWorker — the stateless `WorkerEntrypoint` a project's one event handler extends\n\nimport { DurableObject, WorkerEntrypoint } from \"cloudflare:workers\";\nimport type { IterateContextApi, StreamPage } from \"../api.ts\";\nimport {\n ProcessorEngine,\n type ScannedRange,\n type StreamProcessor,\n ReduceCheckpointTable,\n type StreamEvent,\n type StreamEventInput,\n} from \"../stream/processor.ts\";\nimport { auth } from \"./auth.ts\";\nexport { auth };\nexport {\n StreamProcessor,\n defineProcessorContract,\n jsonEqual,\n type ConsumedEvent,\n type EventCatalog,\n type EventDefinition,\n type EmittedEventInput,\n type EventInput,\n type ProcessorContract,\n type ProcessorState,\n type ProcessorStream,\n type ProcessEventArgs,\n type ReduceArgs,\n type ScannedRange,\n type StreamEvent,\n type StreamEventInput,\n} from \"../stream/processor.ts\";\nexport { z } from \"zod\";\n// capnweb's CLIENT constructors, so userspace can dial a remote capnweb API from inside its isolate\n// through the context's own egress, and `newWorkersRpcResponse`, the SERVER half, so a loaded worker\n// can serve a capnweb API over its `fetch`. The HTTP batch is exported ON PURPOSE beside the\n// WebSocket session: a stateless entrypoint answering one method with one remote call has no session\n// to hold across calls, and a one-shot POST is the honest shape (the lint rule targets long-lived workers).\n// eslint-disable-next-line iterate/no-capnweb-http-batch -- userspace one-shot remote calls; see above\nexport { newHttpBatchRpcSession, newWebSocketRpcSession, newWorkersRpcResponse } from \"capnweb\";\nexport { applyPatch, diff, type PatchOp } from \"../lib.ts\";\nexport { LiveState, type LiveStateSink } from \"../stream/processor.ts\";\n// LIVE STATE for a mini-app DO that is NOT a processor (a processor's base owns one internally):\n// `new LiveState({ append: (e) => env.ITX.get().append(e) }, \"chat\", {…})` — a field initializer\n// cannot await — then `set` to mutate and `snapshot()` as the client seed door (stream/processor.ts).\n// ── StreamProcessorDurableObject ── THE SDK HOST: the `DurableObject` shell that hosts ONE\n// `StreamProcessor` as a facet of its context. An author writes the pure processor and its host,\n// one line long:\n//\n// export class PresenceDurableObject extends StreamProcessorDurableObject {\n// processor = new PresenceProcessor();\n// }\n//\n// hosted through the ordinary `itx.facets.get('presence', { source, className: 'PresenceDurableObject' })`\n// — a processor is a named facet that additionally gets pushed every commit. `processor` is a FIELD\n// so it can take what its effects need from this object (`new Notifier(this.env.ITX)`), and so the\n// same class is constructed bare in a test.\n//\n// IDENTITY is `ctx.props` — `{ iterateContextName, name }`, minted by the parent, the only party\n// that knows it (pinned in __workers-tests__/facet-props.test.ts). THE STREAM is the itx scope\n// behind `env.ITX.get()` (iterate-context.ts `ItxEntrypoint`); the engine's `append`/`read` ride it like any other\n// dotted call.\n//\n// NEVER define alarm(): facets have none (workerd#6810 — the runtime answers \"Facets currently\n// cannot set alarms.\"); a timer, when one is needed, is a scheduled append on the context. The\n// engine's own recovery is a CLAIM on the context's alarm (processor.ts, rule 3): while a\n// `runInBackground` attempt is in flight the context owes this facet a `revive()`, so a host that\n// dies mid-attempt is re-materialized and runs its at-head pass again\n// (__workers-tests__/agent-revive.test.ts: an LLM call survives its context's death).\n\n/** What the parent mints the class with — the whole identity. */\nexport type StreamProcessorProps = { iterateContextName: string; name: string };\n\n/** The itx scope as `env.ITX.get()` hands it over: a context's declared API (api.ts) — a capnweb stub\n * of os-next's `IterateContextRpcTarget`, which satisfies it. */\nexport type ItxScope = IterateContextApi;\n/** What hands the scope over: the loopback entrypoint a loaded worker has as `env.ITX`, or the one a\n * class of the platform's own worker mints from `ctx.exports`. */\nexport type ItxEntrypointService = { get(): ItxScope };\n/** The least a host needs of its scope: the fixed-point log calls the engine makes. The platform's own\n * facets pass the Workers-RPC STUB of a context (every dotted step pipelined; a property there is a\n * promise), which no plain-promise interface can name — so the constraint is this, not `ItxScope`. */\nexport type ProcessorScope = {\n append(...events: StreamEventInput[]): Promise<unknown>;\n readEvents(afterOffset?: number, limit?: number): Promise<unknown>;\n /** The engine's claim on the context's alarm (processor.ts rule 3): \"come back by `at`\", or null. */\n processors: { claim(name: string, at: number | null): Promise<unknown> };\n /** Another context of the project — where `appendTo` lands — by its dotted surface (`.append`),\n * which the platform's handle and a loaded worker's alike answer. Through the table like every\n * other word here: a loaded processor's `cd` goes down only (the app wall), the platform's own\n * go anywhere. */\n cd(path: string): { append(...events: StreamEventInput[]): Promise<unknown> };\n};\n\n/** THE SCOPE ACCESSOR a host hands its processor: one pipelined round trip on the context's itx,\n * released after (`StreamProcessorDurableObject.withItx`). A processor that needs an effect —\n * `itx.cfArtifacts.create(path)`, `itx.ai.run(…)` — takes this and nothing else, so a unit test\n * hands it a fake and the e2e lends one by rule on the context. */\nexport type WithItx<Scope = ItxScope> = <T>(call: (itx: Scope) => T) => Promise<Awaited<T>>;\n\nexport abstract class StreamProcessorDurableObject<\n State = unknown,\n Env extends { ITX?: ItxEntrypointService } = { ITX: ItxEntrypointService },\n Scope extends ProcessorScope = ItxScope,\n> extends DurableObject<Env, StreamProcessorProps> {\n /** The processor this object hosts — `processor = new PresenceProcessor()` at the top of the subclass. */\n abstract readonly processor: StreamProcessor<State>;\n\n // ── what an author reaches (the itx scope is `this.env.ITX.get()`, typed; identity is `this.ctx.props`) ──\n\n /** After a runtime field on the processor moved OUTSIDE a batch (an RPC method on this object);\n * inside `processEvent` the engine re-projects on its own. */\n protected publishLiveState(): void {\n this.#engine.publishLiveState();\n }\n\n // ── the doors the delivery loop and `itx.facets.get(name)` reach ──\n\n /** THE push door: the context hands over each committed batch with its scanned-range proof. */\n processEventBatch(events: StreamEvent[], range: ScannedRange): Promise<void> {\n return this.#engine.processEventBatch(events, range);\n }\n /** Catch up from the log (the read-your-writes entry after an eviction). */\n catchUpFromLog(): Promise<void> {\n return this.#engine.catchUpFromLog();\n }\n /** THE REVIVE: the context's alarm pass calls it for a due claim — catch up, then run the\n * at-head pass, so an attempt the last incarnation was running is started again from state. */\n revive(): Promise<void> {\n return this.#engine.revive();\n }\n /** Caught up through the log, then `{ offset, state }`. */\n snapshot(): Promise<{ offset: number; state: State }> {\n return this.#engine.snapshot();\n }\n /** The live-state seed door: `{ rev, state: projectLiveState(reduced) }`. */\n liveSnapshot(): Promise<{ rev: number; state: unknown }> {\n return this.#engine.liveSnapshot();\n }\n /** The barrier: resolves once processed at least through `offset` (default timeout 10s). */\n waitUntilProcessed(input: { offset: number; timeoutMs?: number }): Promise<void> {\n return this.#engine.waitUntilProcessed(input);\n }\n\n /** The loopback to this facet's context: a LOADED class gets it as `env.ITX` (the loader bakes the\n * stub in, worker-loader.ts); a class of THIS worker hosted through `ctx.exports` has the\n * worker's real env and mints the same stub itself from its props — `ctx.exports` is populated\n * inside a facet (__workers-tests__/facet-props.test.ts). */\n #itxEntrypoint(): { get(): Scope } {\n return (this.env.ITX ??\n (\n this.ctx.exports as unknown as {\n ItxEntrypoint: (options: { props: object }) => ItxEntrypointService;\n }\n ).ItxEntrypoint({\n // PLATFORM: this worker's own class, minted from its own exports — the full handle, the fixed\n // point spellable, `cd` free to go up. A LOADED class never reaches this branch (it has\n // `env.ITX`, baked in by the loader, and its own module's exports).\n props: { iterateContextName: this.ctx.props.iterateContextName, platform: true },\n })) as unknown as {\n get(): Scope;\n };\n }\n // ── the engine: one ProcessorEngine over `processor` and this object's storage, built on first use —\n // `processor` is a subclass field, which does not exist yet while this base class constructs. ──\n #engineBuiltOnFirstUse?: ProcessorEngine<State>;\n get #engine(): ProcessorEngine<State> {\n return (this.#engineBuiltOnFirstUse ??= new ProcessorEngine(this.processor, {\n // The engine's own emits, catch-up and gap repair are the CONTEXT ROOTS `append`, `readEvents`,\n // `processors.claim` — implicit in every context (itx-expression-rewriting.ts rule 3), so they\n // resolve to this log with no row and no hop; a row at `itx.append` is the OWNER's deliberate\n // wall (a jailed processor halts visibly), never a loaded worker's — the fixed point is not a\n // loaded worker's word. `appendTo` is `cd` through the same table: a first-party saga (the\n // platform's mint) reaches `/`; a loaded processor's `cd` goes down only.\n stream: {\n // A stub scope's answers are pipelined shapes by type and plain data on the wire (the\n // engine awaits them): the engine's own types, asserted.\n append: (...events) =>\n this.withItx((itx) => itx.append(...events)) as Promise<StreamEvent[]>,\n appendTo: (path, ...events) =>\n this.withItx((itx) => itx.cd(path).append(...events)) as Promise<StreamEvent[]>,\n read: (after, limit) =>\n this.withItx((itx) => itx.readEvents(after, limit)) as Promise<StreamPage>,\n claim: (at) => this.withItx((itx) => itx.processors.claim(this.ctx.props.name, at)),\n },\n storage: new ReduceCheckpointTable(this.ctx.storage.sql),\n }));\n }\n\n /** ONE pipelined round trip on the itx scope, then RELEASE it: `env.ITX.get()` and the call\n * pipelined on it PIN THE PARENT DO until GC (the \"GC is too late\" defect the DO's facet door\n * fixes in the other direction). Await the answer — plain data, the wire already copied it —\n * then dispose the call AND the get. Protected: a host with methods of its own (the workspace,\n * src/workspace/durable-object.ts) reaches its context the same way. */\n protected async withItx<T>(call: (itx: Scope) => T): Promise<Awaited<T>> {\n const itx = this.#itxEntrypoint().get();\n const result = call(itx);\n try {\n return await result;\n } finally {\n (result as unknown as Disposable)[Symbol.dispose]?.();\n (itx as unknown as Disposable)[Symbol.dispose]?.();\n }\n }\n}\n\n// ConfigWorker is a stateless event handler loaded with an explicit workers.get spec.\n// Subscribe its processEventBatch method explicitly; fetch routing is configured separately.\nexport type ConfigWorkerItx = ItxScope;\nexport type ConfigEventArgs = { event: StreamEvent; range: ScannedRange; itx: ConfigWorkerItx };\n\nexport abstract class ConfigWorker<\n Env extends { ITX: ItxEntrypointService } = { ITX: ItxEntrypointService },\n> extends WorkerEntrypoint<Env> {\n /** At fetch entry: `const denied = this.auth.require(request); if (denied) return denied;` */\n protected readonly auth = auth;\n /** Process an explicitly subscribed batch with this worker's context scope. */\n async processEventBatch(events: StreamEvent[], range: ScannedRange): Promise<void> {\n const itx = this.env.ITX.get();\n try {\n for (const event of events) {\n await this.processEvent({ event, range, itx });\n }\n } finally {\n (itx as unknown as Disposable)[Symbol.dispose]?.();\n }\n }\n\n /** THE AUTHOR HOOK — one event at a time, in offset order. Append reactions through the itx scope;\n * make them idempotent (a redelivery must be a no-op). Default: ignore the event. */\n processEvent(_args: ConfigEventArgs): void | Promise<void> {}\n\n /** THE WEB ROOT — the Request a project host with no app label rode in on (`x-iterate-app` absent;\n * the project's configured ingress target). Route by hostname to `this.env.ITX.get().apps.<x>.fetch(request)`\n * or answer it here. Default: not found. */\n override fetch(_request: Request): Response | Promise<Response> {\n return new Response(\"Not found\\n\", { status: 404 });\n }\n}\n\nexport { RunContract, RunRequested, RunSettled } from \"../stream/run.ts\";\n"],"mappings":";;;;;;;;AAGA,MAAM,YAAYA,IAAE,OAAO;CAAE,OAAOA,IAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CAAG,OAAOA,IAAE,OAAO,CAAC,CAAC,SAAS;AAAE,CAAC;;;AAIrF,MAAa,OAAO,EAClB,QAAQ,SAAmC;CACzC,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;CAC/B,IAAI,CAAC;EAAC;EAAO;EAAQ;CAAS,CAAC,CAAC,SAAS,QAAQ,MAAM,GAAG;EACxD,MAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;EAC3C,IAAI,UAAU,WAAW,IAAI,QAC3B,OAAO,IAAI,SAAS,8BAA8B,EAAE,QAAQ,IAAI,CAAC;CACrE;CACA,MAAM,YAAY,QAAQ,QAAQ,IAAI,oBAAoB;CAC1D,IAAI,WAAW;EACb,UAAU,MAAM,KAAK,MAAM,SAAS,CAAC;EACrC,OAAO;CACT;CACA,IAAI,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,QAAQ,MAAM,GAC1C,OAAO,IAAI,SAAS,iBAAiB,EAAE,QAAQ,IAAI,CAAC;CACtD,OAAO,IAAI,SAAS,MAAM;EACxB,QAAQ;EACR,SAAS;GACP,UAAU,qBAAqB,mBAAmB,IAAI,WAAW,IAAI,MAAM;GAC3E,iBAAiB;EACnB;CACF,CAAC;AACH,EACF;;;AC6EA,IAAsB,+BAAtB,cAIU,cAAyC;;;CAQjD,mBAAmC;EACjC,KAAKC,QAAQ,iBAAiB;CAChC;;CAKA,kBAAkB,QAAuB,OAAoC;EAC3E,OAAO,KAAKA,QAAQ,kBAAkB,QAAQ,KAAK;CACrD;;CAEA,iBAAgC;EAC9B,OAAO,KAAKA,QAAQ,eAAe;CACrC;;;CAGA,SAAwB;EACtB,OAAO,KAAKA,QAAQ,OAAO;CAC7B;;CAEA,WAAsD;EACpD,OAAO,KAAKA,QAAQ,SAAS;CAC/B;;CAEA,eAAyD;EACvD,OAAO,KAAKA,QAAQ,aAAa;CACnC;;CAEA,mBAAmB,OAA8D;EAC/E,OAAO,KAAKA,QAAQ,mBAAmB,KAAK;CAC9C;;;;;CAMA,iBAAmC;EACjC,OAAQ,KAAK,IAAI,OAEb,KAAK,IAAI,QAGT,cAAc,EAId,OAAO;GAAE,oBAAoB,KAAK,IAAI,MAAM;GAAoB,UAAU;EAAK,EACjF,CAAC;CAGL;CAGA;CACA,IAAIA,UAAkC;EACpC,OAAQ,KAAKC,2BAA2B,IAAI,gBAAgB,KAAK,WAAW;GAO1E,QAAQ;IAGN,SAAS,GAAG,WACV,KAAK,SAAS,QAAQ,IAAI,OAAO,GAAG,MAAM,CAAC;IAC7C,WAAW,MAAM,GAAG,WAClB,KAAK,SAAS,QAAQ,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,GAAG,MAAM,CAAC;IACtD,OAAO,OAAO,UACZ,KAAK,SAAS,QAAQ,IAAI,WAAW,OAAO,KAAK,CAAC;IACpD,QAAQ,OAAO,KAAK,SAAS,QAAQ,IAAI,WAAW,MAAM,KAAK,IAAI,MAAM,MAAM,EAAE,CAAC;GACpF;GACA,SAAS,IAAI,sBAAsB,KAAK,IAAI,QAAQ,GAAG;EACzD,CAAC;CACH;;;;;;CAOA,MAAgB,QAAW,MAA8C;EACvE,MAAM,MAAM,KAAKC,eAAe,CAAC,CAAC,IAAI;EACtC,MAAM,SAAS,KAAK,GAAG;EACvB,IAAI;GACF,OAAO,MAAM;EACf,UAAU;GACR,OAAkC,OAAO,QAAQ,GAAG;GACpD,IAA+B,OAAO,QAAQ,GAAG;EACnD;CACF;AACF;AAOA,IAAsB,eAAtB,cAEU,iBAAsB;;CAE9B,OAA0B;;CAE1B,MAAM,kBAAkB,QAAuB,OAAoC;EACjF,MAAM,MAAM,KAAK,IAAI,IAAI,IAAI;EAC7B,IAAI;GACF,KAAK,MAAM,SAAS,QAClB,MAAM,KAAK,aAAa;IAAE;IAAO;IAAO;GAAI,CAAC;EAEjD,UAAU;GACR,IAA+B,OAAO,QAAQ,GAAG;EACnD;CACF;;;CAIA,aAAa,OAA8C,CAAC;;;;CAK5D,MAAe,UAAiD;EAC9D,OAAO,IAAI,SAAS,eAAe,EAAE,QAAQ,IAAI,CAAC;CACpD;AACF"}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import type { SqlStorageValue } from "@cloudflare/workers-types";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { jsonEqual } from "../lib.ts";
|
|
4
|
+
/** What a processor declares: its checkpoint slug and reducer version, what it consumes and emits,
|
|
5
|
+
* and its initial state (`defineProcessorContract` below builds one from zod schemas). */
|
|
6
|
+
export type ProcessorContract<State = unknown> = {
|
|
7
|
+
slug: string;
|
|
8
|
+
/** Bumping this re-reduces state from offset 0 (reduce only — side effects never re-run). */
|
|
9
|
+
version: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
/** What it reacts to: type strings, or "*" for every DURABLE event. Ephemeral events are
|
|
12
|
+
* delivered ONLY when their type is named here — `"*"` never sweeps them. */
|
|
13
|
+
consumes: readonly string[];
|
|
14
|
+
/** What its `append` is allowed to emit. */
|
|
15
|
+
emits: readonly string[];
|
|
16
|
+
/** The schema-initial state ("{} with every field defaulted" for zod contracts). */
|
|
17
|
+
initialState: () => State;
|
|
18
|
+
/** The zod payload schema for a consumed event type (owned or a dep's), or undefined if the type
|
|
19
|
+
* is unknown or the contract declares no `events` catalog. The engine validates a consumed event's
|
|
20
|
+
* payload against it before reducing (a malformed payload for a KNOWN event is skipped, never
|
|
21
|
+
* folded). Present on `defineProcessorContract` contracts; a hand-built core contract omits it and
|
|
22
|
+
* reduces unvalidated. */
|
|
23
|
+
payloadSchemaFor?: (type: string) => z.ZodType | undefined;
|
|
24
|
+
};
|
|
25
|
+
/** The stream a processor reduces. `read` answers durable rows plus the proof: `scannedThroughOffset`
|
|
26
|
+
* is how far the read is CONTIGUOUSLY known (never past the durable mark — stream.ts), and `atHead`
|
|
27
|
+
* says whether the page was cut; its length says nothing (a budget cut is short of `limit`). */
|
|
28
|
+
export type ProcessorStream = {
|
|
29
|
+
append(...events: StreamEventInput[]): Promise<StreamEvent[]> | StreamEvent[];
|
|
30
|
+
/** Append onto ANOTHER context of the same project, by path — how an entity's processor lands its
|
|
31
|
+
* birth certificate on `/` for the project catalog. A stand-in with one path may omit it. */
|
|
32
|
+
appendTo?(path: string, ...events: StreamEventInput[]): Promise<StreamEvent[]> | StreamEvent[];
|
|
33
|
+
read(afterOffset?: number, limit?: number): Promise<{
|
|
34
|
+
events: StreamEvent[];
|
|
35
|
+
scannedThroughOffset: number;
|
|
36
|
+
atHead: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
/** This processor's claim on its context's alarm: "come back by `at`" (the context's alarm pass
|
|
39
|
+
* then calls `revive()`), or `null` to release it. Durable on the context, never a log event. */
|
|
40
|
+
claim(at: number | null): Promise<unknown>;
|
|
41
|
+
};
|
|
42
|
+
/** How long after an attempt starts a dead host is revived: the recovery bound. A revive that finds
|
|
43
|
+
* the attempt still in flight claims again with the delay doubled, up to `REVIVE_AFTER_MAX_MS`. */
|
|
44
|
+
export declare const REVIVE_AFTER_MS = 20000;
|
|
45
|
+
export declare const REVIVE_AFTER_MAX_MS: number;
|
|
46
|
+
/** The contiguity proof a delivery carries: the half-open offset window `(after, through]`. A chain
|
|
47
|
+
* of these (each `after` === the previous `through`) is how a subscriber proves it missed nothing. */
|
|
48
|
+
export type ScannedRange = {
|
|
49
|
+
after: number;
|
|
50
|
+
through: number;
|
|
51
|
+
};
|
|
52
|
+
export type ReduceArgs<State, Event = StreamEvent> = {
|
|
53
|
+
event: Event;
|
|
54
|
+
state: State;
|
|
55
|
+
};
|
|
56
|
+
export type ProcessEventArgs<State, Event = StreamEvent,
|
|
57
|
+
/** What `append`/`appendTo` take: `EmittedEventInput<typeof Contract>` for a processor that
|
|
58
|
+
* declares one — each type the contract `emits`, its payload as the catalog spells it. */
|
|
59
|
+
Emitted extends StreamEventInput = StreamEventInput> = {
|
|
60
|
+
/** The consumed event — or `null` for the eventless at-head pass. */
|
|
61
|
+
event: Event | null;
|
|
62
|
+
state: State;
|
|
63
|
+
previousState: State;
|
|
64
|
+
/** Emit (validated against `emits`, provenance-stamped) onto this processor's own stream. Declared
|
|
65
|
+
* as METHODS (not arrow-typed properties) on purpose: a subclass that narrows `Emitted` must stay
|
|
66
|
+
* assignable to `StreamProcessor<State>` (the host's field), and only method parameters are
|
|
67
|
+
* compared bivariantly. */
|
|
68
|
+
append(...events: Emitted[]): Promise<StreamEvent[]>;
|
|
69
|
+
/** The same, onto the context at `path` (apps/os's `appendTo`): a certificate cross-posted to `/`. */
|
|
70
|
+
appendTo(path: string, ...events: Emitted[]): Promise<StreamEvent[]>;
|
|
71
|
+
/** Hold the cursor until `work` settles; FIFO with other blockers of the SAME event. */
|
|
72
|
+
blockProcessorWhile: (work: () => Promise<unknown>) => void;
|
|
73
|
+
/** Fire-and-forget attempt; may overtake later events; outcome must be state-recoverable. */
|
|
74
|
+
runInBackground: (work: () => Promise<unknown>) => void;
|
|
75
|
+
delivery: {
|
|
76
|
+
caughtUp: boolean;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
/** THE ONE consumes rule — the processor engine, the subscription delivery loop, and the inline
|
|
80
|
+
* reduces all call this; there is no second copy to drift. `consumes` undefined = every durable event
|
|
81
|
+
* (a subscriber's default). "*" = every durable event. A NAMED type opts that type in, INCLUDING
|
|
82
|
+
* ephemerals ("*" NEVER sweeps ephemerals) — so a live-state watcher spells
|
|
83
|
+
* `consumes: ["events.iterate.com/live-state/changed"]` and filters `payload.key` itself. The wake
|
|
84
|
+
* record (`stream/woken`) is a durable event like any other: a "*" row receives every incarnation's. */
|
|
85
|
+
export declare function consumesEvent(consumes: readonly string[] | undefined, event: {
|
|
86
|
+
type: string;
|
|
87
|
+
ephemeral?: boolean;
|
|
88
|
+
}): boolean;
|
|
89
|
+
/** THE AUTHOR CLASS: a contract, three hooks and one helper. Deps an effect needs arrive through
|
|
90
|
+
* the subclass's own constructor, as for any class. One instance lives as long as its host; a field
|
|
91
|
+
* on it is RUNTIME state (gone with the host), which `projectLiveState` may reduce into the live view. */
|
|
92
|
+
export declare abstract class StreamProcessor<State, Event extends StreamEvent = StreamEvent> {
|
|
93
|
+
abstract readonly contract: ProcessorContract<State>;
|
|
94
|
+
/** Pure reduce. Return the NEXT state (a new object) — or null/undefined to keep the current. The
|
|
95
|
+
* `Event` type param — a discriminated union of the events the contract consumes — narrows
|
|
96
|
+
* `event.payload` per `event.type` inside the body, so no cast is needed; it defaults to the
|
|
97
|
+
* untyped `StreamEvent` for processors that don't declare one. */
|
|
98
|
+
reduce(_args: ReduceArgs<State, Event>): State | null | undefined;
|
|
99
|
+
/** Side-effect hook. Synchronous by design: register async work via the two helpers on args.
|
|
100
|
+
* `append`/`appendTo` take what THIS class's `contract` emits (`EmittedEventInput<this["contract"]>`:
|
|
101
|
+
* a subclass whose `contract` is a defined one gets each emitted type's payload as its catalog
|
|
102
|
+
* spells it; the base, and a hand-built contract, take any input). */
|
|
103
|
+
processEvent(_args: ProcessEventArgs<State, Event, EmittedEventInput<this["contract"]>>): undefined;
|
|
104
|
+
/** The live-state PROJECTION — the shape clients see and the diffs are computed over. DEFAULT: the
|
|
105
|
+
* reduced state verbatim, so every processor is live out of the box; that is deliberate — the
|
|
106
|
+
* delta is an EPHEMERAL event, so "always live" costs an offset and a cheap diff, nothing durable.
|
|
107
|
+
* Override to redact, or to REDUCE IN RUNTIME FIELDS (`return { ...state, lastSeenMs: this.lastSeenMs }`);
|
|
108
|
+
* the engine re-projects after EVERY batch, and a field changed outside a batch needs the host's
|
|
109
|
+
* `publishLiveState()`. */
|
|
110
|
+
projectLiveState(state: State): unknown;
|
|
111
|
+
/** Stable idempotency key namespaced by slug; pass the event being processed for a per-event key. */
|
|
112
|
+
idempotencyKey(key: string, event?: StreamEvent): string;
|
|
113
|
+
}
|
|
114
|
+
/** THE ENGINE: everything below the author's three hooks — the serial chain, the checkpoint, gap
|
|
115
|
+
* repair, the at-head pass, version re-reduces, live-state publishing. Constructed by the host
|
|
116
|
+
* (`StreamProcessorDurableObject`; a test with the stand-ins in test-support.ts). */
|
|
117
|
+
export declare class ProcessorEngine<State> {
|
|
118
|
+
#private;
|
|
119
|
+
readonly processor: StreamProcessor<State>;
|
|
120
|
+
constructor(processor: StreamProcessor<State>, deps: {
|
|
121
|
+
stream: ProcessorStream;
|
|
122
|
+
storage: ReduceCheckpointTable;
|
|
123
|
+
});
|
|
124
|
+
/** THE SEED DOOR for live-state clients (LiveState.snapshot), caught up first. */
|
|
125
|
+
liveSnapshot(): Promise<{
|
|
126
|
+
rev: number;
|
|
127
|
+
state: unknown;
|
|
128
|
+
}>;
|
|
129
|
+
/** Emit a delta for the CURRENT projection (reduced + any runtime fields) if it changed. The engine
|
|
130
|
+
* calls this after every batch; the host calls it after a runtime field moved outside a batch. A
|
|
131
|
+
* throwing projection loses only its notification (the client re-seeds on the chain gap). */
|
|
132
|
+
publishLiveState(): void;
|
|
133
|
+
/** THE push door: contiguous → reduce it directly (no read); anything else → gap repair from the
|
|
134
|
+
* own cursor first. Fire-and-forget safe: enqueues on the serial chain. */
|
|
135
|
+
processEventBatch(events: StreamEvent[], range: ScannedRange): Promise<void>;
|
|
136
|
+
/** Catch up from the own checkpoint (a cold boot, the read verbs, the barrier), page by page — a
|
|
137
|
+
* failed batch, a missed push, or a fresh incarnation can never skip a durable event. */
|
|
138
|
+
catchUpFromLog(): Promise<void>;
|
|
139
|
+
/** Reduce-and-effects caught up through the log, then `{ offset, state }`. */
|
|
140
|
+
snapshot(): Promise<{
|
|
141
|
+
offset: number;
|
|
142
|
+
state: State;
|
|
143
|
+
}>;
|
|
144
|
+
/** THE barrier verb (read-your-writes): resolves once processed AT LEAST through `offset`. An
|
|
145
|
+
* offset ABOVE the durable mark (an ephemeral's) is reached only if this processor was pushed it —
|
|
146
|
+
* the log cannot prove past the mark, so a wake alone never advances there. */
|
|
147
|
+
waitUntilProcessed(input: {
|
|
148
|
+
offset: number;
|
|
149
|
+
timeoutMs?: number;
|
|
150
|
+
}): Promise<void>;
|
|
151
|
+
/** THE REVIVE — the context's alarm pass calls this for a due claim (spent by then): catch up
|
|
152
|
+
* from the log and run the at-head pass, so a processor restarts what state says is still owed
|
|
153
|
+
* (rule 3). A fresh incarnation finds nothing in flight and starts it; an attempt still in flight
|
|
154
|
+
* here claims again, later each time (20 s, 40 s, … `REVIVE_AFTER_MAX_MS`). */
|
|
155
|
+
revive(): Promise<void>;
|
|
156
|
+
}
|
|
157
|
+
export { jsonEqual };
|
|
158
|
+
/** What `append` accepts: the event body, before the stream assigns its committed identity. The
|
|
159
|
+
* door checks ONE rule by hand: `type` is a non-empty string. */
|
|
160
|
+
export type StreamEventInput = {
|
|
161
|
+
/** Convention: `events.iterate.com/<domain>/<fact>`. */
|
|
162
|
+
type: string;
|
|
163
|
+
payload?: Record<string, unknown>;
|
|
164
|
+
metadata?: Record<string, unknown>;
|
|
165
|
+
/** Provenance: which processor (while processing what) appended this — stamped by the engine's
|
|
166
|
+
* `append` — and WHO: the session's verified principal (src/principal.ts), set by the DO's append
|
|
167
|
+
* root from the session's project token and never taken from a client. */
|
|
168
|
+
source?: {
|
|
169
|
+
/** The durable schedule definition responsible for this occurrence. */
|
|
170
|
+
schedule?: {
|
|
171
|
+
key: string;
|
|
172
|
+
scheduledAtOffset: number;
|
|
173
|
+
at: string;
|
|
174
|
+
/** Attribution of the definition, distinct from the platform writing the occurrence. */
|
|
175
|
+
definedBy?: Omit<NonNullable<StreamEventInput["source"]>, "schedule">;
|
|
176
|
+
};
|
|
177
|
+
processor?: {
|
|
178
|
+
slug: string;
|
|
179
|
+
version: string;
|
|
180
|
+
whileProcessing?: {
|
|
181
|
+
offset: number;
|
|
182
|
+
type: string;
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
principal?: {
|
|
186
|
+
actor: string;
|
|
187
|
+
email?: string;
|
|
188
|
+
};
|
|
189
|
+
/** THE CONNECTION the principal acted through: the OAuth grant's id — one per
|
|
190
|
+
* connected client (a Claude Code install, a dash sign-in, a personal token). Stamped beside
|
|
191
|
+
* `principal` by the DO's append root (src/principal.ts); absent for the admin secret and the kernel. */
|
|
192
|
+
grant?: string;
|
|
193
|
+
};
|
|
194
|
+
/** Same key + same body = dedupe (the existing event is returned); different body = loud error. */
|
|
195
|
+
idempotencyKey?: string;
|
|
196
|
+
/** OPTIONAL PRECONDITION (apps/os): land at exactly this offset or refuse the whole batch with
|
|
197
|
+
* OFFSET_CONFLICT — "nothing has happened since I last looked". Never stored in the body. */
|
|
198
|
+
offset?: number;
|
|
199
|
+
/** An EPHEMERAL event rides the stream to live subscribers but is NEVER persisted: it consumes an
|
|
200
|
+
* offset, triggers zero writes, and its body is gone the moment the incarnation ends — nobody can
|
|
201
|
+
* redeliver it (stream.ts, the zero-write contract). A durable OMITS the field. */
|
|
202
|
+
ephemeral?: true;
|
|
203
|
+
};
|
|
204
|
+
/** A committed event: the input plus the identity the stream assigned at its commit point. */
|
|
205
|
+
export type StreamEvent = Omit<StreamEventInput, "offset"> & {
|
|
206
|
+
offset: number;
|
|
207
|
+
createdAt: string;
|
|
208
|
+
path: string;
|
|
209
|
+
};
|
|
210
|
+
export declare function idempotencyConflictMessage(idempotencyKey: string, existingOffset: number): string;
|
|
211
|
+
/** Structural equality of the parts an idempotent retry must not change. */
|
|
212
|
+
export declare function sameIdempotentEvent(existingEvent: StreamEventInput, requestedEvent: StreamEventInput): boolean;
|
|
213
|
+
/** Sync SQLite as the platform hands it over (`ctx.storage.sql`): a query is a LAZY cursor —
|
|
214
|
+
* iterate it, or `toArray()`. Spelled structurally so a node:sqlite stand-in satisfies it. */
|
|
215
|
+
export type SqlStorageHandle = {
|
|
216
|
+
exec<T extends Record<string, SqlStorageValue>>(query: string, ...bindings: unknown[]): Iterable<T> & {
|
|
217
|
+
toArray(): T[];
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
/** A persisted checkpoint as read back: the version it was reduced under (the caller gates on it),
|
|
221
|
+
* the offset reduced through, and the state — `undefined` when the reduce never changed it. */
|
|
222
|
+
export type ReduceCheckpoint<State> = {
|
|
223
|
+
reducerVersion: string;
|
|
224
|
+
reducedThroughOffset: number;
|
|
225
|
+
state: State | undefined;
|
|
226
|
+
};
|
|
227
|
+
/** What BOTH hosts read and write their checkpoints through — the stream's storage and a facet's
|
|
228
|
+
* own (the unit lane drives it over node:sqlite, stream/test-support.ts). */
|
|
229
|
+
export declare class ReduceCheckpointTable {
|
|
230
|
+
#private;
|
|
231
|
+
/** `createTable: false` when the caller knows the table exists (the stream's storage skips every
|
|
232
|
+
* CREATE on a re-wake); a facet host constructs one per incarnation and lets it create. */
|
|
233
|
+
constructor(sql: SqlStorageHandle, options?: {
|
|
234
|
+
createTable: boolean;
|
|
235
|
+
});
|
|
236
|
+
static createTable(sql: SqlStorageHandle): void;
|
|
237
|
+
read<State>(slug: string): ReduceCheckpoint<State> | undefined;
|
|
238
|
+
/** ALWAYS the cursor; the state ONLY when `stateChanged` — one write either way. */
|
|
239
|
+
write<State>(slug: string, cursor: {
|
|
240
|
+
reducerVersion: string;
|
|
241
|
+
reducedThroughOffset: number;
|
|
242
|
+
}, state: State, stateChanged: boolean): void;
|
|
243
|
+
}
|
|
244
|
+
/** The only thing a LiveState needs from its host: somewhere to append the delta. A
|
|
245
|
+
* `ProcessorStream` and the itx scope both satisfy it. */
|
|
246
|
+
export type LiveStateSink = {
|
|
247
|
+
append(event: {
|
|
248
|
+
type: string;
|
|
249
|
+
ephemeral?: true;
|
|
250
|
+
payload?: Record<string, unknown>;
|
|
251
|
+
}): unknown;
|
|
252
|
+
};
|
|
253
|
+
export declare class LiveState<S> {
|
|
254
|
+
#private;
|
|
255
|
+
constructor(sink: LiveStateSink, key: string, initial: S);
|
|
256
|
+
/** The current value (reflects every `set`). */
|
|
257
|
+
get(): S;
|
|
258
|
+
/** THE seed door: `{rev, state}` read together (single-threaded ⇒ atomically), which is what lets
|
|
259
|
+
* a client chain patches exactly instead of guessing which changes its snapshot already contains. */
|
|
260
|
+
snapshot(): {
|
|
261
|
+
rev: number;
|
|
262
|
+
state: S;
|
|
263
|
+
};
|
|
264
|
+
/** Replace the value: diff the last serialized base → next; on a real change bump the revision
|
|
265
|
+
* and append the delta. Build a NEW value (don't mutate `next` in place) — the diff is over JSON.
|
|
266
|
+
* A diff/append failure degrades to a LOST notification (the client re-seeds on the chain gap),
|
|
267
|
+
* never a throw the caller sees. */
|
|
268
|
+
set(next: S): void;
|
|
269
|
+
}
|
|
270
|
+
/** One owned event: its description and the zod schema for its payload. `ephemeral: true` marks a
|
|
271
|
+
* non-durable event (delivered only when its type is named in `consumes`). */
|
|
272
|
+
export type EventDefinition = {
|
|
273
|
+
description: string;
|
|
274
|
+
payloadSchema: z.ZodType;
|
|
275
|
+
ephemeral?: true;
|
|
276
|
+
};
|
|
277
|
+
/** A durable event type string → its definition. */
|
|
278
|
+
export type EventCatalog = Record<string, EventDefinition>;
|
|
279
|
+
/** A `processorDeps` entry's own event catalog. */
|
|
280
|
+
type DepCatalog<Dep> = Dep extends {
|
|
281
|
+
events: infer Events extends EventCatalog;
|
|
282
|
+
} ? Events : never;
|
|
283
|
+
/** The definition owning `Type` — local events win, then each dep. */
|
|
284
|
+
type DefinitionForType<Events extends EventCatalog, Deps extends readonly unknown[], Type extends string> = Type extends keyof Events ? Events[Type] : Deps[number] extends infer Dep ? Dep extends unknown ? Type extends keyof DepCatalog<Dep> ? DepCatalog<Dep>[Type] : never : never : never;
|
|
285
|
+
/** The committed event for one resolved type: `StreamEvent` narrowed to its `{ type, payload }`. */
|
|
286
|
+
type EventForType<Events extends EventCatalog, Deps extends readonly unknown[], Type extends string> = Type extends unknown ? DefinitionForType<Events, Deps, Type> extends {
|
|
287
|
+
payloadSchema: infer Schema extends z.ZodType;
|
|
288
|
+
} ? StreamEvent & {
|
|
289
|
+
type: Type;
|
|
290
|
+
payload: z.output<Schema>;
|
|
291
|
+
} : never : never;
|
|
292
|
+
/** The reduce union for a `consumes` tuple — `"*"` alone means any `StreamEvent`. */
|
|
293
|
+
type EventForTypes<Events extends EventCatalog, Deps extends readonly unknown[], Types extends readonly string[]> = "*" extends Types[number] ? StreamEvent : EventForType<Events, Deps, Types[number]>;
|
|
294
|
+
/** A contract's `processorDeps` tuple, defaulting to empty. */
|
|
295
|
+
type DepsOf<Contract> = Contract extends {
|
|
296
|
+
processorDeps: infer Deps extends readonly unknown[];
|
|
297
|
+
} ? Deps : readonly [];
|
|
298
|
+
/** A contract's reduced-state type, inferred from its `stateSchema`. */
|
|
299
|
+
export type ProcessorState<Contract> = Contract extends {
|
|
300
|
+
stateSchema: infer Schema extends z.ZodType;
|
|
301
|
+
} ? z.output<Schema> : never;
|
|
302
|
+
/** The committed-event union a contract's `consumes` list can deliver to `reduce`/`processEvent`. */
|
|
303
|
+
export type ConsumedEvent<Contract> = Contract extends {
|
|
304
|
+
events: infer Events extends EventCatalog;
|
|
305
|
+
consumes: infer Consumes extends readonly string[];
|
|
306
|
+
} ? EventForTypes<Events, DepsOf<Contract>, Consumes> : never;
|
|
307
|
+
/** The input for ONE event type as a catalog spells it (`EventInput`'s row) — or, for a type no
|
|
308
|
+
* catalog defines (a core control event a processor emits, `project/ingress-configured`), the plain
|
|
309
|
+
* input: it widens the whole union, so a contract that emits one undefined type appends untyped
|
|
310
|
+
* until that type is in a catalog it depends on. */
|
|
311
|
+
type EventInputForType<Events extends EventCatalog, Deps extends readonly unknown[], Type extends string> = Type extends unknown ? [DefinitionForType<Events, Deps, Type>] extends [never] ? StreamEventInput : DefinitionForType<Events, Deps, Type> extends {
|
|
312
|
+
payloadSchema: infer Schema extends z.ZodType;
|
|
313
|
+
} ? {
|
|
314
|
+
type: Type;
|
|
315
|
+
payload: z.input<Schema>;
|
|
316
|
+
idempotencyKey?: string;
|
|
317
|
+
metadata?: Record<string, unknown>;
|
|
318
|
+
} & (DefinitionForType<Events, Deps, Type> extends {
|
|
319
|
+
ephemeral: true;
|
|
320
|
+
} ? {
|
|
321
|
+
ephemeral: true;
|
|
322
|
+
} : {
|
|
323
|
+
ephemeral?: never;
|
|
324
|
+
}) : never : never;
|
|
325
|
+
/** What a processor's `append`/`appendTo` take: one input per type the contract `emits` — its own
|
|
326
|
+
* events and its deps' as their catalogs spell them (`z.input`), a type no catalog defines as the
|
|
327
|
+
* plain input under that name. A contract whose `emits` is not a literal tuple gets every input. */
|
|
328
|
+
export type EmittedEventInput<Contract> = Contract extends {
|
|
329
|
+
events: infer Events extends EventCatalog;
|
|
330
|
+
emits: infer Emits extends readonly string[];
|
|
331
|
+
} ? string[] extends Emits ? StreamEventInput : Emits extends readonly [] ? StreamEventInput : EventInputForType<Events, DepsOf<Contract>, Emits[number]> : StreamEventInput;
|
|
332
|
+
/** What a caller APPENDS for one of a contract's OWNED events — the typed write on an entity
|
|
333
|
+
* (`itx.agents.get(path).append(…)`, library.ts): the type string, the payload as its schema takes
|
|
334
|
+
* it (`z.input`), a key and metadata; `ephemeral` only where the definition says so. Derived from
|
|
335
|
+
* the catalog, so a payload field renamed in the contract is a type error at every call site. */
|
|
336
|
+
export type EventInput<Contract> = Contract extends {
|
|
337
|
+
events: infer Events extends EventCatalog;
|
|
338
|
+
} ? {
|
|
339
|
+
[Type in keyof Events & string]: {
|
|
340
|
+
type: Type;
|
|
341
|
+
payload: z.input<Events[Type]["payloadSchema"]>;
|
|
342
|
+
idempotencyKey?: string;
|
|
343
|
+
metadata?: Record<string, unknown>;
|
|
344
|
+
} & (Events[Type] extends {
|
|
345
|
+
ephemeral: true;
|
|
346
|
+
} ? {
|
|
347
|
+
ephemeral: true;
|
|
348
|
+
} : {
|
|
349
|
+
ephemeral?: never;
|
|
350
|
+
});
|
|
351
|
+
}[keyof Events & string] : never;
|
|
352
|
+
/** What `defineProcessorContract` returns: the base the engine reads, plus the events catalog and the
|
|
353
|
+
* resolved deps. (Events are written LITERALLY at the call site — `itx.append({ type, payload })` —
|
|
354
|
+
* so there is no event-builder here; the engine validates the payload against `payloadSchemaFor` at
|
|
355
|
+
* reduce, and `ConsumedEvent`/`ProcessorState` give the reduce its types.) */
|
|
356
|
+
export type DefinedProcessorContract<StateSchema extends z.ZodType, Events extends EventCatalog, Consumes extends readonly string[], Deps extends readonly unknown[], Emits extends readonly string[] = readonly string[]> = ProcessorContract<z.output<StateSchema>> & {
|
|
357
|
+
stateSchema: StateSchema;
|
|
358
|
+
events: Events;
|
|
359
|
+
consumes: Consumes;
|
|
360
|
+
emits: Emits;
|
|
361
|
+
processorDeps: Deps;
|
|
362
|
+
};
|
|
363
|
+
export declare function defineProcessorContract<const StateSchema extends z.ZodType, const Events extends EventCatalog = Record<string, never>, const Consumes extends readonly string[] = readonly string[], const Deps extends readonly {
|
|
364
|
+
events: EventCatalog;
|
|
365
|
+
}[] = readonly [], const Emits extends readonly string[] = readonly string[]>(contract: {
|
|
366
|
+
slug: string;
|
|
367
|
+
version: string;
|
|
368
|
+
description: string;
|
|
369
|
+
/** Must parse `{}` — the initial state is `stateSchema.parse({})` (all fields defaulted). */
|
|
370
|
+
stateSchema: StateSchema;
|
|
371
|
+
/** The events this contract OWNS, keyed by durable type string. Omit for a kernel-generic
|
|
372
|
+
* processor that types its own reduce through the `Event` param instead of an events catalog. */
|
|
373
|
+
events?: Events;
|
|
374
|
+
/** Other processors' contracts whose events this one may `consumes`/`emits` without owning. */
|
|
375
|
+
processorDeps?: Deps;
|
|
376
|
+
consumes: Consumes;
|
|
377
|
+
emits: Emits;
|
|
378
|
+
}): DefinedProcessorContract<StateSchema, Events, Consumes, Deps, Emits>;
|