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,933 @@
|
|
|
1
|
+
import { a as createStreamProcessorRegistry, c as compactPatch, d as disposeIgnoredRpcResult, f as isThenable, l as applyPatch, n as ProcessorFacet, o as LiveState, p as retainCallback, s as applyCompactPatch, u as diff } from "./cloudflare-BTm90gQ4.mjs";
|
|
2
|
+
import { DurableObject, RpcTarget, WorkerEntrypoint } from "cloudflare:workers";
|
|
3
|
+
import { RpcTarget as RpcTarget$1 } from "@iterate-com/capnweb";
|
|
4
|
+
//#region \0rolldown/runtime.js
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) __defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
16
|
+
return target;
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
20
|
+
key = keys[i];
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
22
|
+
get: ((k) => from[k]).bind(null, key),
|
|
23
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/sdk/capnweb/live-state/store.ts
|
|
31
|
+
function createLiveStateStore() {
|
|
32
|
+
let held = {
|
|
33
|
+
revision: -1,
|
|
34
|
+
state: void 0
|
|
35
|
+
};
|
|
36
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
37
|
+
const notify = () => listeners.forEach((listener) => listener());
|
|
38
|
+
return {
|
|
39
|
+
getState: () => held.state,
|
|
40
|
+
subscribe: (listener) => {
|
|
41
|
+
listeners.add(listener);
|
|
42
|
+
return () => void listeners.delete(listener);
|
|
43
|
+
},
|
|
44
|
+
reset: () => {
|
|
45
|
+
held = {
|
|
46
|
+
revision: -1,
|
|
47
|
+
state: void 0
|
|
48
|
+
};
|
|
49
|
+
notify();
|
|
50
|
+
},
|
|
51
|
+
apply: (update, resync) => {
|
|
52
|
+
if ("s" in update) held = {
|
|
53
|
+
revision: update.s[0],
|
|
54
|
+
state: update.s[1]
|
|
55
|
+
};
|
|
56
|
+
else if ("p" in update) {
|
|
57
|
+
if (update.p[0] !== held.revision) {
|
|
58
|
+
resync();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
held = {
|
|
62
|
+
revision: update.p[1],
|
|
63
|
+
state: applyCompactPatch(held.state, update.p[2])
|
|
64
|
+
};
|
|
65
|
+
} else if (update.type === "snapshot") held = {
|
|
66
|
+
revision: update.revision,
|
|
67
|
+
state: update.state
|
|
68
|
+
};
|
|
69
|
+
else if (update.from !== held.revision) {
|
|
70
|
+
resync();
|
|
71
|
+
return;
|
|
72
|
+
} else held = {
|
|
73
|
+
revision: update.to,
|
|
74
|
+
state: applyPatch(held.state, update.patch)
|
|
75
|
+
};
|
|
76
|
+
notify();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/sdk/capnweb/live-state/protocol.ts
|
|
82
|
+
function isLiveStateSnapshot(update) {
|
|
83
|
+
return "s" in update || "type" in update && update.type === "snapshot";
|
|
84
|
+
}
|
|
85
|
+
function liveStateRevision(update) {
|
|
86
|
+
if ("s" in update) return update.s[0];
|
|
87
|
+
if ("p" in update) return update.p[1];
|
|
88
|
+
return update.type === "snapshot" ? update.revision : update.to;
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/sdk/capnweb/live-state/index.ts
|
|
92
|
+
/**
|
|
93
|
+
* Expose mutable server state as a read-only Cap'n Web capability.
|
|
94
|
+
* Pass an in-memory `LiveState`, or a stream-processor registry whose engine
|
|
95
|
+
* must hydrate before its first snapshot.
|
|
96
|
+
*/
|
|
97
|
+
var LiveStateRpcTarget = class extends RpcTarget$1 {
|
|
98
|
+
#live;
|
|
99
|
+
#beforeRead;
|
|
100
|
+
constructor(source) {
|
|
101
|
+
super();
|
|
102
|
+
if ("loadAndRefreshLive" in source) {
|
|
103
|
+
this.#live = source.live;
|
|
104
|
+
this.#beforeRead = () => source.loadAndRefreshLive();
|
|
105
|
+
} else {
|
|
106
|
+
this.#live = source;
|
|
107
|
+
this.#beforeRead = void 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async get() {
|
|
111
|
+
const loading = this.#beforeRead?.();
|
|
112
|
+
if (isThenable(loading)) await loading;
|
|
113
|
+
return this.#live.getState();
|
|
114
|
+
}
|
|
115
|
+
async subscribe(onUpdate, options) {
|
|
116
|
+
const loading = this.#beforeRead?.();
|
|
117
|
+
if (isThenable(loading)) await loading;
|
|
118
|
+
return new LiveStateSubscriptionRpcTarget(this.#live.subscribe(onUpdate, options));
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* The wire handle for one subscription — `ping`/`unsubscribe` over RPC.
|
|
123
|
+
* Exported for relays that build their subscription OUTSIDE a `LiveState`
|
|
124
|
+
* engine (the worker-local liveState-socket relay) but must hand back the
|
|
125
|
+
* same handle shape `LiveStateRpcTarget.subscribe` does.
|
|
126
|
+
*/
|
|
127
|
+
var LiveStateSubscriptionRpcTarget = class extends RpcTarget$1 {
|
|
128
|
+
#subscription;
|
|
129
|
+
constructor(subscription) {
|
|
130
|
+
super();
|
|
131
|
+
this.#subscription = subscription;
|
|
132
|
+
}
|
|
133
|
+
ping() {
|
|
134
|
+
return this.#subscription.ping();
|
|
135
|
+
}
|
|
136
|
+
unsubscribe() {
|
|
137
|
+
this.#subscription.unsubscribe();
|
|
138
|
+
}
|
|
139
|
+
[Symbol.dispose]() {
|
|
140
|
+
this.#subscription.unsubscribe();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/sdk/capnweb/index.ts
|
|
145
|
+
var capnweb_exports = /* @__PURE__ */ __exportAll({
|
|
146
|
+
LiveState: () => LiveState,
|
|
147
|
+
LiveStateRpcTarget: () => LiveStateRpcTarget,
|
|
148
|
+
LiveStateSubscriptionRpcTarget: () => LiveStateSubscriptionRpcTarget,
|
|
149
|
+
applyCompactPatch: () => applyCompactPatch,
|
|
150
|
+
applyPatch: () => applyPatch,
|
|
151
|
+
compactPatch: () => compactPatch,
|
|
152
|
+
createLiveStateStore: () => createLiveStateStore,
|
|
153
|
+
diff: () => diff,
|
|
154
|
+
disposeIgnoredRpcResult: () => disposeIgnoredRpcResult,
|
|
155
|
+
isLiveStateSnapshot: () => isLiveStateSnapshot,
|
|
156
|
+
isThenable: () => isThenable,
|
|
157
|
+
liveStateRevision: () => liveStateRevision,
|
|
158
|
+
retainCallback: () => retainCallback
|
|
159
|
+
});
|
|
160
|
+
import * as import__iterate_com_capnweb from "@iterate-com/capnweb";
|
|
161
|
+
__reExport(capnweb_exports, import__iterate_com_capnweb);
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/serve-itx.ts
|
|
164
|
+
/**
|
|
165
|
+
* Answer an app's `/api` request with an itx session for one narrowed scope.
|
|
166
|
+
* Call it from `fetch` after deciding who the visitor is:
|
|
167
|
+
*
|
|
168
|
+
* if (url.pathname === "/api") {
|
|
169
|
+
* return await serveItx(request, {
|
|
170
|
+
* project: await this.env.ITX.get(),
|
|
171
|
+
* scope: {
|
|
172
|
+
* path: `/agents/web/${visitorIdFrom(request)}`,
|
|
173
|
+
* surface: [
|
|
174
|
+
* "agent.message",
|
|
175
|
+
* "agent.liveState.get",
|
|
176
|
+
* "agent.liveState.subscribe",
|
|
177
|
+
* "agent.stream.openConnection",
|
|
178
|
+
* ],
|
|
179
|
+
* },
|
|
180
|
+
* });
|
|
181
|
+
* }
|
|
182
|
+
*
|
|
183
|
+
* WebSocket upgrades carry the live session (stream connections, live
|
|
184
|
+
* state); `POST` answers the client's HTTP-batch transport.
|
|
185
|
+
*/
|
|
186
|
+
async function serveItx(request, options) {
|
|
187
|
+
const { path, surface } = options.scope;
|
|
188
|
+
const roots = [...new Set(surface.map((entry) => entry.split(".", 1)[0]))];
|
|
189
|
+
const root = new ServedItxRoot(new ServedItxSession(relayServedProject(options.project.scope({
|
|
190
|
+
path,
|
|
191
|
+
surface: roots
|
|
192
|
+
}), surface)));
|
|
193
|
+
if (request.method === "POST") return await (0, capnweb_exports.newHttpBatchRpcResponse)(request, root);
|
|
194
|
+
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") return new Response("This endpoint accepts WebSocket upgrades and POST batches.", { status: 400 });
|
|
195
|
+
return (0, capnweb_exports.newWorkersWebSocketRpcResponse)(request, root);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The served project: the relay tree over a scoped stub for these dotted
|
|
199
|
+
* members. Exported for the relay's own tests; callers use `serveItx`.
|
|
200
|
+
* @internal
|
|
201
|
+
*/
|
|
202
|
+
function relayServedProject(scoped, surface) {
|
|
203
|
+
return relayNode(scoped, [], surfaceTree(surface));
|
|
204
|
+
}
|
|
205
|
+
const LEAF = Symbol("served member");
|
|
206
|
+
/** `["agent.message", "agent.stream.openConnection"]` → nested member tree. */
|
|
207
|
+
function surfaceTree(surface) {
|
|
208
|
+
const tree = {};
|
|
209
|
+
for (const entry of surface) {
|
|
210
|
+
const segments = entry.split(".");
|
|
211
|
+
if (segments.length < 2) throw new Error(`serveItx cannot relay the bare root "${entry}": list the members to serve as dotted paths, e.g. "${entry}.someMethod"`);
|
|
212
|
+
let node = tree;
|
|
213
|
+
for (const [index, segment] of segments.entries()) {
|
|
214
|
+
const last = index === segments.length - 1;
|
|
215
|
+
const existing = node[segment];
|
|
216
|
+
if (last) {
|
|
217
|
+
if (existing !== void 0 && existing !== LEAF) throw new Error(`serveItx: "${entry}" is both a member and a branch of the surface`);
|
|
218
|
+
node[segment] = LEAF;
|
|
219
|
+
} else {
|
|
220
|
+
if (existing === LEAF) throw new Error(`serveItx: "${entry}" is both a member and a branch of the surface`);
|
|
221
|
+
node = existing ?? (node[segment] = {});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return tree;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* One node of the served project: a real Cap'n Web target whose PROTOTYPE
|
|
229
|
+
* carries exactly the served members — a method per leaf, a getter per
|
|
230
|
+
* branch — plus `__describe`, which forwards so the platform's restricted
|
|
231
|
+
* description comes back unchanged. A leaf IS a method call: an
|
|
232
|
+
* object-valued member (`liveState`) must appear as a branch, i.e. listed
|
|
233
|
+
* through to its methods.
|
|
234
|
+
*/
|
|
235
|
+
function relayNode(stub, path, tree) {
|
|
236
|
+
class ServedNode extends capnweb_exports.RpcTarget {}
|
|
237
|
+
for (const [member, child] of Object.entries(tree)) Object.defineProperty(ServedNode.prototype, member, child === LEAF ? {
|
|
238
|
+
configurable: true,
|
|
239
|
+
value: (...args) => forward(stub, [...path, member], args),
|
|
240
|
+
writable: true
|
|
241
|
+
} : {
|
|
242
|
+
configurable: true,
|
|
243
|
+
get: () => relayNode(stub, [...path, member], child)
|
|
244
|
+
});
|
|
245
|
+
Object.defineProperty(ServedNode.prototype, "__describe", {
|
|
246
|
+
configurable: true,
|
|
247
|
+
value: () => forward(stub, [...path, "__describe"], []),
|
|
248
|
+
writable: true
|
|
249
|
+
});
|
|
250
|
+
return new ServedNode();
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Walk `path` on the Workers-RPC stub (pipelined) and call the last segment
|
|
254
|
+
* — as a genuine member call: extracting the method and `apply`ing it would
|
|
255
|
+
* make the stub's proxy dial a remote member named "apply" with the stub
|
|
256
|
+
* itself as an argument, which workerd refuses to serialize.
|
|
257
|
+
*/
|
|
258
|
+
async function forward(stub, path, args) {
|
|
259
|
+
let target = stub;
|
|
260
|
+
for (const segment of path.slice(0, -1)) target = target[segment];
|
|
261
|
+
return relayResult(await target[path.at(-1)](...args.map((arg) => relayArgument(arg))));
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* A browser callback arrives as a Cap'n Web stub function; the platform can
|
|
265
|
+
* only call back a plain function, so wrap it. Plain data passes through,
|
|
266
|
+
* recursively, because callbacks ride inside option objects.
|
|
267
|
+
*/
|
|
268
|
+
function relayArgument(value) {
|
|
269
|
+
if (typeof value === "function") {
|
|
270
|
+
const stub = value;
|
|
271
|
+
const callback = typeof stub.dup === "function" ? stub.dup() : value;
|
|
272
|
+
return (...callbackArgs) => callback(...callbackArgs);
|
|
273
|
+
}
|
|
274
|
+
if (Array.isArray(value)) return value.map((entry) => relayArgument(entry));
|
|
275
|
+
if (isPlainObject(value)) {
|
|
276
|
+
const relayed = {};
|
|
277
|
+
for (const [key, entry] of Object.entries(value)) relayed[key] = relayArgument(entry);
|
|
278
|
+
return relayed;
|
|
279
|
+
}
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
/** A stub a call returned (a connection handle) is wrapped so its methods stay callable. */
|
|
283
|
+
function relayResult(value) {
|
|
284
|
+
return isWorkersRpcStub(value) ? new RelayedStub(value) : value;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Workers-RPC stubs are callable proxies (`typeof` says "function") that
|
|
288
|
+
* implement the dispose protocol; plain data has `Object.prototype` (or no
|
|
289
|
+
* prototype), arrays are arrays, and a plain function has no disposer.
|
|
290
|
+
* `cloudflare:workers` does not export the stub class to type against, so
|
|
291
|
+
* the check is structural.
|
|
292
|
+
*/
|
|
293
|
+
function isWorkersRpcStub(value) {
|
|
294
|
+
return (typeof value === "function" || typeof value === "object" && value !== null) && !Array.isArray(value) && !isPlainObject(value) && typeof value[Symbol.dispose] === "function";
|
|
295
|
+
}
|
|
296
|
+
function isPlainObject(value) {
|
|
297
|
+
if (value === null || typeof value !== "object") return false;
|
|
298
|
+
const prototype = Object.getPrototypeOf(value);
|
|
299
|
+
return prototype === Object.prototype || prototype === null;
|
|
300
|
+
}
|
|
301
|
+
/** Names common protocols probe on any object; never forwarded. */
|
|
302
|
+
const NEVER_FORWARDED = new Set([
|
|
303
|
+
"then",
|
|
304
|
+
"toJSON",
|
|
305
|
+
"asymmetricMatch",
|
|
306
|
+
"constructor",
|
|
307
|
+
"dup",
|
|
308
|
+
"onRpcBroken"
|
|
309
|
+
]);
|
|
310
|
+
/**
|
|
311
|
+
* A stub a served call returned, as a Cap'n Web target: every unknown member
|
|
312
|
+
* is a method forwarding onto the stub (a connection handle's `close()`,
|
|
313
|
+
* `ping()`). Data properties of such a stub are not readable through the
|
|
314
|
+
* relay — methods only.
|
|
315
|
+
*/
|
|
316
|
+
var RelayedStub = class extends capnweb_exports.RpcTarget {
|
|
317
|
+
#stub;
|
|
318
|
+
constructor(stub) {
|
|
319
|
+
super();
|
|
320
|
+
this.#stub = stub;
|
|
321
|
+
}
|
|
322
|
+
static stubOf(relayed) {
|
|
323
|
+
return relayed.#stub;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
Object.setPrototypeOf(RelayedStub.prototype, new Proxy(Object.create(capnweb_exports.RpcTarget.prototype), { get(hopTarget, key, receiver) {
|
|
327
|
+
if (typeof key === "symbol" || key in hopTarget || NEVER_FORWARDED.has(key)) return Reflect.get(hopTarget, key, receiver);
|
|
328
|
+
if (!(receiver instanceof RelayedStub)) return void 0;
|
|
329
|
+
return (...args) => forward(RelayedStub.stubOf(receiver), [key], args);
|
|
330
|
+
} }));
|
|
331
|
+
var ServedItxProjects = class extends capnweb_exports.RpcTarget {
|
|
332
|
+
#project;
|
|
333
|
+
constructor(project) {
|
|
334
|
+
super();
|
|
335
|
+
this.#project = project;
|
|
336
|
+
}
|
|
337
|
+
/** The served project, whatever slug the page addresses it by. */
|
|
338
|
+
get(_slug) {
|
|
339
|
+
return this.#project;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
var ServedItxSession = class extends capnweb_exports.RpcTarget {
|
|
343
|
+
#projects;
|
|
344
|
+
constructor(project) {
|
|
345
|
+
super();
|
|
346
|
+
this.#projects = new ServedItxProjects(project);
|
|
347
|
+
}
|
|
348
|
+
get projects() {
|
|
349
|
+
return this.#projects;
|
|
350
|
+
}
|
|
351
|
+
/** Also the session keeper's liveness probe. */
|
|
352
|
+
async __describe() {
|
|
353
|
+
return {
|
|
354
|
+
instructions: "An app-served itx session: projects.get(slug) is the project.",
|
|
355
|
+
children: { projects: "The served projects catalog." }
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
var ServedItxRoot = class extends capnweb_exports.RpcTarget {
|
|
360
|
+
#session;
|
|
361
|
+
constructor(session) {
|
|
362
|
+
super();
|
|
363
|
+
this.#session = session;
|
|
364
|
+
}
|
|
365
|
+
/** The stock client's `authenticate` call. Identity was decided by the caller of `serveItx`. */
|
|
366
|
+
authenticate(_credentials) {
|
|
367
|
+
return this.#session;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/sdk.ts
|
|
372
|
+
/**
|
|
373
|
+
* Forward a request to one of the project's dynamic workers (an "app") —
|
|
374
|
+
* pages, APIs, streaming bodies, and WebSocket upgrades all ride through.
|
|
375
|
+
*
|
|
376
|
+
* Why this is a real `env.ITX.fetch` hop with the ref in a header, and not a
|
|
377
|
+
* method call on the app: workerd only performs protocol work — WebSocket 101
|
|
378
|
+
* upgrades, streaming response bodies — through genuine `fetch` handler hops
|
|
379
|
+
* between workers. Calling `fetch` (or anything else) on an app handle from
|
|
380
|
+
* `project.workers.get(ref)` is ordinary RPC: arguments and results are
|
|
381
|
+
* serialized copies, so a Response carrying a socket cannot cross it (workerd
|
|
382
|
+
* throws a DataCloneError). And because a fetch hop's only side channel for
|
|
383
|
+
* "which worker do I mean" is the request itself, the target ref rides the
|
|
384
|
+
* x-iterate-worker-dispatch header (JSON `{ ref, buildBudgetMs? }` — the same
|
|
385
|
+
* ref shape `project.workers.get` takes). Method calls on apps still go
|
|
386
|
+
* through `project.workers.get(ref)` RPC dispatch; HTTP never does.
|
|
387
|
+
*
|
|
388
|
+
* A cold build past `buildBudgetMs` (default 15s) answers a 503 building page
|
|
389
|
+
* that refreshes itself, marked with x-iterate-worker-building — intercept
|
|
390
|
+
* that response here to render your own.
|
|
391
|
+
*/
|
|
392
|
+
async function fetchDynamicWorker(env, req, ref, opts) {
|
|
393
|
+
const headers = new Headers(req.headers);
|
|
394
|
+
headers.set("x-iterate-worker-dispatch", JSON.stringify({
|
|
395
|
+
buildBudgetMs: opts?.buildBudgetMs ?? 15e3,
|
|
396
|
+
ref
|
|
397
|
+
}));
|
|
398
|
+
return await env.ITX.fetch(new Request(req, { headers }));
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Walk a dotted capability path over the worker instance and invoke the final
|
|
402
|
+
* segment. The platform dispatches `itx.worker.<path>` calls as ONE flattened
|
|
403
|
+
* `invokeCapability({ path, args })`, and this walks it in userland: nothing
|
|
404
|
+
* ever crosses RPC except the final method's arguments and result, so a
|
|
405
|
+
* getter on the worker class can hand back a whole vendor SDK (installed from
|
|
406
|
+
* package.json) — or any nested surface — in a single round trip.
|
|
407
|
+
*/
|
|
408
|
+
async function invokeCapability(receiver, { args = [], path }) {
|
|
409
|
+
for (const segment of path.slice(0, -1)) receiver = await Reflect.get(Object(receiver), segment);
|
|
410
|
+
const method = path.at(-1);
|
|
411
|
+
const handler = Reflect.get(Object(receiver), method);
|
|
412
|
+
if (typeof handler !== "function") throw new Error(`"${path.join(".")}" is not a method on this worker`);
|
|
413
|
+
return await Reflect.apply(handler, receiver, args);
|
|
414
|
+
}
|
|
415
|
+
/** Dispose an RPC stub, tolerating stubs without a disposer and disposers
|
|
416
|
+
* that throw — cleanup must never mask the value it was cleaning up after. */
|
|
417
|
+
function disposeStub(stub) {
|
|
418
|
+
try {
|
|
419
|
+
stub[Symbol.dispose]?.();
|
|
420
|
+
} catch {}
|
|
421
|
+
}
|
|
422
|
+
/** Open a project itx session, use it, dispose it. RPC stubs from
|
|
423
|
+
* `env.ITX.get()` must not outlive the invocation that dialed them, so every
|
|
424
|
+
* operation opens its own session. */
|
|
425
|
+
async function withProject(env, fn) {
|
|
426
|
+
const project = await env.ITX.get();
|
|
427
|
+
try {
|
|
428
|
+
return await fn(project);
|
|
429
|
+
} finally {
|
|
430
|
+
disposeStub(project);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Detach plain data returned by Workers RPC before releasing the per-call
|
|
435
|
+
* result capability. Disposing the project root does not release values
|
|
436
|
+
* obtained through it; each stream call owns its own result reference.
|
|
437
|
+
*/
|
|
438
|
+
function detachPlainRpcResult(result) {
|
|
439
|
+
if (result === null || typeof result !== "object" && typeof result !== "function") return result;
|
|
440
|
+
const detached = Array.isArray(result) ? [...result] : { ...result };
|
|
441
|
+
Reflect.deleteProperty(detached, Symbol.dispose);
|
|
442
|
+
try {
|
|
443
|
+
const dispose = Reflect.get(result, Symbol.dispose);
|
|
444
|
+
if (typeof dispose === "function") Reflect.apply(dispose, result, []);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
console.warn("project stream RPC result dispose failed after detaching plain data", { error });
|
|
447
|
+
}
|
|
448
|
+
return detached;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* A view of `target` with `overrides` in front and everything else passed
|
|
452
|
+
* through. Proxies (not spreads or Object.create) because DurableObjectState
|
|
453
|
+
* and DurableObjectStorage are host objects: their methods must be invoked
|
|
454
|
+
* with the REAL receiver or workerd throws "Illegal invocation".
|
|
455
|
+
*/
|
|
456
|
+
function overlay(target, overrides) {
|
|
457
|
+
return new Proxy(target, { get(object, prop) {
|
|
458
|
+
if (Object.hasOwn(overrides, prop)) return overrides[prop];
|
|
459
|
+
const value = Reflect.get(object, prop, object);
|
|
460
|
+
return typeof value === "function" ? value.bind(object) : value;
|
|
461
|
+
} });
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* A {@link ProcessorStream} over the project stream at `path`, dialed through
|
|
465
|
+
* the platform ITX binding — the stream handle a worker-hosted stream
|
|
466
|
+
* processor registry needs (`iterate/processors`; see the config-repo
|
|
467
|
+
* template's guestbook for the full hosting shape). RPC stubs from
|
|
468
|
+
* `env.ITX.get()` must not outlive the invocation that dialed them, so every
|
|
469
|
+
* operation opens, uses, and disposes its own session; `readEvents` owns one
|
|
470
|
+
* session for its pager's lifetime (scope it with `using`, like any pager).
|
|
471
|
+
*/
|
|
472
|
+
function itxProjectStream(env, path) {
|
|
473
|
+
const withStream = (fn) => withProject(env, (project) => fn(project.streams)).then(detachPlainRpcResult);
|
|
474
|
+
return {
|
|
475
|
+
append: (...events) => withStream((streams) => streams.get(path).append(...events)),
|
|
476
|
+
appendIfStreamId: (args) => withStream((streams) => streams.get(path).appendIfStreamId(args)),
|
|
477
|
+
getEvent: (args) => withStream((streams) => streams.get(path).getEvent(args)),
|
|
478
|
+
getEvents: (args) => withStream((streams) => streams.get(path).getEvents(args)),
|
|
479
|
+
getEventPage: (args) => withStream((streams) => streams.get(path).getEventPage(args)),
|
|
480
|
+
readEvents: (args) => {
|
|
481
|
+
let opened;
|
|
482
|
+
let closed = false;
|
|
483
|
+
return {
|
|
484
|
+
next: async () => {
|
|
485
|
+
if (closed) return [];
|
|
486
|
+
opened ??= env.ITX.get().then((project) => ({
|
|
487
|
+
pager: project.streams.get(path).readEvents(args),
|
|
488
|
+
project
|
|
489
|
+
}));
|
|
490
|
+
const { pager } = await opened;
|
|
491
|
+
return detachPlainRpcResult(await pager.next());
|
|
492
|
+
},
|
|
493
|
+
[Symbol.dispose]() {
|
|
494
|
+
closed = true;
|
|
495
|
+
opened?.then(({ pager, project }) => {
|
|
496
|
+
disposeStub(pager);
|
|
497
|
+
disposeStub(project);
|
|
498
|
+
}).catch(() => {});
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
},
|
|
502
|
+
at: (siblingPath) => itxProjectStream(env, siblingPath)
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
/** Where the platform-delivered self ref lives in this worker's own storage
|
|
506
|
+
* (see {@link IterateDurableObject.__stashSelfRef}). */
|
|
507
|
+
const SELF_REF_STORAGE_KEY = "iterate:self-ref";
|
|
508
|
+
/**
|
|
509
|
+
* Durable alarms for a stateful dynamic worker, presented as the ordinary
|
|
510
|
+
* `DurableObjectState` alarm API — what `IterateDurableObject` installs as
|
|
511
|
+
* `this.ctx`, so alarms on stateful workers just work.
|
|
512
|
+
*
|
|
513
|
+
* workerd does not implement alarms for facet-hosted Durable Objects — the
|
|
514
|
+
* hosting model for stateful dynamic workers (workerd#6810; a facet
|
|
515
|
+
* `setAlarm` even appears to succeed, then poisons the commit path). So the
|
|
516
|
+
* platform Durable Object that hosts this worker keeps the real alarm on its
|
|
517
|
+
* behalf: the trio here dials `itx.workers.get(ref).setAlarm/getAlarm` on the
|
|
518
|
+
* worker's own ref — delivered by the host before any other traffic and
|
|
519
|
+
* stashed durably ({@link IterateDurableObject.__stashSelfRef}) — and the
|
|
520
|
+
* host's fire calls this class's `alarm(alarmInfo)` method, retried by the
|
|
521
|
+
* platform if it throws, exactly like a native alarm handler.
|
|
522
|
+
*
|
|
523
|
+
* Everything except the three alarm methods is the worker's own `ctx`,
|
|
524
|
+
* untouched (`storage.kv`, `waitUntil`, WebSocket APIs, …), so code written
|
|
525
|
+
* against this state uses only standard Durable Object concepts, and if
|
|
526
|
+
* workerd ships facet alarms the shim disappears without a caller changing.
|
|
527
|
+
* Honest divergences: the alarm methods ignore the native `options` bag,
|
|
528
|
+
* alarms armed inside `storage.transaction()` bypass the shim, and a class
|
|
529
|
+
* with no `alarm()` handler fails at fire time (native `setAlarm` rejects at
|
|
530
|
+
* arm time; the shim cannot check).
|
|
531
|
+
*/
|
|
532
|
+
function selfAlarmState(ctx, env) {
|
|
533
|
+
const withSelf = (fn) => {
|
|
534
|
+
const ref = ctx.storage.kv.get(SELF_REF_STORAGE_KEY);
|
|
535
|
+
if (ref === void 0) throw new Error("this worker's own ref has not been delivered yet — alarms are unavailable in the constructor; arm from a handler instead");
|
|
536
|
+
return withProject(env, (project) => fn(project.workers.get(ref)));
|
|
537
|
+
};
|
|
538
|
+
return overlay(ctx, { storage: overlay(ctx.storage, {
|
|
539
|
+
setAlarm: (scheduledTime) => withSelf((worker) => worker.setAlarm(typeof scheduledTime === "number" ? scheduledTime : scheduledTime.getTime())),
|
|
540
|
+
deleteAlarm: () => withSelf((worker) => worker.setAlarm(null)),
|
|
541
|
+
getAlarm: () => withSelf((worker) => worker.getAlarm())
|
|
542
|
+
}) });
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Base class for stateless dynamic workers — the project worker (a repo's
|
|
546
|
+
* default export) and stateless apps. A WorkerEntrypoint whose env carries
|
|
547
|
+
* the platform's itx binding, plus the platform ceremony every worker needs
|
|
548
|
+
* but shouldn't have to hand-roll:
|
|
549
|
+
*
|
|
550
|
+
* - `processEventBatch` / `processEvent`: the platform delivers every
|
|
551
|
+
* committed DURABLE event on every stream in the project as checkpointed
|
|
552
|
+
* per-stream batches — in per-stream order, at-least-once. Events appended
|
|
553
|
+
* with `ephemeral: true` (LLM streaming chunks and other transient
|
|
554
|
+
* signals) never arrive here. Session callbacks can receive one while its
|
|
555
|
+
* body remains in the Stream Durable Object's bounded memory buffer; react
|
|
556
|
+
* here to the durable fact that supersedes it (e.g. an assistant-role
|
|
557
|
+
* `agents/context-added` item). The base unpacks batches
|
|
558
|
+
* into one `processEvent(event)` call per event; override `processEvent` to react
|
|
559
|
+
* (one `if` per reaction, keyed on event.path + event.type). Throwing (or a worker that fails to build) leaves that
|
|
560
|
+
* stream's checkpoint in place and the whole batch is redelivered later,
|
|
561
|
+
* so return normally to advance past events you don't care about — and
|
|
562
|
+
* anything a reaction appends should carry an idempotency key.
|
|
563
|
+
* - `invokeCapability`: the flattened `itx.worker.<path>` dispatcher — add a
|
|
564
|
+
* getter or method to your class and it becomes a capability surface.
|
|
565
|
+
* - `fetchDynamicWorker`: forward HTTP (WebSockets included) to another of
|
|
566
|
+
* the project's dynamic workers.
|
|
567
|
+
*/
|
|
568
|
+
var IterateWorkerEntrypoint = class extends WorkerEntrypoint {
|
|
569
|
+
#itx;
|
|
570
|
+
constructor(ctx, env) {
|
|
571
|
+
super(ctx, env);
|
|
572
|
+
this.env = env;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* This invocation's project-root itx. Cloudflare constructs one
|
|
576
|
+
* WorkerEntrypoint per invocation and releases its RPC stubs when that
|
|
577
|
+
* execution context ends. The getter returns the native Workers RPC
|
|
578
|
+
* promise-proxy, so `await this.itx.agents.list()` pipelines immediately;
|
|
579
|
+
* repeated access within one event batch or fetch reuses the same handle.
|
|
580
|
+
*
|
|
581
|
+
* Durable Objects have a longer, multi-invocation lifetime and no eviction
|
|
582
|
+
* callback, so IterateDurableObject deliberately does not expose this
|
|
583
|
+
* memoized getter.
|
|
584
|
+
*/
|
|
585
|
+
get itx() {
|
|
586
|
+
return this.#itx ??= this.env.ITX.get();
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Run the project-member auth partial fetch without transferring the app's
|
|
590
|
+
* request body on paths auth only inspects. The callback POST belongs to
|
|
591
|
+
* auth, so that one path deliberately transfers the complete request.
|
|
592
|
+
*/
|
|
593
|
+
async fetchProjectAuth(request, policy) {
|
|
594
|
+
if (request.method === "POST" && new URL(request.url).pathname === "/_iterate/auth/callback") return await this.itx.auth.get(policy).fetch(request);
|
|
595
|
+
const authRequest = new Request(request.url, {
|
|
596
|
+
headers: request.headers,
|
|
597
|
+
method: request.method
|
|
598
|
+
});
|
|
599
|
+
return await this.itx.auth.get(policy).fetch(authRequest);
|
|
600
|
+
}
|
|
601
|
+
/** See `fetchDynamicWorker` at module level: a real fetch hop into a
|
|
602
|
+
* sibling dynamic worker — the only lane that can carry WebSocket upgrades
|
|
603
|
+
* and streaming bodies (RPC serializes; sockets can't cross it). */
|
|
604
|
+
async fetchDynamicWorker(req, ref, opts) {
|
|
605
|
+
return await fetchDynamicWorker(this.env, req, ref, opts);
|
|
606
|
+
}
|
|
607
|
+
/** Platform entry point for event delivery (see the class docstring for
|
|
608
|
+
* the delivery contract). Override `processEvent`, not this, unless you
|
|
609
|
+
* need whole-batch atomicity. */
|
|
610
|
+
async processEventBatch(batch) {
|
|
611
|
+
for (const event of batch.events) await this.processEvent(event);
|
|
612
|
+
}
|
|
613
|
+
/** Called once per delivered event, in per-stream order, at-least-once.
|
|
614
|
+
* Override to react; the default ignores everything. */
|
|
615
|
+
async processEvent(_event) {}
|
|
616
|
+
/** Platform entry point for flattened `itx.worker.<path>` capability
|
|
617
|
+
* calls (see the class docstring). */
|
|
618
|
+
async invokeCapability(input) {
|
|
619
|
+
return await invokeCapability(this, input);
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
/**
|
|
623
|
+
* Base class for stateful dynamic workers (apps hosted as Durable Objects
|
|
624
|
+
* under a `durableWorkerKey`). Same platform surface as
|
|
625
|
+
* `IterateWorkerEntrypoint` — see its docstring for the event-delivery and
|
|
626
|
+
* capability-dispatch contracts — on a DurableObject, so state survives
|
|
627
|
+
* across requests and WebSockets can be served from `fetch`. Durable alarms
|
|
628
|
+
* work natively — `this.ctx.storage.setAlarm(...)` plus an `alarm()` method
|
|
629
|
+
* on the class — even though this object is hosted as a workerd facet with
|
|
630
|
+
* no alarms of its own; see {@link selfAlarmState} for the mechanism.
|
|
631
|
+
*/
|
|
632
|
+
var IterateDurableObject = class extends DurableObject {
|
|
633
|
+
constructor(ctx, env) {
|
|
634
|
+
super(ctx, env);
|
|
635
|
+
this.env = env;
|
|
636
|
+
this.ctx = selfAlarmState(this.ctx, this.env);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Platform bootstrap door: the Durable Object hosting this worker delivers
|
|
640
|
+
* the worker's OWN ref here before any other traffic in its incarnation —
|
|
641
|
+
* a facet has no channel to learn its identity by itself, and the ref is
|
|
642
|
+
* what the alarm shim dials (`itx.workers.get(ref)` resolves back to the
|
|
643
|
+
* hosting DO). Stashed durably, so a warm facet under a fresh host needs
|
|
644
|
+
* no re-delivery to keep working.
|
|
645
|
+
*/
|
|
646
|
+
__stashSelfRef(ref) {
|
|
647
|
+
this.ctx.storage.kv.put(SELF_REF_STORAGE_KEY, ref);
|
|
648
|
+
}
|
|
649
|
+
/** A real fetch hop into a sibling dynamic worker — see
|
|
650
|
+
* `IterateWorkerEntrypoint.fetchDynamicWorker`. */
|
|
651
|
+
async fetchDynamicWorker(req, ref, opts) {
|
|
652
|
+
return await fetchDynamicWorker(this.env, req, ref, opts);
|
|
653
|
+
}
|
|
654
|
+
/** Platform entry point for event delivery — see
|
|
655
|
+
* `IterateWorkerEntrypoint.processEventBatch`. */
|
|
656
|
+
async processEventBatch(batch) {
|
|
657
|
+
for (const event of batch.events) await this.processEvent(event);
|
|
658
|
+
}
|
|
659
|
+
/** Called once per delivered event — see
|
|
660
|
+
* `IterateWorkerEntrypoint.processEvent`. */
|
|
661
|
+
async processEvent(_event) {}
|
|
662
|
+
/** Platform entry point for flattened `itx.worker.<path>` capability
|
|
663
|
+
* calls — see `IterateWorkerEntrypoint.invokeCapability`. */
|
|
664
|
+
async invokeCapability(input) {
|
|
665
|
+
return await invokeCapability(this, input);
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
const HOST_PROJECT_ID_KEY = "iterate:processor-host:project-id";
|
|
669
|
+
const HOST_STREAM_PATH_KEY = "iterate:processor-host:stream-path";
|
|
670
|
+
/**
|
|
671
|
+
* The worker's `processor` node: the wake door the stream spine dials PLUS
|
|
672
|
+
* the public read verbs of the standard processor contract
|
|
673
|
+
* (`StreamProcessorRpc`). The reads serve the registered runner directly, so
|
|
674
|
+
* `streams.get(path).subscriptions.get(name).processor` and the stream's
|
|
675
|
+
* `waitUntilProcessed` barrier reach an expression-placed dynamic worker's
|
|
676
|
+
* processor with no extra wiring — the stream replays those verbs onto this
|
|
677
|
+
* node (the stored wake expression minus its trailing wake step). It crosses
|
|
678
|
+
* Workers RPC as a property read before its method is called, so it must be
|
|
679
|
+
* a real RpcTarget.
|
|
680
|
+
*/
|
|
681
|
+
var ProcessorWakeTarget = class extends RpcTarget {
|
|
682
|
+
#registryFor;
|
|
683
|
+
#reads;
|
|
684
|
+
constructor(registryFor, reads) {
|
|
685
|
+
super();
|
|
686
|
+
this.#registryFor = registryFor;
|
|
687
|
+
this.#reads = reads;
|
|
688
|
+
}
|
|
689
|
+
async wakeStreamProcessor(request) {
|
|
690
|
+
if (request.stream.projectId === null) throw new Error("hosted stream processors require project streams");
|
|
691
|
+
return await this.#registryFor(request.stream.projectId, request.stream.path).wakeStreamProcessor(request);
|
|
692
|
+
}
|
|
693
|
+
/** One consistent read of the committed fold, pulled through the durable
|
|
694
|
+
* stream tail first (read-your-writes, the hosting relays' catch-up leg). */
|
|
695
|
+
async snapshot() {
|
|
696
|
+
const reads = await this.#reads();
|
|
697
|
+
await reads.catchUp();
|
|
698
|
+
return await reads.snapshot();
|
|
699
|
+
}
|
|
700
|
+
async getRuntimeState() {
|
|
701
|
+
return await (await this.#reads()).getRuntimeState();
|
|
702
|
+
}
|
|
703
|
+
/** Offset barrier against the runner's committed fold. The runner's waiter
|
|
704
|
+
* self-pulls, so its timeout bounds the whole read-your-writes operation. */
|
|
705
|
+
async waitUntilProcessed(input) {
|
|
706
|
+
await (await this.#reads()).waitUntilEvent(input);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
/**
|
|
710
|
+
* Host one stream processor in a Durable Object: the registry ceremony, the
|
|
711
|
+
* RPC-safe wake door, the alarm multiplex, and the durable coordinate cache
|
|
712
|
+
* in one place, so an app class is only its processor plus its own verbs.
|
|
713
|
+
*
|
|
714
|
+
* With `path` the host serves one fixed home stream (the guestbook shape).
|
|
715
|
+
* Without it the host learns its stream from the first wake request and
|
|
716
|
+
* caches the coordinates durably (one Durable Object per dynamic stream,
|
|
717
|
+
* keyed by the ref that names it). One host per Durable Object — the cache
|
|
718
|
+
* keys assume it.
|
|
719
|
+
*/
|
|
720
|
+
function createProcessorHost(args) {
|
|
721
|
+
let built;
|
|
722
|
+
const ensure = (projectId, path) => {
|
|
723
|
+
if (built === void 0) {
|
|
724
|
+
args.ctx.storage.kv.put(HOST_PROJECT_ID_KEY, projectId);
|
|
725
|
+
args.ctx.storage.kv.put(HOST_STREAM_PATH_KEY, path);
|
|
726
|
+
const stream = itxProjectStream(args.env, path);
|
|
727
|
+
const registry = createStreamProcessorRegistry(args.ctx, {
|
|
728
|
+
path,
|
|
729
|
+
projectId,
|
|
730
|
+
stream,
|
|
731
|
+
version: args.env.ITERATE_WORKER_VERSION
|
|
732
|
+
});
|
|
733
|
+
const processor = registry.register(args.createProcessor({
|
|
734
|
+
path,
|
|
735
|
+
projectId,
|
|
736
|
+
stream
|
|
737
|
+
}), args.recovery === void 0 ? void 0 : { recovery: args.recovery });
|
|
738
|
+
built = {
|
|
739
|
+
reads: registry.reads(processor),
|
|
740
|
+
registry
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
return built;
|
|
744
|
+
};
|
|
745
|
+
const buildOutsideWake = async () => {
|
|
746
|
+
if (built !== void 0) return built;
|
|
747
|
+
const path = args.path ?? args.ctx.storage.kv.get(HOST_STREAM_PATH_KEY);
|
|
748
|
+
if (path === void 0) throw new Error("this processor host learns its stream from the first wake request");
|
|
749
|
+
return ensure(args.ctx.storage.kv.get(HOST_PROJECT_ID_KEY) ?? await withProject(args.env, async (project) => await project.projectId), path);
|
|
750
|
+
};
|
|
751
|
+
return {
|
|
752
|
+
registry: async () => (await buildOutsideWake()).registry,
|
|
753
|
+
snapshot: async () => await (await buildOutsideWake()).reads.snapshot(),
|
|
754
|
+
wakeProcessor: new ProcessorWakeTarget((projectId, path) => ensure(projectId, args.path ?? path).registry, async () => (await buildOutsideWake()).reads),
|
|
755
|
+
async handleAlarm(alarmInfo) {
|
|
756
|
+
const projectId = args.ctx.storage.kv.get(HOST_PROJECT_ID_KEY);
|
|
757
|
+
const path = args.path ?? args.ctx.storage.kv.get(HOST_STREAM_PATH_KEY);
|
|
758
|
+
if (projectId === void 0 || path === void 0) return;
|
|
759
|
+
await ensure(projectId, path).registry.handleAlarm(alarmInfo);
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Ergonomic base for a Durable Object that hosts exactly one stream processor.
|
|
765
|
+
*
|
|
766
|
+
* It collapses the host boilerplate every processor app writes by hand today —
|
|
767
|
+
* a {@link createProcessorHost} field, an `alarm()` that forwards to it, and a
|
|
768
|
+
* `processor` getter exposing the wake door — down to a single
|
|
769
|
+
* {@link createProcessor} factory. First-party and userspace processors are
|
|
770
|
+
* authored the SAME way: all a subclass writes is how to build its processor
|
|
771
|
+
* from the host-derived platform deps (`{ stream, path, projectId }`) plus
|
|
772
|
+
* whatever it wires off `this.env`. That single seam is why a built-in
|
|
773
|
+
* processor forks into userspace with no change to the processor itself — only
|
|
774
|
+
* the dep wiring in `createProcessor` differs.
|
|
775
|
+
*
|
|
776
|
+
* Host-agnostic: the exact same class runs as its own Durable Object, as a
|
|
777
|
+
* facet of the Stream DO, or as a facet of another DO. Only the alarm adapter
|
|
778
|
+
* (native vs parent-proxied) and who dials the wake door differ per host, and
|
|
779
|
+
* the host owns both.
|
|
780
|
+
*
|
|
781
|
+
* ```ts
|
|
782
|
+
* export class GuestbookDurableObject extends StreamProcessorDurableObject<GuestbookState> {
|
|
783
|
+
* protected readonly streamPath = guestbookStreamPath; // fixed home stream
|
|
784
|
+
* protected createProcessor(deps: ProcessorHostDeps) {
|
|
785
|
+
* return new GuestbookProcessor(deps);
|
|
786
|
+
* }
|
|
787
|
+
* // alarm(), the `processor` wake door, snapshot(), registry() — inherited.
|
|
788
|
+
* }
|
|
789
|
+
* ```
|
|
790
|
+
*/
|
|
791
|
+
var StreamProcessorDurableObject = class extends IterateDurableObject {
|
|
792
|
+
/**
|
|
793
|
+
* A fixed home stream path (the guestbook shape). Leave undefined to learn
|
|
794
|
+
* the stream from the first wake request and cache the coordinates durably
|
|
795
|
+
* (one Durable Object per dynamic stream).
|
|
796
|
+
*/
|
|
797
|
+
streamPath;
|
|
798
|
+
/**
|
|
799
|
+
* Post-eviction keepalive recovery, for processors that owe registered
|
|
800
|
+
* background work (the obligation pattern). Off by default.
|
|
801
|
+
*/
|
|
802
|
+
recovery = false;
|
|
803
|
+
/**
|
|
804
|
+
* Built lazily on first door/alarm access — deliberately NOT a field
|
|
805
|
+
* initializer. A base-class field initializer runs before the subclass's
|
|
806
|
+
* `streamPath` / `recovery` field initializers, so it would capture the base
|
|
807
|
+
* defaults instead of a subclass's overrides; reading them on first use
|
|
808
|
+
* sidesteps that ordering trap. Memoized because the host owns the registry
|
|
809
|
+
* cache, and one host per Durable Object is assumed.
|
|
810
|
+
*/
|
|
811
|
+
#memoHost;
|
|
812
|
+
get #host() {
|
|
813
|
+
return this.#memoHost ??= createProcessorHost({
|
|
814
|
+
ctx: this.ctx,
|
|
815
|
+
env: this.env,
|
|
816
|
+
path: this.streamPath,
|
|
817
|
+
recovery: this.recovery,
|
|
818
|
+
createProcessor: (deps) => this.createProcessor(deps)
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* The processor node the stream spine dials: the wake door plus the standard
|
|
823
|
+
* read verbs (`snapshot` / `getRuntimeState` / `waitUntilProcessed`), so the
|
|
824
|
+
* subscriptions catalog and the stream barrier reach this processor. It
|
|
825
|
+
* crosses Workers RPC as a property read before its method is called, so it
|
|
826
|
+
* must be a real RpcTarget — which the host guarantees.
|
|
827
|
+
*/
|
|
828
|
+
get processor() {
|
|
829
|
+
return this.#host.wakeProcessor;
|
|
830
|
+
}
|
|
831
|
+
/** Route the Durable Object's native `alarm()` into the host's multiplex. */
|
|
832
|
+
async alarm(alarmInfo) {
|
|
833
|
+
await this.#host.handleAlarm(alarmInfo);
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* One consistent read of the hosted processor's committed fold, for a
|
|
837
|
+
* subclass's own read verbs (e.g. a `getState()`).
|
|
838
|
+
*/
|
|
839
|
+
snapshot() {
|
|
840
|
+
return this.#host.snapshot();
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* The lazily built registry, for subclasses that drive catch-up / live
|
|
844
|
+
* refresh directly (the guestbook's HTTP + append flow).
|
|
845
|
+
*/
|
|
846
|
+
registry() {
|
|
847
|
+
return this.#host.registry();
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
/**
|
|
851
|
+
* Ergonomic base for a userspace stream processor hosted as a FACET of the
|
|
852
|
+
* platform's Stream Durable Object — the `facet-processor` subscription with a
|
|
853
|
+
* `userspace` source, whose `StatefulDynamicWorkerRef` names THIS class. The
|
|
854
|
+
* Stream DO loads the class from the project's source and starts it with
|
|
855
|
+
* `ctx.facets.get(name, …)`, so it runs INSIDE the stream it serves: wakes are
|
|
856
|
+
* an in-process parent→facet dial (no itx wake expression), and checkpoints /
|
|
857
|
+
* projections land in the facet's own private SQLite.
|
|
858
|
+
*
|
|
859
|
+
* Same authoring seam as {@link StreamProcessorDurableObject} — a subclass
|
|
860
|
+
* writes only `createProcessor` (and sets `recovery` when it owes background
|
|
861
|
+
* work) — so a processor forks between "its own Durable Object" and "a facet
|
|
862
|
+
* of its stream" with no change to the processor itself. The two differences a
|
|
863
|
+
* facet forces, both handled here:
|
|
864
|
+
*
|
|
865
|
+
* - **No native alarm.** workerd does not implement alarms for facets
|
|
866
|
+
* (workerd#6810), so this base routes the registry's keepalive/recovery
|
|
867
|
+
* alarm through the parent's real platform alarm over itx —
|
|
868
|
+
* `streams.get(path).proxySetAlarm/proxyDeleteAlarm/proxyGetAlarm`, the
|
|
869
|
+
* itx-channel twin of a stateful worker's `workers.get(ref).setAlarm`. The
|
|
870
|
+
* parent fires it back as {@link ProcessorFacet.handleAlarm}. NEVER define
|
|
871
|
+
* `alarm()` on a subclass (the base throws): a facet's native `setAlarm`
|
|
872
|
+
* would appear to succeed, then poison its output gate.
|
|
873
|
+
* - **Identity by first-contact `configure`, not the constructor.** The Stream
|
|
874
|
+
* DO delivers `{ parentName, projectId, path }`; the base stashes it and
|
|
875
|
+
* rebuilds the host from that stash on every incarnation (see
|
|
876
|
+
* {@link ProcessorFacet}).
|
|
877
|
+
*
|
|
878
|
+
* ```ts
|
|
879
|
+
* export class GuestbookFacet extends StreamProcessorFacet {
|
|
880
|
+
* protected readonly recovery = true; // owes background work
|
|
881
|
+
* protected createProcessor(deps: ProcessorHostDeps) {
|
|
882
|
+
* return new GuestbookProcessor(deps);
|
|
883
|
+
* }
|
|
884
|
+
* }
|
|
885
|
+
* ```
|
|
886
|
+
*/
|
|
887
|
+
var StreamProcessorFacet = class extends ProcessorFacet {
|
|
888
|
+
/**
|
|
889
|
+
* Post-eviction keepalive recovery, for processors that owe registered
|
|
890
|
+
* background work (the obligation pattern). Off by default.
|
|
891
|
+
*/
|
|
892
|
+
recovery = false;
|
|
893
|
+
/**
|
|
894
|
+
* Dial the parent stream's alarm proxy over itx, resolved PER CALL (a stub
|
|
895
|
+
* must not outlive its RPC turn), each verb opening and disposing its own
|
|
896
|
+
* session like {@link itxProjectStream}. `identity.path` is the coordinate;
|
|
897
|
+
* `parentName` is unused here (userspace facets have no `env.STREAM`).
|
|
898
|
+
*/
|
|
899
|
+
parentAlarms(identity) {
|
|
900
|
+
const env = this.env;
|
|
901
|
+
const { path } = identity;
|
|
902
|
+
return {
|
|
903
|
+
proxySetAlarm: (scheduledTimeMs) => withProject(env, (project) => project.streams.get(path).proxySetAlarm(scheduledTimeMs)),
|
|
904
|
+
proxyDeleteAlarm: () => withProject(env, (project) => project.streams.get(path).proxyDeleteAlarm()),
|
|
905
|
+
proxyGetAlarm: () => withProject(env, (project) => project.streams.get(path).proxyGetAlarm())
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Build the host wiring from the stashed identity: the stream handle is the
|
|
910
|
+
* same itx-backed {@link itxProjectStream} a worker-hosted processor appends
|
|
911
|
+
* through, and the single processor is registered under this facet's name.
|
|
912
|
+
*/
|
|
913
|
+
createHost(identity) {
|
|
914
|
+
if (identity.projectId === null) throw new Error("userspace stream-processor facets require a project stream");
|
|
915
|
+
const { projectId, path } = identity;
|
|
916
|
+
const stream = itxProjectStream(this.env, path);
|
|
917
|
+
return {
|
|
918
|
+
stream,
|
|
919
|
+
version: this.env.ITERATE_WORKER_VERSION,
|
|
920
|
+
registerProcessors: (registry) => {
|
|
921
|
+
registry.register(this.createProcessor({
|
|
922
|
+
path,
|
|
923
|
+
projectId,
|
|
924
|
+
stream
|
|
925
|
+
}), { recovery: this.recovery });
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
//#endregion
|
|
931
|
+
export { createProcessorHost as a, capnweb_exports as c, StreamProcessorFacet as i, LiveStateRpcTarget as l, IterateWorkerEntrypoint as n, itxProjectStream as o, StreamProcessorDurableObject as r, serveItx as s, IterateDurableObject as t };
|
|
932
|
+
|
|
933
|
+
//# sourceMappingURL=sdk-DMB-IM11.mjs.map
|