iterate 0.3.0 → 0.4.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 +164 -82
- package/dist/{next/api.d.ts → api.d.ts} +197 -33
- package/dist/{next/app-server.d.ts → app-server.d.ts} +7 -0
- package/dist/{next/app-server.mjs → app-server.mjs} +17 -15
- package/dist/app-server.mjs.map +1 -0
- package/dist/{next/app-session.mjs → app-session.mjs} +12 -15
- package/dist/app-session.mjs.map +1 -0
- package/dist/{next/app.mjs → app.mjs} +53 -14
- package/dist/app.mjs.map +1 -0
- package/dist/{next/client → client}/live-state.d.ts +11 -11
- package/dist/client/oauth.d.ts +17 -0
- package/dist/{next/client → client}/react.d.ts +10 -42
- package/dist/{next/client → client}/socket.d.ts +1 -0
- package/dist/client.mjs +156 -4
- package/dist/client.mjs.map +1 -0
- package/dist/{next/expression.d.ts → expression.d.ts} +11 -69
- package/dist/{next/expression.mjs → expression.mjs} +11 -109
- package/dist/expression.mjs.map +1 -0
- package/dist/lib-BWr-5mFO.mjs +36 -0
- package/dist/lib-BWr-5mFO.mjs.map +1 -0
- package/dist/{next/lib.d.ts → lib.d.ts} +16 -2
- package/dist/{next/lib.mjs → lib.mjs} +32 -3
- package/dist/lib.mjs.map +1 -0
- package/dist/node.d.ts +15 -3
- package/dist/node.mjs +36 -174
- package/dist/node.mjs.map +1 -1
- package/dist/{next/oauth-scopes.mjs → oauth-scopes.mjs} +1 -1
- package/dist/oauth-scopes.mjs.map +1 -0
- package/dist/{next/oauth.mjs → oauth.mjs} +14 -2
- package/dist/oauth.mjs.map +1 -0
- package/dist/principal.d.ts +8 -0
- package/dist/principal.mjs +8 -0
- package/dist/principal.mjs.map +1 -0
- package/dist/project-ingress.d.ts +58 -0
- package/dist/project-ingress.mjs +104 -0
- package/dist/project-ingress.mjs.map +1 -0
- package/dist/{next/react.mjs → react.mjs} +12 -12
- package/dist/react.mjs.map +1 -0
- package/dist/sdk/auth.d.ts +25 -0
- package/dist/sdk/index.d.ts +155 -0
- package/dist/sdk/record-pipelined-steps.d.ts +19 -0
- package/dist/sdk.mjs +245 -2
- package/dist/sdk.mjs.map +1 -0
- package/dist/{next/stream → stream}/processor.d.ts +28 -23
- package/dist/{next/stream → stream}/processor.mjs +48 -25
- package/dist/stream/processor.mjs.map +1 -0
- package/dist/{next/stream → stream}/run.d.ts +9 -6
- package/dist/{next/stream → stream}/run.mjs +13 -8
- package/dist/stream/run.mjs.map +1 -0
- package/dist/stream/test-support.d.ts +45 -0
- package/dist/stream/test-support.mjs +196 -0
- package/dist/stream/test-support.mjs.map +1 -0
- package/package.json +65 -219
- package/THIRD_PARTY_NOTICES.md +0 -55
- package/bin/iterate.js +0 -94
- package/dist/api-url-B6404M82.mjs +0 -17
- package/dist/api-url-B6404M82.mjs.map +0 -1
- package/dist/app-ref-BipL0feU.mjs +0 -35
- package/dist/app-ref-BipL0feU.mjs.map +0 -1
- package/dist/app-ref-C1CrgXqX.mjs +0 -7
- package/dist/app-ref-C1CrgXqX.mjs.map +0 -1
- package/dist/app-ref-DYai_om1.mjs +0 -7
- package/dist/app-ref-DYai_om1.mjs.map +0 -1
- package/dist/cli-D0c-pDL_.mjs +0 -1010
- package/dist/cli-D0c-pDL_.mjs.map +0 -1
- package/dist/client.d.ts +0 -3
- package/dist/cloudflare-BTm90gQ4.mjs +0 -951
- package/dist/cloudflare-BTm90gQ4.mjs.map +0 -1
- package/dist/contract-s4FW4eES.mjs +0 -309
- package/dist/contract-s4FW4eES.mjs.map +0 -1
- package/dist/document-review/index.d.ts +0 -5
- package/dist/document-review/types.d.ts +0 -107
- package/dist/document-review.mjs +0 -7015
- package/dist/document-review.mjs.map +0 -1
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs +0 -205
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +0 -1
- package/dist/idempotency-DleloJNt.mjs +0 -28
- package/dist/idempotency-DleloJNt.mjs.map +0 -1
- package/dist/index.d.mts +0 -5
- package/dist/index.mjs +0 -8
- package/dist/index.mjs.map +0 -1
- package/dist/itx/api-url.d.ts +0 -6
- package/dist/itx/itx-node-client.d.ts +0 -65
- package/dist/itx/itx-session.d.ts +0 -215
- package/dist/itx/owned-rpc-session.d.ts +0 -14
- package/dist/itx/query-client.d.ts +0 -10
- package/dist/itx-api.generated.d.ts +0 -6195
- package/dist/itx-session-sjud8GiT.mjs +0 -534
- package/dist/itx-session-sjud8GiT.mjs.map +0 -1
- package/dist/live-state-BJNqOwFw.mjs +0 -299
- package/dist/live-state-BJNqOwFw.mjs.map +0 -1
- package/dist/next/app-server.mjs.map +0 -1
- package/dist/next/app-session.mjs.map +0 -1
- package/dist/next/app.mjs.map +0 -1
- package/dist/next/client/oauth.d.ts +0 -12
- package/dist/next/client.mjs +0 -156
- package/dist/next/client.mjs.map +0 -1
- package/dist/next/expression.mjs.map +0 -1
- package/dist/next/lib.mjs.map +0 -1
- package/dist/next/oauth-scopes.mjs.map +0 -1
- package/dist/next/oauth.mjs.map +0 -1
- package/dist/next/principal.d.ts +0 -64
- package/dist/next/principal.mjs +0 -98
- package/dist/next/principal.mjs.map +0 -1
- package/dist/next/project-ingress.d.ts +0 -37
- package/dist/next/project-ingress.mjs +0 -75
- package/dist/next/project-ingress.mjs.map +0 -1
- package/dist/next/react.mjs.map +0 -1
- package/dist/next/sdk/auth.d.ts +0 -5
- package/dist/next/sdk/index.d.ts +0 -112
- package/dist/next/sdk.mjs +0 -139
- package/dist/next/sdk.mjs.map +0 -1
- package/dist/next/stream/processor.mjs.map +0 -1
- package/dist/next/stream/run.mjs.map +0 -1
- package/dist/next-node.d.ts +0 -15
- package/dist/next-node.mjs +0 -51
- package/dist/next-node.mjs.map +0 -1
- package/dist/processor-host-capabilities-BMFH3KTM.mjs +0 -56
- package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +0 -1
- package/dist/processors/cloudflare.d.ts +0 -3
- package/dist/processors/durable-object-processor-durability.d.ts +0 -79
- package/dist/processors/event-consumption-metrics.d.ts +0 -82
- package/dist/processors/idempotency.d.ts +0 -13
- package/dist/processors/index.d.ts +0 -12
- package/dist/processors/processor-contracts.d.ts +0 -342
- package/dist/processors/processor-facet.d.ts +0 -186
- package/dist/processors/processor-host-capabilities.d.ts +0 -60
- package/dist/processors/prompt-sections.d.ts +0 -17
- package/dist/processors/rpc-types.d.ts +0 -515
- package/dist/processors/schemas.d.ts +0 -102
- package/dist/processors/stream-handle.d.ts +0 -45
- package/dist/processors/stream-processor-keepalive.d.ts +0 -95
- package/dist/processors/stream-processor-registry.d.ts +0 -233
- package/dist/processors/stream-processor-runner.d.ts +0 -289
- package/dist/processors/stream-processor.d.ts +0 -339
- package/dist/processors/stream-runtime-metrics.d.ts +0 -107
- package/dist/processors/testing.d.ts +0 -302
- package/dist/processors-BoNyeBfQ.mjs +0 -10
- package/dist/processors-BoNyeBfQ.mjs.map +0 -1
- package/dist/processors-cloudflare.mjs +0 -3
- package/dist/processors-testing.mjs +0 -435
- package/dist/processors-testing.mjs.map +0 -1
- package/dist/processors.mjs +0 -52
- package/dist/processors.mjs.map +0 -1
- package/dist/protocol-DnK_f2m6.mjs +0 -251
- package/dist/protocol-DnK_f2m6.mjs.map +0 -1
- package/dist/sdk/capnweb/index.d.ts +0 -2
- package/dist/sdk/capnweb/live-state/compact.d.ts +0 -5
- package/dist/sdk/capnweb/live-state/diff.d.ts +0 -41
- package/dist/sdk/capnweb/live-state/engine.d.ts +0 -44
- package/dist/sdk/capnweb/live-state/index.d.ts +0 -41
- package/dist/sdk/capnweb/live-state/protocol.d.ts +0 -87
- package/dist/sdk/capnweb/live-state/retain.d.ts +0 -23
- package/dist/sdk/capnweb/live-state/store.d.ts +0 -20
- package/dist/sdk/capnweb/live-state/types.d.ts +0 -11
- package/dist/sdk/capnweb/react.d.ts +0 -45
- package/dist/sdk/capnweb/react.mjs +0 -316
- package/dist/sdk/capnweb/react.mjs.map +0 -1
- package/dist/sdk/capnweb.mjs +0 -4
- package/dist/sdk/itx/react.d.ts +0 -191
- package/dist/sdk/itx/react.mjs +0 -383
- package/dist/sdk/itx/react.mjs.map +0 -1
- package/dist/sdk-DMB-IM11.mjs +0 -933
- package/dist/sdk-DMB-IM11.mjs.map +0 -1
- package/dist/sdk.d.ts +0 -339
- package/dist/serve-itx.d.ts +0 -46
- package/dist/starter-apps/flake-dashboard/app-ref.d.ts +0 -31
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs +0 -1055
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/flake-dashboard/contract.d.ts +0 -4839
- package/dist/starter-apps/flake-dashboard/contract.mjs +0 -2
- package/dist/starter-apps/flake-dashboard/index.d.ts +0 -17
- package/dist/starter-apps/flake-dashboard/index.mjs +0 -56
- package/dist/starter-apps/flake-dashboard/index.mjs.map +0 -1
- package/dist/starter-apps/flake-dashboard/worker.d.ts +0 -4607
- package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +0 -8914
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs +0 -17987
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/github-ai-linter/contract.d.ts +0 -9193
- package/dist/starter-apps/github-ai-linter/index.d.ts +0 -10
- package/dist/starter-apps/github-ai-linter/index.mjs +0 -36
- package/dist/starter-apps/github-ai-linter/index.mjs.map +0 -1
- package/dist/starter-apps/github-ai-linter/prompt.d.ts +0 -13
- package/dist/starter-apps/github-ai-linter/review-bot.d.ts +0 -808
- package/dist/starter-apps/github-ai-linter/rules.d.ts +0 -34
- package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +0 -19
- package/dist/starter-apps/github-ai-linter/worker.d.ts +0 -19
- package/dist/starter-apps/github-ai-linter/worker.mjs +0 -947
- package/dist/starter-apps/github-ai-linter/worker.mjs.map +0 -1
- package/dist/starter-apps/guestbook/app-ref.d.ts +0 -27
- package/dist/starter-apps/guestbook/client.d.ts +0 -7
- package/dist/starter-apps/guestbook/client.mjs +0 -59
- package/dist/starter-apps/guestbook/configured-worker.mjs +0 -205
- package/dist/starter-apps/guestbook/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/guestbook/index.d.ts +0 -9
- package/dist/starter-apps/guestbook/index.mjs +0 -31
- package/dist/starter-apps/guestbook/index.mjs.map +0 -1
- package/dist/starter-apps/guestbook/processor.d.ts +0 -2267
- package/dist/starter-apps/guestbook/worker.d.ts +0 -26
- package/dist/starter-apps/guestbook/worker.mjs +0 -191
- package/dist/starter-apps/guestbook/worker.mjs.map +0 -1
- package/dist/starter-apps/media/configured-worker.mjs +0 -577
- package/dist/starter-apps/media/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/media/index.mjs +0 -36
- package/dist/starter-apps/media/index.mjs.map +0 -1
- package/dist/starter-apps/media/ref.mjs +0 -20
- package/dist/starter-apps/media/ref.mjs.map +0 -1
- package/dist/starter-apps/media/worker.mjs +0 -579
- package/dist/starter-apps/media/worker.mjs.map +0 -1
- package/dist/starter-apps/notes/configured-worker.mjs +0 -6134
- package/dist/starter-apps/notes/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/notes/index.mjs +0 -23
- package/dist/starter-apps/notes/index.mjs.map +0 -1
- package/dist/starter-apps/notes/ref.mjs +0 -21
- package/dist/starter-apps/notes/ref.mjs.map +0 -1
- package/dist/starter-apps/notes/worker.mjs +0 -427
- package/dist/starter-apps/notes/worker.mjs.map +0 -1
- package/dist/starter-apps/todo/client.mjs +0 -59
- package/dist/starter-apps/todo/configured-worker.mjs +0 -2864
- package/dist/starter-apps/todo/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/todo/index.d.ts +0 -8
- package/dist/starter-apps/todo/index.mjs +0 -29
- package/dist/starter-apps/todo/index.mjs.map +0 -1
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs +0 -2082
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +0 -1
- package/dist/usingCtx-mZx5nsAW.mjs +0 -11800
- package/dist/usingCtx-mZx5nsAW.mjs.map +0 -1
- package/dist/worker-ref-DZxPDmb_.mjs +0 -390
- package/dist/worker-ref-DZxPDmb_.mjs.map +0 -1
- package/dist/worker.d.mts +0 -33
- package/dist/worker.mjs +0 -18
- package/dist/worker.mjs.map +0 -1
- package/menubar/Iterate.entitlements +0 -12
- package/menubar/Iterate.swift +0 -914
- package/menubar/IterateIcon.swift +0 -145
- package/menubar/README.md +0 -28
- package/menubar/build-menubar-app.sh +0 -59
- /package/dist/{next/api.mjs → api.mjs} +0 -0
- /package/dist/{next/app-session.d.ts → app-session.d.ts} +0 -0
- /package/dist/{next/app.d.ts → app.d.ts} +0 -0
- /package/dist/{next/oauth-scopes.d.ts → oauth-scopes.d.ts} +0 -0
package/dist/sdk.mjs
CHANGED
|
@@ -1,2 +1,245 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { applyPatch, diff, isSameOriginBrowserRequest, jsonEqual, releaseRpcSessions } 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/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 `x-itx-principal` for a project member
|
|
14
|
+
* only: a visitor signed out, signed in without this project, or riding a session cookie on a
|
|
15
|
+
* cross-site write arrives without one. This guard runs in the config worker, before it proxies an
|
|
16
|
+
* app.
|
|
17
|
+
*
|
|
18
|
+
* Signed out, every request gets `401` with `WWW-Authenticate: Bearer realm="iterate"`: the
|
|
19
|
+
* platform's edge turns that answer into the sign-in for a page load (or into "sign in again with
|
|
20
|
+
* this project" for someone signed in without it), whatever path the app is served under, and hands
|
|
21
|
+
* a fetch, a write or a WebSocket upgrade the 401 itself. Any app can ask for a signed-in visitor
|
|
22
|
+
* the same way:
|
|
23
|
+
*
|
|
24
|
+
* ```js
|
|
25
|
+
* if (!request.headers.get("x-itx-principal"))
|
|
26
|
+
* return new Response("Sign in\n", { status: 401, headers: { "WWW-Authenticate": 'Bearer realm="iterate"' } });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* A write or a WebSocket upgrade must also come from this origin (or carry no Origin, a non-browser
|
|
30
|
+
* client), else 403. The edge already sends such a cookie request on anonymous; this repeats the
|
|
31
|
+
* check where the app runs. The handshake is a GET, but it opens a two-way channel, and the app
|
|
32
|
+
* session cookie is `SameSite=Lax`: every `<routingSlug>--<project>.iterate.app` host is same-site with
|
|
33
|
+
* every other, so a page on another project's host could otherwise open a socket to this app with
|
|
34
|
+
* the visitor's cookie. */
|
|
35
|
+
const auth = { require(request) {
|
|
36
|
+
const isWebSocketUpgrade = request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
37
|
+
if (!([
|
|
38
|
+
"GET",
|
|
39
|
+
"HEAD",
|
|
40
|
+
"OPTIONS"
|
|
41
|
+
].includes(request.method) && !isWebSocketUpgrade) && !isSameOriginBrowserRequest(request)) return new Response("Cross-site request refused", { status: 403 });
|
|
42
|
+
const principal = request.headers.get(ITX_PRINCIPAL_HEADER);
|
|
43
|
+
if (principal) {
|
|
44
|
+
Principal.parse(JSON.parse(principal));
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return new Response("Sign in\n", {
|
|
48
|
+
status: 401,
|
|
49
|
+
headers: {
|
|
50
|
+
"WWW-Authenticate": "Bearer realm=\"iterate\"",
|
|
51
|
+
"Cache-Control": "no-store"
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
} };
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/sdk/record-pipelined-steps.ts
|
|
57
|
+
/** ONE round trip on `entrypoint.get()`, then RELEASE EVERYTHING IT REACHED: the scope and every call
|
|
58
|
+
* `call` made through it or through a handle it awaited, the last first. A release that throws is reported and the rest still run
|
|
59
|
+
* (lib.ts `releaseRpcSessions`), so the call's answer stands. Data it answers stays usable; a stub or
|
|
60
|
+
* handle it answers is released with the rest, so return data.
|
|
61
|
+
*
|
|
62
|
+
* const { projectSlug } = await withItx(this.env.ITX, (itx) => itx.whoami());
|
|
63
|
+
*/
|
|
64
|
+
async function withItx(entrypoint, call) {
|
|
65
|
+
const steps = [];
|
|
66
|
+
const itx = entrypoint.get();
|
|
67
|
+
try {
|
|
68
|
+
return await call(recordPipelinedSteps(itx, steps));
|
|
69
|
+
} finally {
|
|
70
|
+
releaseRpcSessions([itx, ...steps]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** `stub` as the caller sees it, except that every CALL made through it — at any depth, on the stub,
|
|
74
|
+
* on a call's result, or on the handle a call's result resolves to once awaited — is pushed onto
|
|
75
|
+
* `steps`, so the caller can dispose each one: a Workers-RPC call's result is a stub-bearing promise
|
|
76
|
+
* that keeps its session open until disposed, awaited or not. Awaiting hands back a handle (a stub
|
|
77
|
+
* is callable, in workerd and capnweb alike) recorded and pushed too, and plain data untouched, so
|
|
78
|
+
* data still copies across RPC. `catch`/`finally` and symbol members (`Symbol.dispose`) are the
|
|
79
|
+
* value's own, bound to it, so disposing behaves exactly as on the bare stub; an argument that is
|
|
80
|
+
* itself a recorded value crosses the wire as the stub it wraps. */
|
|
81
|
+
function recordPipelinedSteps(stub, steps) {
|
|
82
|
+
const wrapped = /* @__PURE__ */ new WeakMap();
|
|
83
|
+
const record = (value, receiver) => {
|
|
84
|
+
if (!value || typeof value !== "object" && typeof value !== "function") return value;
|
|
85
|
+
const proxy = new Proxy(value, {
|
|
86
|
+
get(target, key) {
|
|
87
|
+
const member = Reflect.get(target, key);
|
|
88
|
+
if (key === "then" && typeof member === "function") return (onFulfilled, onRejected) => Reflect.apply(member, target, [typeof onFulfilled === "function" ? (answer) => {
|
|
89
|
+
if (typeof answer !== "function") return onFulfilled(answer);
|
|
90
|
+
steps.push(answer);
|
|
91
|
+
return onFulfilled(record(answer, void 0));
|
|
92
|
+
} : onFulfilled, onRejected]);
|
|
93
|
+
if (typeof key === "symbol" || key === "then" || key === "catch" || key === "finally") return typeof member === "function" ? member.bind(target) : member;
|
|
94
|
+
return record(member, target);
|
|
95
|
+
},
|
|
96
|
+
apply(target, _proxyReceiver, args) {
|
|
97
|
+
const result = Reflect.apply(target, receiver, args.map((arg) => wrapped.get(Object(arg)) ?? arg));
|
|
98
|
+
steps.push(result);
|
|
99
|
+
return record(result, void 0);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
wrapped.set(proxy, value);
|
|
103
|
+
return proxy;
|
|
104
|
+
};
|
|
105
|
+
return record(stub, void 0);
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/sdk/index.ts
|
|
109
|
+
/** THE FACET SHELL: a `DurableObject` a context hosts as a facet — `itx.facets.get(name, { source,
|
|
110
|
+
* className })`, a rule naming it, or a processor's row. A caller reaches a facet by itx expression
|
|
111
|
+
* (`itx.facets.get(name).<method>(…)`) only through what its class lists in `publicMethods`: the
|
|
112
|
+
* context refuses any other first step FORBIDDEN before the call reaches the facet
|
|
113
|
+
* (apps/os context/facet-public-methods.ts). The platform's own calls — the delivery loop's push
|
|
114
|
+
* and catch-up, the alarm's revive — never go through the list. A loaded class that does not
|
|
115
|
+
* extend this shell lists nothing, so no caller reaches it by expression. */
|
|
116
|
+
var FacetDurableObject = class extends DurableObject {
|
|
117
|
+
/** What a caller may reach by itx expression: the FIRST step of `itx.facets.get(name).<step>…`, a
|
|
118
|
+
* method or a property of this class. A subclass lists its own on top of its parent's:
|
|
119
|
+
* `static override publicMethods = [...super.publicMethods, "send"]`. */
|
|
120
|
+
static publicMethods = ["fetch"];
|
|
121
|
+
/** This class's `publicMethods`, for the context that loaded it — a static does not cross the
|
|
122
|
+
* isolate. On no list: only the context asks it. */
|
|
123
|
+
listPublicMethods() {
|
|
124
|
+
return this.constructor.publicMethods;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var StreamProcessorDurableObject = class extends FacetDurableObject {
|
|
128
|
+
/** The reads a caller reaches on every processor: `fetch`, and the state caught up through the log
|
|
129
|
+
* (`snapshot`, `liveSnapshot`) or awaited (`waitUntilProcessed`). What feeds the processor —
|
|
130
|
+
* `processEventBatch`, `catchUpFromLog`, `revive` — is the platform's, never a caller's. */
|
|
131
|
+
static publicMethods = [
|
|
132
|
+
...super.publicMethods,
|
|
133
|
+
"snapshot",
|
|
134
|
+
"liveSnapshot",
|
|
135
|
+
"waitUntilProcessed"
|
|
136
|
+
];
|
|
137
|
+
/** After a runtime field on the processor moved OUTSIDE a batch (an RPC method on this object);
|
|
138
|
+
* inside `processEvent` the engine re-projects on its own. */
|
|
139
|
+
publishLiveState() {
|
|
140
|
+
this.#engine.publishLiveState();
|
|
141
|
+
}
|
|
142
|
+
/** THE push: the context hands over each committed batch with its scanned-range proof. */
|
|
143
|
+
processEventBatch(events, range) {
|
|
144
|
+
return this.#engine.processEventBatch(events, range);
|
|
145
|
+
}
|
|
146
|
+
/** Catch up from the log (the delivery loop's, when a row is configured or resumed). */
|
|
147
|
+
catchUpFromLog() {
|
|
148
|
+
return this.#engine.catchUpFromLog();
|
|
149
|
+
}
|
|
150
|
+
/** THE REVIVE: the context's alarm pass calls it for a due claim — catch up, then run the
|
|
151
|
+
* at-head pass, so an attempt the last incarnation was running is started again from state. */
|
|
152
|
+
revive() {
|
|
153
|
+
return this.#engine.revive();
|
|
154
|
+
}
|
|
155
|
+
/** Caught up through the log, then `{ offset, state }`. */
|
|
156
|
+
snapshot() {
|
|
157
|
+
return this.#engine.snapshot();
|
|
158
|
+
}
|
|
159
|
+
/** The live-state seed read: `{ rev, state: projectLiveState(reduced) }`. */
|
|
160
|
+
liveSnapshot() {
|
|
161
|
+
return this.#engine.liveSnapshot();
|
|
162
|
+
}
|
|
163
|
+
/** The barrier: resolves once processed at least through `offset` (default timeout 10s). */
|
|
164
|
+
waitUntilProcessed(input) {
|
|
165
|
+
return this.#engine.waitUntilProcessed(input);
|
|
166
|
+
}
|
|
167
|
+
/** The loopback to this facet's context: a LOADED class gets it as `env.ITX` (the loader bakes the
|
|
168
|
+
* stub in, worker-loader.ts); a class of THIS worker hosted through `ctx.exports` has the
|
|
169
|
+
* worker's real env and mints the same stub itself from its props — `ctx.exports` is populated
|
|
170
|
+
* inside a facet (__workers-tests__/facet-props.test.ts). */
|
|
171
|
+
#itxEntrypoint() {
|
|
172
|
+
return this.env.ITX ?? this.ctx.exports.ItxEntrypoint({ props: {
|
|
173
|
+
iterateContextName: this.ctx.props.iterateContextName,
|
|
174
|
+
platform: true
|
|
175
|
+
} });
|
|
176
|
+
}
|
|
177
|
+
#engineBuiltOnFirstUse;
|
|
178
|
+
get #engine() {
|
|
179
|
+
return this.#engineBuiltOnFirstUse ??= new ProcessorEngine(this.processor, {
|
|
180
|
+
stream: {
|
|
181
|
+
append: (...events) => this.withItx((itx) => itx.append(...events)),
|
|
182
|
+
read: (after, limit) => this.withItx((itx) => itx.readEvents(after, limit)),
|
|
183
|
+
claim: (at) => this.withItx((itx) => itx.processors.claim(this.ctx.props.name, at))
|
|
184
|
+
},
|
|
185
|
+
storage: new ReduceCheckpointTable(this.ctx.storage.sql),
|
|
186
|
+
fedByPushes: this.ctx.props.fedByPushes === true
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/** ONE round trip on the itx scope, then RELEASE EVERYTHING IT REACHED: the get, and every call the
|
|
190
|
+
* callback made through it — not only the last. A Workers-RPC value this facet leaves undisposed —
|
|
191
|
+
* the `itx.cd(path)` of `itx.cd(path).append(…)`, the `cfArtifacts.get(p)` of `.remote()`, an
|
|
192
|
+
* answer awaited inside the callback (`const { state } = await context.invoke(…)`), data included —
|
|
193
|
+
* keeps THIS FACET running after its context is evicted, until V8 collects the value, which an
|
|
194
|
+
* idle isolate may not do for many minutes: each new incarnation of the context reattaches to the
|
|
195
|
+
* facet, and the object stays billed (measured 2026-09-23: a new website project's `/` and
|
|
196
|
+
* `/repos/config` billed 60 s of every minute for 30 min with no request). The context's own
|
|
197
|
+
* `invoke` cannot end this from its side: the facet holds the value (context-residency.e2e.test.ts,
|
|
198
|
+
* "… does not outlive …"). Protected: a host with methods of its own (the workspace,
|
|
199
|
+
* src/workspace/durable-object.ts) reaches its context the same way. */
|
|
200
|
+
withItx(call) {
|
|
201
|
+
return withItx(this.#itxEntrypoint(), call);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var ConfigWorker = class extends WorkerEntrypoint {
|
|
205
|
+
/** At fetch entry: `const denied = this.auth.require(request); if (denied) return denied;`
|
|
206
|
+
* `x-itx-principal` is on a request only when a project member (or the operator) sent it, safe
|
|
207
|
+
* to act on. A private route written by hand answers the platform's sign-in challenge, which
|
|
208
|
+
* the edge turns into the sign-in for a page load (`auth.require` does the same):
|
|
209
|
+
*
|
|
210
|
+
* ```js
|
|
211
|
+
* if (!request.headers.get("x-itx-principal"))
|
|
212
|
+
* return new Response("Sign in\n", { status: 401, headers: { "WWW-Authenticate": 'Bearer realm="iterate"' } });
|
|
213
|
+
* ``` */
|
|
214
|
+
auth = auth;
|
|
215
|
+
/** Process an explicitly subscribed batch with this worker's context scope. */
|
|
216
|
+
async processEventBatch(events, range) {
|
|
217
|
+
await this.withItx(async (itx) => {
|
|
218
|
+
for (const event of events) await this.processEvent({
|
|
219
|
+
event,
|
|
220
|
+
range,
|
|
221
|
+
itx
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/** ONE round trip on the itx scope, then release the scope and every call made through it
|
|
226
|
+
* (`StreamProcessorDurableObject.withItx` says why an undisposed step keeps a context billed). */
|
|
227
|
+
withItx(call) {
|
|
228
|
+
return withItx(this.env.ITX, call);
|
|
229
|
+
}
|
|
230
|
+
/** THE AUTHOR HOOK — one event at a time, in offset order. Append reactions through the itx scope;
|
|
231
|
+
* make them idempotent (a redelivery must be a no-op). Default: ignore the event. */
|
|
232
|
+
processEvent(_args) {}
|
|
233
|
+
/** THE WEB ROOT — every Request on a host of the project (the project's configured ingress
|
|
234
|
+
* target). The host's routing slug is in `x-iterate-routing-slug` (`notes` for
|
|
235
|
+
* `notes--<project>.<hostname>`; absent on the apex), written only by the platform: route on it
|
|
236
|
+
* in plain code, answering here (reaching the context through `this.withItx`) or forwarding the
|
|
237
|
+
* Request. Default: not found. */
|
|
238
|
+
fetch(_request) {
|
|
239
|
+
return new Response("Not found\n", { status: 404 });
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
//#endregion
|
|
243
|
+
export { ConfigWorker, FacetDurableObject, LiveState, RunContract, RunRequested, RunSettled, StreamProcessor, StreamProcessorDurableObject, applyPatch, defineProcessorContract, diff, jsonEqual, newHttpBatchRpcSession, newWebSocketRpcSession, newWorkersRpcResponse, withItx, z };
|
|
244
|
+
|
|
245
|
+
//# sourceMappingURL=sdk.mjs.map
|
package/dist/sdk.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk.mjs","names":["z","#engine","#engineBuiltOnFirstUse","#itxEntrypoint"],"sources":["../src/sdk/auth.ts","../src/sdk/record-pipelined-steps.ts","../src/sdk/index.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { isSameOriginBrowserRequest } from \"../lib.ts\";\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 `x-itx-principal` for a project member\n * only: a visitor signed out, signed in without this project, or riding a session cookie on a\n * cross-site write arrives without one. This guard runs in the config worker, before it proxies an\n * app.\n *\n * Signed out, every request gets `401` with `WWW-Authenticate: Bearer realm=\"iterate\"`: the\n * platform's edge turns that answer into the sign-in for a page load (or into \"sign in again with\n * this project\" for someone signed in without it), whatever path the app is served under, and hands\n * a fetch, a write or a WebSocket upgrade the 401 itself. Any app can ask for a signed-in visitor\n * the same way:\n *\n * ```js\n * if (!request.headers.get(\"x-itx-principal\"))\n * return new Response(\"Sign in\\n\", { status: 401, headers: { \"WWW-Authenticate\": 'Bearer realm=\"iterate\"' } });\n * ```\n *\n * A write or a WebSocket upgrade must also come from this origin (or carry no Origin, a non-browser\n * client), else 403. The edge already sends such a cookie request on anonymous; this repeats the\n * check where the app runs. The handshake is a GET, but it opens a two-way channel, and the app\n * session cookie is `SameSite=Lax`: every `<routingSlug>--<project>.iterate.app` host is same-site with\n * every other, so a page on another project's host could otherwise open a socket to this app with\n * the visitor's cookie. */\nexport const auth = {\n require(request: Request): Response | null {\n const isWebSocketUpgrade = request.headers.get(\"upgrade\")?.toLowerCase() === \"websocket\";\n const isRead = [\"GET\", \"HEAD\", \"OPTIONS\"].includes(request.method) && !isWebSocketUpgrade;\n if (!isRead && !isSameOriginBrowserRequest(request))\n return new Response(\"Cross-site request refused\", { status: 403 });\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 return new Response(\"Sign in\\n\", {\n status: 401,\n headers: { \"WWW-Authenticate\": 'Bearer realm=\"iterate\"', \"Cache-Control\": \"no-store\" },\n });\n },\n};\n","// sdk/record-pipelined-steps.ts — `withItx`, THE one way code reaches its context: ONE round trip on\n// `env.ITX`, then RELEASE a Workers-RPC round trip completely — the scope and every call it made, not\n// only the last. Loaded code imports it from \"./processor.js\" (`withItx(this.env.ITX, (itx) => …)`); the\n// SDK's hosts (`StreamProcessorDurableObject.withItx`, `ConfigWorker.withItx`) delegate to it. No\n// workerd import, so the unit tests run it in node (record-pipelined-steps.test.ts) and the platform\n// bundles it alone for a script's isolate (apps/os `runScriptModule`); on native RpcPromises it is\n// proven by every apps/os e2e row that reaches a facet, and pinned by\n// apps/os/e2e/context-residency.e2e.test.ts (\"… does not outlive …\": a facet that kept one value from\n// its context stayed running, billed). Lint refuses the raw `env.ITX.get()` (iterate/no-raw-itx-get).\n\nimport { releaseRpcSessions } from \"../lib.ts\";\n\n/** ONE round trip on `entrypoint.get()`, then RELEASE EVERYTHING IT REACHED: the scope and every call\n * `call` made through it or through a handle it awaited, the last first. A release that throws is reported and the rest still run\n * (lib.ts `releaseRpcSessions`), so the call's answer stands. Data it answers stays usable; a stub or\n * handle it answers is released with the rest, so return data.\n *\n * const { projectSlug } = await withItx(this.env.ITX, (itx) => itx.whoami());\n */\nexport async function withItx<Scope, T>(\n entrypoint: { get(): Scope },\n call: (itx: Scope) => T,\n): Promise<Awaited<T>> {\n const steps: unknown[] = [];\n const itx = entrypoint.get();\n try {\n return await call(recordPipelinedSteps(itx, steps));\n } finally {\n releaseRpcSessions([itx, ...steps]);\n }\n}\n\n/** `stub` as the caller sees it, except that every CALL made through it — at any depth, on the stub,\n * on a call's result, or on the handle a call's result resolves to once awaited — is pushed onto\n * `steps`, so the caller can dispose each one: a Workers-RPC call's result is a stub-bearing promise\n * that keeps its session open until disposed, awaited or not. Awaiting hands back a handle (a stub\n * is callable, in workerd and capnweb alike) recorded and pushed too, and plain data untouched, so\n * data still copies across RPC. `catch`/`finally` and symbol members (`Symbol.dispose`) are the\n * value's own, bound to it, so disposing behaves exactly as on the bare stub; an argument that is\n * itself a recorded value crosses the wire as the stub it wraps. */\nexport function recordPipelinedSteps<T>(stub: T, steps: unknown[]): T {\n const wrapped = new WeakMap<object, object>();\n const record = (value: unknown, receiver: unknown): unknown => {\n // oxlint-disable-next-line iterate/simple-truthiness-check -- a Proxy target must be an object or a function: a call may answer any value, and only those two can be wrapped\n if (!value || (typeof value !== \"object\" && typeof value !== \"function\")) return value;\n const proxy = new Proxy(value, {\n get(target, key) {\n const member: unknown = Reflect.get(target, key);\n if (key === \"then\" && typeof member === \"function\")\n // `const repo = await itx.repos.get(p); await repo.whoami()`: disposing the step releases\n // `repo` (workerd disposes a promise's result with it), never `whoami`'s call, and an\n // awaited property (`await itx.repos`) is no step at all.\n return (onFulfilled?: unknown, onRejected?: unknown) =>\n Reflect.apply(member, target, [\n typeof onFulfilled === \"function\"\n ? (answer: unknown) => {\n if (typeof answer !== \"function\") return onFulfilled(answer);\n steps.push(answer);\n return onFulfilled(record(answer, undefined));\n }\n : onFulfilled,\n onRejected,\n ]);\n if (typeof key === \"symbol\" || key === \"then\" || key === \"catch\" || key === \"finally\")\n return typeof member === \"function\" ? member.bind(target) : member;\n return record(member, target);\n },\n apply(target, _proxyReceiver, args: unknown[]) {\n // Only a callable target reaches this trap; the call runs on the unwrapped receiver, as\n // `stub.method(…)` would have.\n const result: unknown = Reflect.apply(\n target as (...args: unknown[]) => unknown,\n receiver,\n // `Object(arg)` is a fresh wrapper for a primitive, so only a recorded value is found.\n args.map((arg) => wrapped.get(Object(arg)) ?? arg),\n );\n steps.push(result);\n return record(result, undefined);\n },\n });\n wrapped.set(proxy, value);\n return proxy;\n };\n // The proxy answers every member the stub does (it forwards each one), so it is the stub's type.\n return record(stub, undefined) as T;\n}\n","// sdk/index.ts — THE userspace SDK surface, bundled (zod included — the owner's call) into every\n// loaded isolate as `processor.js` (apps/os/scripts/build.ts bundles it):\n//\n// import { StreamProcessor, StreamProcessorDurableObject, defineProcessorContract, z } from \"./processor.js\";\n//\n// The workerd HOSTS live here too (this file imports cloudflare:workers; the Node unit tests never import it):\n// FacetDurableObject — the `DurableObject` shell a context hosts as a facet: its class lists\n// the methods a caller reaches by itx expression (`publicMethods`)\n// StreamProcessorDurableObject — the facet shell that hosts ONE `StreamProcessor`\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\";\nimport { withItx } from \"./record-pipelined-steps.ts\";\n// THE ONE WAY code reaches its context: `withItx(this.env.ITX, (itx) => …)` — one round trip, then\n// everything it reached released (record-pipelined-steps.ts). A host's `this.withItx(fn)` is the same\n// function. Never `env.ITX.get()` alone: whatever it hands out keeps this isolate, and the object\n// hosting it, running and billed after the context is evicted (lint: iterate/no-raw-itx-get).\nexport { withItx };\nexport {\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) => withItx(this.env.ITX, (itx) => itx.append(e)) }, \"chat\", {…})` — a\n // field initializer cannot await — then `set` to mutate and `snapshot()` as the client's seed read\n // (stream/processor.ts).\n LiveState,\n StreamProcessor,\n defineProcessorContract,\n type ConsumedEvent,\n type EventCatalog,\n type EventDefinition,\n type EmittedEventInput,\n type EventInput,\n type LiveStateSink,\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// oxlint-disable-next-line iterate/no-capnweb-http-batch -- userspace one-shot remote calls; see above\nexport { newHttpBatchRpcSession, newWebSocketRpcSession, newWorkersRpcResponse } from \"capnweb\";\nexport { applyPatch, diff, jsonEqual, type PatchOp } from \"../lib.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 — reach as a `WithItx` accessor, never a\n// scope: `new Notifier((call) => this.withItx(call))` — and so the same class is constructed bare in\n// a test. A method of the host's own that callers reach by itx expression goes on its list:\n// `static override publicMethods = [...super.publicMethods, \"message\"]`.\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), plus `fedByPushes` when a row\n// pushes it (FacetProps). THE STREAM is the itx scope `this.withItx(fn)` hands `fn` (apps/os\n// iterate-context.ts `ItxEntrypoint`); the engine's `append`/`read` ride it like any other 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// THE CLAIM IS ALSO WHAT KEEPS A FACET RUNNING: a loaded facet that holds none when its context\n// starts a new incarnation is reset then (os FacetHost `resetUnclaimedLoadedFacets`). So work that\n// must outlive the call that started it — a model request, a retry's backoff sleep, an open\n// provider socket — runs through `runInBackground` (ProcessEventArgs), never as a bare floating\n// promise, a `ctx.waitUntil` or a timer the facet keeps on its own.\n\n/** What the parent mints a facet's class with — the whole identity, and one fact about its feed. */\nexport type FacetProps = {\n iterateContextName: string;\n name: string;\n /** Set when, as this facet started, a subscription row of its context pushed it every commit it\n * consumes (`processEventBatch`, the delivery loop's push): a processor's engine then trusts the\n * head a catch-up read until the next push (stream/processor.ts, the read verbs). Absent, only a\n * push is proof, so a processor no row pushes reads its log on every read. */\n fedByPushes?: true;\n};\n\n/** THE FACET SHELL: a `DurableObject` a context hosts as a facet — `itx.facets.get(name, { source,\n * className })`, a rule naming it, or a processor's row. A caller reaches a facet by itx expression\n * (`itx.facets.get(name).<method>(…)`) only through what its class lists in `publicMethods`: the\n * context refuses any other first step FORBIDDEN before the call reaches the facet\n * (apps/os context/facet-public-methods.ts). The platform's own calls — the delivery loop's push\n * and catch-up, the alarm's revive — never go through the list. A loaded class that does not\n * extend this shell lists nothing, so no caller reaches it by expression. */\nexport abstract class FacetDurableObject<Env = unknown> extends DurableObject<Env, FacetProps> {\n /** What a caller may reach by itx expression: the FIRST step of `itx.facets.get(name).<step>…`, a\n * method or a property of this class. A subclass lists its own on top of its parent's:\n * `static override publicMethods = [...super.publicMethods, \"send\"]`. */\n static publicMethods: readonly string[] = [\"fetch\"];\n\n /** This class's `publicMethods`, for the context that loaded it — a static does not cross the\n * isolate. On no list: only the context asks it. */\n listPublicMethods(): readonly string[] {\n // `this.constructor` is the concrete facet class, a subclass of this one; TypeScript types it as\n // `Function`, which has no `publicMethods`.\n return (this.constructor as typeof FacetDurableObject).publicMethods;\n }\n}\n\n/** The itx scope `withItx` hands its callback: a context's declared API (api.ts) — a capnweb stub\n * of apps/os'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 by its dotted surface (`.append`), which the platform's handle\n * and a loaded worker's alike answer — how an entity's processor cross-posts its certificate to\n * `/` (`withItx((itx) => itx.cd(\"/\").append(certificate))`). Through the table like every other\n * word here: a loaded processor's `cd` goes down only (the app wall), the platform's own go\n * anywhere within the project. */\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 FacetDurableObject<Env> {\n /** The reads a caller reaches on every processor: `fetch`, and the state caught up through the log\n * (`snapshot`, `liveSnapshot`) or awaited (`waitUntilProcessed`). What feeds the processor —\n * `processEventBatch`, `catchUpFromLog`, `revive` — is the platform's, never a caller's. */\n static override publicMethods = [\n ...super.publicMethods,\n \"snapshot\",\n \"liveSnapshot\",\n \"waitUntilProcessed\",\n ];\n\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: `this.withItx(fn)`; identity: `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 // ── what the platform calls: the delivery loop's push and catch-up, the alarm's revive ──\n\n /** THE push: 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 delivery loop's, when a row is configured or resumed). */\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\n // ── what a caller reaches by itx expression (`publicMethods`) ──\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 read: `{ 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.\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 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 fedByPushes: this.ctx.props.fedByPushes === true,\n }));\n }\n\n /** ONE round trip on the itx scope, then RELEASE EVERYTHING IT REACHED: the get, and every call the\n * callback made through it — not only the last. A Workers-RPC value this facet leaves undisposed —\n * the `itx.cd(path)` of `itx.cd(path).append(…)`, the `cfArtifacts.get(p)` of `.remote()`, an\n * answer awaited inside the callback (`const { state } = await context.invoke(…)`), data included —\n * keeps THIS FACET running after its context is evicted, until V8 collects the value, which an\n * idle isolate may not do for many minutes: each new incarnation of the context reattaches to the\n * facet, and the object stays billed (measured 2026-09-23: a new website project's `/` and\n * `/repos/config` billed 60 s of every minute for 30 min with no request). The context's own\n * `invoke` cannot end this from its side: the facet holds the value (context-residency.e2e.test.ts,\n * \"… does not outlive …\"). Protected: a host with methods of its own (the workspace,\n * src/workspace/durable-object.ts) reaches its context the same way. */\n protected withItx<T>(call: (itx: Scope) => T): Promise<Awaited<T>> {\n return withItx(this.#itxEntrypoint(), call);\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 ConfigEventArgs = { event: StreamEvent; range: ScannedRange; itx: ItxScope };\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 * `x-itx-principal` is on a request only when a project member (or the operator) sent it, safe\n * to act on. A private route written by hand answers the platform's sign-in challenge, which\n * the edge turns into the sign-in for a page load (`auth.require` does the same):\n *\n * ```js\n * if (!request.headers.get(\"x-itx-principal\"))\n * return new Response(\"Sign in\\n\", { status: 401, headers: { \"WWW-Authenticate\": 'Bearer realm=\"iterate\"' } });\n * ``` */\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 await this.withItx(async (itx) => {\n for (const event of events) {\n await this.processEvent({ event, range, itx });\n }\n });\n }\n\n /** ONE round trip on the itx scope, then release the scope and every call made through it\n * (`StreamProcessorDurableObject.withItx` says why an undisposed step keeps a context billed). */\n protected withItx<T>(call: (itx: ItxScope) => T): Promise<Awaited<T>> {\n return withItx(this.env.ITX, call);\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 — every Request on a host of the project (the project's configured ingress\n * target). The host's routing slug is in `x-iterate-routing-slug` (`notes` for\n * `notes--<project>.<hostname>`; absent on the apex), written only by the platform: route on it\n * in plain code, answering here (reaching the context through `this.withItx`) or forwarding the\n * Request. 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":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { SqlStorageValue } from "@cloudflare/workers-types";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { jsonEqual } from "../lib.ts";
|
|
4
3
|
/** What a processor declares: its checkpoint slug and reducer version, what it consumes and emits,
|
|
5
4
|
* and its initial state (`defineProcessorContract` below builds one from zod schemas). */
|
|
6
5
|
export type ProcessorContract<State = unknown> = {
|
|
@@ -27,9 +26,6 @@ export type ProcessorContract<State = unknown> = {
|
|
|
27
26
|
* says whether the page was cut; its length says nothing (a budget cut is short of `limit`). */
|
|
28
27
|
export type ProcessorStream = {
|
|
29
28
|
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
29
|
read(afterOffset?: number, limit?: number): Promise<{
|
|
34
30
|
events: StreamEvent[];
|
|
35
31
|
scannedThroughOffset: number;
|
|
@@ -54,8 +50,8 @@ export type ReduceArgs<State, Event = StreamEvent> = {
|
|
|
54
50
|
state: State;
|
|
55
51
|
};
|
|
56
52
|
export type ProcessEventArgs<State, Event = StreamEvent,
|
|
57
|
-
/** What `append
|
|
58
|
-
*
|
|
53
|
+
/** What `append` takes: `EmittedEventInput<typeof Contract>` for a processor that declares one —
|
|
54
|
+
* each type the contract `emits`, its payload as the catalog spells it. */
|
|
59
55
|
Emitted extends StreamEventInput = StreamEventInput> = {
|
|
60
56
|
/** The consumed event — or `null` for the eventless at-head pass. */
|
|
61
57
|
event: Event | null;
|
|
@@ -66,8 +62,6 @@ Emitted extends StreamEventInput = StreamEventInput> = {
|
|
|
66
62
|
* assignable to `StreamProcessor<State>` (the host's field), and only method parameters are
|
|
67
63
|
* compared bivariantly. */
|
|
68
64
|
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
65
|
/** Hold the cursor until `work` settles; FIFO with other blockers of the SAME event. */
|
|
72
66
|
blockProcessorWhile: (work: () => Promise<unknown>) => void;
|
|
73
67
|
/** Fire-and-forget attempt; may overtake later events; outcome must be state-recoverable. */
|
|
@@ -80,8 +74,8 @@ Emitted extends StreamEventInput = StreamEventInput> = {
|
|
|
80
74
|
* reduces all call this; there is no second copy to drift. `consumes` undefined = every durable event
|
|
81
75
|
* (a subscriber's default). "*" = every durable event. A NAMED type opts that type in, INCLUDING
|
|
82
76
|
* ephemerals ("*" NEVER sweeps ephemerals) — so a live-state watcher spells
|
|
83
|
-
* `consumes: ["events.iterate.com/live-state
|
|
84
|
-
* record (`
|
|
77
|
+
* `consumes: ["events.iterate.com/itx/live-state-changed"]` and filters `payload.key` itself. The wake
|
|
78
|
+
* record (`itx/woken`) is a durable event like any other: a "*" row receives every incarnation's. */
|
|
85
79
|
export declare function consumesEvent(consumes: readonly string[] | undefined, event: {
|
|
86
80
|
type: string;
|
|
87
81
|
ephemeral?: boolean;
|
|
@@ -97,7 +91,7 @@ export declare abstract class StreamProcessor<State, Event extends StreamEvent =
|
|
|
97
91
|
* untyped `StreamEvent` for processors that don't declare one. */
|
|
98
92
|
reduce(_args: ReduceArgs<State, Event>): State | null | undefined;
|
|
99
93
|
/** Side-effect hook. Synchronous by design: register async work via the two helpers on args.
|
|
100
|
-
* `append
|
|
94
|
+
* `append` takes what THIS class's `contract` emits (`EmittedEventInput<this["contract"]>`:
|
|
101
95
|
* a subclass whose `contract` is a defined one gets each emitted type's payload as its catalog
|
|
102
96
|
* spells it; the base, and a hand-built contract, take any input). */
|
|
103
97
|
processEvent(_args: ProcessEventArgs<State, Event, EmittedEventInput<this["contract"]>>): undefined;
|
|
@@ -120,8 +114,12 @@ export declare class ProcessorEngine<State> {
|
|
|
120
114
|
constructor(processor: StreamProcessor<State>, deps: {
|
|
121
115
|
stream: ProcessorStream;
|
|
122
116
|
storage: ReduceCheckpointTable;
|
|
117
|
+
/** The host's word that a subscription row pushes this processor every commit it consumes
|
|
118
|
+
* (`processEventBatch`). The read verbs then trust the head a catch-up read until a push shows
|
|
119
|
+
* a later one; absent, only a push's head is trusted, so an unpushed processor reads each time. */
|
|
120
|
+
fedByPushes?: boolean;
|
|
123
121
|
});
|
|
124
|
-
/** THE SEED
|
|
122
|
+
/** THE SEED READ for live-state clients (LiveState.snapshot), caught up first. */
|
|
125
123
|
liveSnapshot(): Promise<{
|
|
126
124
|
rev: number;
|
|
127
125
|
state: unknown;
|
|
@@ -130,7 +128,7 @@ export declare class ProcessorEngine<State> {
|
|
|
130
128
|
* calls this after every batch; the host calls it after a runtime field moved outside a batch. A
|
|
131
129
|
* throwing projection loses only its notification (the client re-seeds on the chain gap). */
|
|
132
130
|
publishLiveState(): void;
|
|
133
|
-
/** THE push
|
|
131
|
+
/** THE push method: contiguous → reduce it directly (no read); anything else → gap repair from the
|
|
134
132
|
* own cursor first. Fire-and-forget safe: enqueues on the serial chain. */
|
|
135
133
|
processEventBatch(events: StreamEvent[], range: ScannedRange): Promise<void>;
|
|
136
134
|
/** Catch up from the own checkpoint (a cold boot, the read verbs, the barrier), page by page — a
|
|
@@ -154,11 +152,11 @@ export declare class ProcessorEngine<State> {
|
|
|
154
152
|
* here claims again, later each time (20 s, 40 s, … `REVIVE_AFTER_MAX_MS`). */
|
|
155
153
|
revive(): Promise<void>;
|
|
156
154
|
}
|
|
157
|
-
export { jsonEqual };
|
|
158
155
|
/** What `append` accepts: the event body, before the stream assigns its committed identity. The
|
|
159
|
-
*
|
|
156
|
+
* append method checks ONE rule by hand: `type` is a non-empty string. */
|
|
160
157
|
export type StreamEventInput = {
|
|
161
|
-
/**
|
|
158
|
+
/** `events.iterate.com/<namespace>/<event>` for the platform's types, named by the rules in
|
|
159
|
+
* packages/iterate/README.md#event-types; any other string is the appender's own. */
|
|
162
160
|
type: string;
|
|
163
161
|
payload?: Record<string, unknown>;
|
|
164
162
|
metadata?: Record<string, unknown>;
|
|
@@ -188,12 +186,16 @@ export type StreamEventInput = {
|
|
|
188
186
|
};
|
|
189
187
|
/** THE CONNECTION the principal acted through: the OAuth grant's id — one per
|
|
190
188
|
* connected client (a Claude Code install, a dash sign-in, a personal token). Stamped beside
|
|
191
|
-
* `principal` by the
|
|
189
|
+
* `principal` by the platform when it appends; absent for the admin secret and the kernel. */
|
|
192
190
|
grant?: string;
|
|
191
|
+
/** THE PLATFORM WROTE THIS FACT, on the principal's behalf:
|
|
192
|
+
* what a processor folding an account's or an organization's facts requires — a client can
|
|
193
|
+
* append any type to a context it holds, never this. */
|
|
194
|
+
platform?: true;
|
|
193
195
|
};
|
|
194
196
|
/** Same key + same body = dedupe (the existing event is returned); different body = loud error. */
|
|
195
197
|
idempotencyKey?: string;
|
|
196
|
-
/** OPTIONAL PRECONDITION
|
|
198
|
+
/** OPTIONAL PRECONDITION: land at exactly this offset or refuse the whole batch with
|
|
197
199
|
* OFFSET_CONFLICT — "nothing has happened since I last looked". Never stored in the body. */
|
|
198
200
|
offset?: number;
|
|
199
201
|
/** An EPHEMERAL event rides the stream to live subscribers but is NEVER persisted: it consumes an
|
|
@@ -225,7 +227,7 @@ export type ReduceCheckpoint<State> = {
|
|
|
225
227
|
state: State | undefined;
|
|
226
228
|
};
|
|
227
229
|
/** What BOTH hosts read and write their checkpoints through — the stream's storage and a facet's
|
|
228
|
-
* own (the unit
|
|
230
|
+
* own (the Node unit tests drive it over node:sqlite, stream/test-support.ts). */
|
|
229
231
|
export declare class ReduceCheckpointTable {
|
|
230
232
|
#private;
|
|
231
233
|
/** `createTable: false` when the caller knows the table exists (the stream's storage skips every
|
|
@@ -242,7 +244,9 @@ export declare class ReduceCheckpointTable {
|
|
|
242
244
|
}, state: State, stateChanged: boolean): void;
|
|
243
245
|
}
|
|
244
246
|
/** The only thing a LiveState needs from its host: somewhere to append the delta. A
|
|
245
|
-
* `ProcessorStream`
|
|
247
|
+
* `ProcessorStream` satisfies it; a facet that is no processor passes one round trip per delta,
|
|
248
|
+
* `{ append: (e) => withItx(this.env.ITX, (itx) => itx.append(e)) }` (sdk/index.ts), never a scope it
|
|
249
|
+
* holds. */
|
|
246
250
|
export type LiveStateSink = {
|
|
247
251
|
append(event: {
|
|
248
252
|
type: string;
|
|
@@ -255,7 +259,7 @@ export declare class LiveState<S> {
|
|
|
255
259
|
constructor(sink: LiveStateSink, key: string, initial: S);
|
|
256
260
|
/** The current value (reflects every `set`). */
|
|
257
261
|
get(): S;
|
|
258
|
-
/** THE seed
|
|
262
|
+
/** THE seed read: `{rev, state}` read together (single-threaded ⇒ atomically), which is what lets
|
|
259
263
|
* a client chain patches exactly instead of guessing which changes its snapshot already contains. */
|
|
260
264
|
snapshot(): {
|
|
261
265
|
rev: number;
|
|
@@ -305,7 +309,7 @@ export type ConsumedEvent<Contract> = Contract extends {
|
|
|
305
309
|
consumes: infer Consumes extends readonly string[];
|
|
306
310
|
} ? EventForTypes<Events, DepsOf<Contract>, Consumes> : never;
|
|
307
311
|
/** 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, `
|
|
312
|
+
* catalog defines (a core control event a processor emits, `itx/ingress-configured`), the plain
|
|
309
313
|
* input: it widens the whole union, so a contract that emits one undefined type appends untyped
|
|
310
314
|
* until that type is in a catalog it depends on. */
|
|
311
315
|
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 {
|
|
@@ -322,7 +326,7 @@ type EventInputForType<Events extends EventCatalog, Deps extends readonly unknow
|
|
|
322
326
|
} : {
|
|
323
327
|
ephemeral?: never;
|
|
324
328
|
}) : never : never;
|
|
325
|
-
/** What a processor's `append
|
|
329
|
+
/** What a processor's `append` takes: one input per type the contract `emits` — its own
|
|
326
330
|
* events and its deps' as their catalogs spell them (`z.input`), a type no catalog defines as the
|
|
327
331
|
* plain input under that name. A contract whose `emits` is not a literal tuple gets every input. */
|
|
328
332
|
export type EmittedEventInput<Contract> = Contract extends {
|
|
@@ -376,3 +380,4 @@ export declare function defineProcessorContract<const StateSchema extends z.ZodT
|
|
|
376
380
|
consumes: Consumes;
|
|
377
381
|
emits: Emits;
|
|
378
382
|
}): DefinedProcessorContract<StateSchema, Events, Consumes, Deps, Emits>;
|
|
383
|
+
export {};
|