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.
Files changed (218) hide show
  1. package/README.md +86 -76
  2. package/THIRD_PARTY_NOTICES.md +55 -0
  3. package/bin/iterate.js +11 -3
  4. package/dist/api-url-B6404M82.mjs +17 -0
  5. package/dist/api-url-B6404M82.mjs.map +1 -0
  6. package/dist/app-ref-BipL0feU.mjs +35 -0
  7. package/dist/app-ref-BipL0feU.mjs.map +1 -0
  8. package/dist/app-ref-C1CrgXqX.mjs +7 -0
  9. package/dist/app-ref-C1CrgXqX.mjs.map +1 -0
  10. package/dist/app-ref-DYai_om1.mjs +7 -0
  11. package/dist/app-ref-DYai_om1.mjs.map +1 -0
  12. package/dist/cli-D0c-pDL_.mjs +1010 -0
  13. package/dist/cli-D0c-pDL_.mjs.map +1 -0
  14. package/dist/client.d.ts +3 -0
  15. package/dist/client.mjs +4 -0
  16. package/dist/cloudflare-BTm90gQ4.mjs +951 -0
  17. package/dist/cloudflare-BTm90gQ4.mjs.map +1 -0
  18. package/dist/contract-s4FW4eES.mjs +309 -0
  19. package/dist/contract-s4FW4eES.mjs.map +1 -0
  20. package/dist/document-review/index.d.ts +5 -0
  21. package/dist/document-review/types.d.ts +107 -0
  22. package/dist/document-review.mjs +7015 -0
  23. package/dist/document-review.mjs.map +1 -0
  24. package/dist/durable-object-processor-durability-CNsTjAJS.mjs +205 -0
  25. package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +1 -0
  26. package/dist/idempotency-DleloJNt.mjs +28 -0
  27. package/dist/idempotency-DleloJNt.mjs.map +1 -0
  28. package/dist/index.mjs +1 -1
  29. package/dist/itx/api-url.d.ts +6 -0
  30. package/dist/itx/itx-node-client.d.ts +65 -0
  31. package/dist/itx/itx-session.d.ts +215 -0
  32. package/dist/itx/owned-rpc-session.d.ts +14 -0
  33. package/dist/itx/query-client.d.ts +10 -0
  34. package/dist/itx-api.generated.d.ts +6195 -0
  35. package/dist/itx-session-sjud8GiT.mjs +534 -0
  36. package/dist/itx-session-sjud8GiT.mjs.map +1 -0
  37. package/dist/live-state-BJNqOwFw.mjs +299 -0
  38. package/dist/live-state-BJNqOwFw.mjs.map +1 -0
  39. package/dist/next/api.d.ts +479 -0
  40. package/dist/next/api.mjs +0 -0
  41. package/dist/next/app-server.d.ts +44 -0
  42. package/dist/next/app-server.mjs +479 -0
  43. package/dist/next/app-server.mjs.map +1 -0
  44. package/dist/next/app-session.d.ts +49 -0
  45. package/dist/next/app-session.mjs +238 -0
  46. package/dist/next/app-session.mjs.map +1 -0
  47. package/dist/next/app.d.ts +29 -0
  48. package/dist/next/app.mjs +141 -0
  49. package/dist/next/app.mjs.map +1 -0
  50. package/dist/next/client/live-state.d.ts +63 -0
  51. package/dist/next/client/oauth.d.ts +12 -0
  52. package/dist/next/client/react.d.ts +109 -0
  53. package/dist/next/client/socket.d.ts +6 -0
  54. package/dist/next/client.mjs +156 -0
  55. package/dist/next/client.mjs.map +1 -0
  56. package/dist/next/expression.d.ts +146 -0
  57. package/dist/next/expression.mjs +399 -0
  58. package/dist/next/expression.mjs.map +1 -0
  59. package/dist/next/lib.d.ts +56 -0
  60. package/dist/next/lib.mjs +199 -0
  61. package/dist/next/lib.mjs.map +1 -0
  62. package/dist/next/oauth-scopes.d.ts +32 -0
  63. package/dist/next/oauth-scopes.mjs +40 -0
  64. package/dist/next/oauth-scopes.mjs.map +1 -0
  65. package/dist/next/oauth.mjs +29 -0
  66. package/dist/next/oauth.mjs.map +1 -0
  67. package/dist/next/principal.d.ts +64 -0
  68. package/dist/next/principal.mjs +98 -0
  69. package/dist/next/principal.mjs.map +1 -0
  70. package/dist/next/project-ingress.d.ts +37 -0
  71. package/dist/next/project-ingress.mjs +75 -0
  72. package/dist/next/project-ingress.mjs.map +1 -0
  73. package/dist/next/react.mjs +285 -0
  74. package/dist/next/react.mjs.map +1 -0
  75. package/dist/next/sdk/auth.d.ts +5 -0
  76. package/dist/next/sdk/index.d.ts +112 -0
  77. package/dist/next/sdk.mjs +139 -0
  78. package/dist/next/sdk.mjs.map +1 -0
  79. package/dist/next/stream/processor.d.ts +378 -0
  80. package/dist/next/stream/processor.mjs +582 -0
  81. package/dist/next/stream/processor.mjs.map +1 -0
  82. package/dist/next/stream/run.d.ts +58 -0
  83. package/dist/next/stream/run.mjs +40 -0
  84. package/dist/next/stream/run.mjs.map +1 -0
  85. package/dist/next-node.d.ts +15 -0
  86. package/dist/next-node.mjs +51 -0
  87. package/dist/next-node.mjs.map +1 -0
  88. package/dist/node.d.ts +3 -0
  89. package/dist/node.mjs +185 -0
  90. package/dist/node.mjs.map +1 -0
  91. package/dist/processor-host-capabilities-BMFH3KTM.mjs +56 -0
  92. package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +1 -0
  93. package/dist/processors/cloudflare.d.ts +3 -0
  94. package/dist/processors/durable-object-processor-durability.d.ts +79 -0
  95. package/dist/processors/event-consumption-metrics.d.ts +82 -0
  96. package/dist/processors/idempotency.d.ts +13 -0
  97. package/dist/processors/index.d.ts +12 -0
  98. package/dist/processors/processor-contracts.d.ts +342 -0
  99. package/dist/processors/processor-facet.d.ts +186 -0
  100. package/dist/processors/processor-host-capabilities.d.ts +60 -0
  101. package/dist/processors/prompt-sections.d.ts +17 -0
  102. package/dist/processors/rpc-types.d.ts +515 -0
  103. package/dist/processors/schemas.d.ts +102 -0
  104. package/dist/processors/stream-handle.d.ts +45 -0
  105. package/dist/processors/stream-processor-keepalive.d.ts +95 -0
  106. package/dist/processors/stream-processor-registry.d.ts +233 -0
  107. package/dist/processors/stream-processor-runner.d.ts +289 -0
  108. package/dist/processors/stream-processor.d.ts +339 -0
  109. package/dist/processors/stream-runtime-metrics.d.ts +107 -0
  110. package/dist/processors/testing.d.ts +302 -0
  111. package/dist/processors-BoNyeBfQ.mjs +10 -0
  112. package/dist/processors-BoNyeBfQ.mjs.map +1 -0
  113. package/dist/processors-cloudflare.mjs +3 -0
  114. package/dist/processors-testing.mjs +435 -0
  115. package/dist/processors-testing.mjs.map +1 -0
  116. package/dist/processors.mjs +52 -0
  117. package/dist/processors.mjs.map +1 -0
  118. package/dist/protocol-DnK_f2m6.mjs +251 -0
  119. package/dist/protocol-DnK_f2m6.mjs.map +1 -0
  120. package/dist/sdk/capnweb/index.d.ts +2 -0
  121. package/dist/sdk/capnweb/live-state/compact.d.ts +5 -0
  122. package/dist/sdk/capnweb/live-state/diff.d.ts +41 -0
  123. package/dist/sdk/capnweb/live-state/engine.d.ts +44 -0
  124. package/dist/sdk/capnweb/live-state/index.d.ts +41 -0
  125. package/dist/sdk/capnweb/live-state/protocol.d.ts +87 -0
  126. package/dist/sdk/capnweb/live-state/retain.d.ts +23 -0
  127. package/dist/sdk/capnweb/live-state/store.d.ts +20 -0
  128. package/dist/sdk/capnweb/live-state/types.d.ts +11 -0
  129. package/dist/sdk/capnweb/react.d.ts +45 -0
  130. package/dist/sdk/capnweb/react.mjs +316 -0
  131. package/dist/sdk/capnweb/react.mjs.map +1 -0
  132. package/dist/sdk/capnweb.mjs +4 -0
  133. package/dist/sdk/itx/react.d.ts +191 -0
  134. package/dist/sdk/itx/react.mjs +383 -0
  135. package/dist/sdk/itx/react.mjs.map +1 -0
  136. package/dist/sdk-DMB-IM11.mjs +933 -0
  137. package/dist/sdk-DMB-IM11.mjs.map +1 -0
  138. package/dist/sdk.d.ts +339 -0
  139. package/dist/sdk.mjs +2 -0
  140. package/dist/serve-itx.d.ts +46 -0
  141. package/dist/starter-apps/flake-dashboard/app-ref.d.ts +31 -0
  142. package/dist/starter-apps/flake-dashboard/configured-worker.mjs +1055 -0
  143. package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +1 -0
  144. package/dist/starter-apps/flake-dashboard/contract.d.ts +4839 -0
  145. package/dist/starter-apps/flake-dashboard/contract.mjs +2 -0
  146. package/dist/starter-apps/flake-dashboard/index.d.ts +17 -0
  147. package/dist/starter-apps/flake-dashboard/index.mjs +56 -0
  148. package/dist/starter-apps/flake-dashboard/index.mjs.map +1 -0
  149. package/dist/starter-apps/flake-dashboard/worker.d.ts +4607 -0
  150. package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +8914 -0
  151. package/dist/starter-apps/github-ai-linter/configured-worker.mjs +17987 -0
  152. package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +1 -0
  153. package/dist/starter-apps/github-ai-linter/contract.d.ts +9193 -0
  154. package/dist/starter-apps/github-ai-linter/index.d.ts +10 -0
  155. package/dist/starter-apps/github-ai-linter/index.mjs +36 -0
  156. package/dist/starter-apps/github-ai-linter/index.mjs.map +1 -0
  157. package/dist/starter-apps/github-ai-linter/prompt.d.ts +13 -0
  158. package/dist/starter-apps/github-ai-linter/review-bot.d.ts +808 -0
  159. package/dist/starter-apps/github-ai-linter/rules.d.ts +34 -0
  160. package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +19 -0
  161. package/dist/starter-apps/github-ai-linter/worker.d.ts +19 -0
  162. package/dist/starter-apps/github-ai-linter/worker.mjs +947 -0
  163. package/dist/starter-apps/github-ai-linter/worker.mjs.map +1 -0
  164. package/dist/starter-apps/guestbook/app-ref.d.ts +27 -0
  165. package/dist/starter-apps/guestbook/client.d.ts +7 -0
  166. package/dist/starter-apps/guestbook/client.mjs +59 -0
  167. package/dist/starter-apps/guestbook/configured-worker.mjs +205 -0
  168. package/dist/starter-apps/guestbook/configured-worker.mjs.map +1 -0
  169. package/dist/starter-apps/guestbook/index.d.ts +9 -0
  170. package/dist/starter-apps/guestbook/index.mjs +31 -0
  171. package/dist/starter-apps/guestbook/index.mjs.map +1 -0
  172. package/dist/starter-apps/guestbook/processor.d.ts +2267 -0
  173. package/dist/starter-apps/guestbook/worker.d.ts +26 -0
  174. package/dist/starter-apps/guestbook/worker.mjs +191 -0
  175. package/dist/starter-apps/guestbook/worker.mjs.map +1 -0
  176. package/dist/starter-apps/media/configured-worker.mjs +577 -0
  177. package/dist/starter-apps/media/configured-worker.mjs.map +1 -0
  178. package/dist/starter-apps/media/index.mjs +36 -0
  179. package/dist/starter-apps/media/index.mjs.map +1 -0
  180. package/dist/starter-apps/media/ref.mjs +20 -0
  181. package/dist/starter-apps/media/ref.mjs.map +1 -0
  182. package/dist/starter-apps/media/worker.mjs +579 -0
  183. package/dist/starter-apps/media/worker.mjs.map +1 -0
  184. package/dist/starter-apps/notes/configured-worker.mjs +6134 -0
  185. package/dist/starter-apps/notes/configured-worker.mjs.map +1 -0
  186. package/dist/starter-apps/notes/index.mjs +23 -0
  187. package/dist/starter-apps/notes/index.mjs.map +1 -0
  188. package/dist/starter-apps/notes/ref.mjs +21 -0
  189. package/dist/starter-apps/notes/ref.mjs.map +1 -0
  190. package/dist/starter-apps/notes/worker.mjs +427 -0
  191. package/dist/starter-apps/notes/worker.mjs.map +1 -0
  192. package/dist/starter-apps/todo/client.mjs +59 -0
  193. package/dist/starter-apps/todo/configured-worker.mjs +2864 -0
  194. package/dist/starter-apps/todo/configured-worker.mjs.map +1 -0
  195. package/dist/starter-apps/todo/index.d.ts +8 -0
  196. package/dist/starter-apps/todo/index.mjs +29 -0
  197. package/dist/starter-apps/todo/index.mjs.map +1 -0
  198. package/dist/stream-processor-keepalive-DAQTP6m3.mjs +2082 -0
  199. package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +1 -0
  200. package/dist/usingCtx-inzbY1Qz.mjs +57 -0
  201. package/dist/usingCtx-mZx5nsAW.mjs +11800 -0
  202. package/dist/usingCtx-mZx5nsAW.mjs.map +1 -0
  203. package/dist/worker-ref-DZxPDmb_.mjs +390 -0
  204. package/dist/worker-ref-DZxPDmb_.mjs.map +1 -0
  205. package/menubar/Iterate.entitlements +12 -0
  206. package/menubar/Iterate.swift +914 -0
  207. package/menubar/IterateIcon.swift +145 -0
  208. package/menubar/README.md +28 -0
  209. package/menubar/build-menubar-app.sh +59 -0
  210. package/package.json +235 -18
  211. package/dist/cli-DMS4kJph.mjs +0 -868
  212. package/dist/cli-DMS4kJph.mjs.map +0 -1
  213. package/dist/config-DtnR7Lv7.mjs +0 -170
  214. package/dist/config-DtnR7Lv7.mjs.map +0 -1
  215. package/dist/index.d.mts.map +0 -1
  216. package/dist/stream-tui/agent-chat-terminal.d.mts +0 -1
  217. package/dist/stream-tui/agent-chat-terminal.mjs +0 -933
  218. package/dist/stream-tui/agent-chat-terminal.mjs.map +0 -1
@@ -0,0 +1,75 @@
1
+ //#region src/next/project-ingress.ts
2
+ /** A DNS label: lowercase letters and digits, single hyphens inside. */
3
+ const DNS_LABEL = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4
+ /** An app label: a DNS label that is also an itx identifier (it becomes a step, `itx.apps.<label>`). */
5
+ const APP_LABEL = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
6
+ /** The labels `host` has under `hostname` — `site--p.iterate.app` ⇒ `["site--p"]` — lowercased, a
7
+ * trailing dot (a fully-qualified Host, `site--p.base.`) dropped; null when `host` is not under
8
+ * `hostname` at all. */
9
+ function labelsUnder(host, hostname) {
10
+ const name = host.toLowerCase().replace(/\.$/, "");
11
+ const suffix = `.${hostname.toLowerCase()}`;
12
+ return name.endsWith(suffix) ? name.slice(0, -suffix.length).split(".") : null;
13
+ }
14
+ /** The project + app `url` names under `routing`, or null when it names none. Pure. */
15
+ function projectAddressOf(routing, url, platformOrigin) {
16
+ if (!routing) return null;
17
+ if (routing.type === "subdomains") {
18
+ const labels = labelsUnder(url.hostname, routing.hostname);
19
+ if (!labels || labels.length > 2) return null;
20
+ const [first, second] = labels;
21
+ const separator = first.startsWith("xn--") ? -1 : first.indexOf("--");
22
+ const [app, project] = second !== void 0 ? [first, second] : separator === -1 ? [null, first] : [first.slice(0, separator), first.slice(separator + 2)];
23
+ if (!DNS_LABEL.test(project) || app !== null && !APP_LABEL.test(app)) return null;
24
+ return {
25
+ app,
26
+ project,
27
+ basePath: ""
28
+ };
29
+ }
30
+ if (url.origin !== new URL(platformOrigin).origin) return null;
31
+ const [, prefix, project = "", app] = url.pathname.split("/");
32
+ if (prefix !== "projects" || !DNS_LABEL.test(project)) return null;
33
+ if (app === void 0 || app === "") return {
34
+ app: null,
35
+ project,
36
+ basePath: `/projects/${project}`
37
+ };
38
+ if (!APP_LABEL.test(app)) return null;
39
+ return {
40
+ app,
41
+ project,
42
+ basePath: `/projects/${project}/${app}`
43
+ };
44
+ }
45
+ /** A CUSTOM HOSTNAME — one of a deployment's own that IS a project's apex (os-next
46
+ * `urls.temporaryCustomHostnames`, `{ "iterate2.com": "iterate" }`): the apex shape, `app: null`, so the
47
+ * project's config worker `fetch` answers exactly as it does on `<project>.<hostname>`. Null for a
48
+ * hostname the map does not name — the map's spelling, case and a trailing dot forgiven, no wildcard
49
+ * under it. Pure. */
50
+ function customProjectHostOf(hostname, hostnames) {
51
+ const project = hostnames[hostname.toLowerCase().replace(/\.$/, "")];
52
+ return project ? {
53
+ app: null,
54
+ project
55
+ } : null;
56
+ }
57
+ /** The URL of `app` (null ⇒ the apex, the config worker) in `project` under `routing`, at `path`
58
+ * (default "/", must start with "/"). Null when there is no ingress, or when the result would not
59
+ * parse back to the same address (a bad label; a `path` that climbs out of its app). subdomains:
60
+ * the protocol and port are `platformOrigin`'s (local dev is `http://localhost:8788`, so
61
+ * `http://<app>--<project>.localhost:8788/…`); paths: `<platformOrigin>/projects/<project>[/<app>]<path>`. Pure. */
62
+ function projectUrlOf(routing, platformOrigin, target) {
63
+ if (!routing) return null;
64
+ const path = target.path || "/";
65
+ if (!path.startsWith("/")) throw new Error(`projectUrlOf: path must start with "/": ${path}`);
66
+ const app = target.app || null;
67
+ const origin = new URL(platformOrigin);
68
+ const url = routing.type === "subdomains" ? new URL(path, `${origin.protocol}//${app ? `${app}--` : ""}${target.project}.${routing.hostname}${origin.port ? `:${origin.port}` : ""}`) : new URL(`/projects/${target.project}${app ? `/${app}` : ""}${path}`, origin.origin);
69
+ const parsed = projectAddressOf(routing, url, platformOrigin);
70
+ return parsed && parsed.project === target.project && parsed.app === app ? url : null;
71
+ }
72
+ //#endregion
73
+ export { customProjectHostOf, projectAddressOf, projectUrlOf };
74
+
75
+ //# sourceMappingURL=project-ingress.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-ingress.mjs","names":[],"sources":["../../src/next/project-ingress.ts"],"sourcesContent":["// next/project-ingress.ts — HOW PROJECTS ARE REACHED OVER HTTP, both directions in ONE pure file. A\n// deployment's `urls.ingressRouting` (os-next app-config.ts) names the mechanism; `projectAddressOf`\n// parses a request's URL into the project and app it names, `projectUrlOf` composes the URL of an\n// app in a project. The platform's edge parses; the platform, the dash and an app compose — one\n// implementation, table-tested to round-trip (project-ingress.test.ts). No imports.\n//\n// subdomains `<app>--<project>.<hostname>`, `<app>.<project>.<hostname>`, the apex `<project>.<hostname>`\n// — every app its own origin, under one wildcard on `hostname`.\n// paths `<platformOrigin>/projects/<project>/<app>/…`, the apex `<platformOrigin>/projects/<project>/`\n// — one origin (workers.dev has no wildcard), every project under `/projects/` so the\n// platform's own paths (`/api`, `/mcp`, `/login`, …) need no reserved list; the edge\n// sandboxes what an app serves.\n\n/** How projects are reached over HTTP; null ⇒ no ingress (`/api` and `/mcp` still answer). */\nexport type IngressRouting = { type: \"subdomains\"; hostname: string } | { type: \"paths\" } | null;\n\n/** What a request names: the project (its slug, as written — whether it EXISTS is the directory's\n * answer), the app (null ⇒ the apex: the project's config worker answers), and the path prefix the\n * edge strips before the app sees the URL (\"\" under subdomains; \"/projects/<project>\" or\n * \"/projects/<project>/<app>\" under paths). */\nexport type ProjectAddress = { project: string; app: string | null; basePath: string };\n\n/** A DNS label: lowercase letters and digits, single hyphens inside. */\nconst DNS_LABEL = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n/** An app label: a DNS label that is also an itx identifier (it becomes a step, `itx.apps.<label>`). */\nconst APP_LABEL = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;\n\n/** The labels `host` has under `hostname` — `site--p.iterate.app` ⇒ `[\"site--p\"]` — lowercased, a\n * trailing dot (a fully-qualified Host, `site--p.base.`) dropped; null when `host` is not under\n * `hostname` at all. */\nfunction labelsUnder(host: string, hostname: string): string[] | null {\n const name = host.toLowerCase().replace(/\\.$/, \"\");\n const suffix = `.${hostname.toLowerCase()}`;\n return name.endsWith(suffix) ? name.slice(0, -suffix.length).split(\".\") : null;\n}\n\n/** The project + app `url` names under `routing`, or null when it names none. Pure. */\nexport function projectAddressOf(\n routing: IngressRouting,\n url: URL,\n platformOrigin: string,\n): ProjectAddress | null {\n if (!routing) return null;\n if (routing.type === \"subdomains\") {\n const labels = labelsUnder(url.hostname, routing.hostname);\n if (!labels || labels.length > 2) return null; // deeper than `<app>.<project>` is not a project host\n const [first, second] = labels as [string, string?];\n const separator = first.startsWith(\"xn--\") ? -1 : first.indexOf(\"--\"); // `xn--…` is an IDN label (punycode), never `<app>--<project>`\n const [app, project] =\n // oxlint-disable-next-line iterate/simple-truthiness-check -- a PRESENT-but-empty second label (`<app>..<base>`) is the `<app>.<project>` shape (rejected below by DNS_LABEL), not the single-label `<project>` shape a truthiness check would route it to\n second !== undefined\n ? [first, second] // `<app>.<project>`\n : separator === -1\n ? [null, first] // the apex, `<project>`\n : [first.slice(0, separator), first.slice(separator + 2)]; // `<app>--<project>`\n // oxlint-disable-next-line iterate/simple-truthiness-check -- an empty app label (`--<project>.<base>`) must still be rejected by APP_LABEL; truthiness would skip the check and admit it\n if (!DNS_LABEL.test(project) || (app !== null && !APP_LABEL.test(app))) return null;\n return { app, project, basePath: \"\" };\n }\n if (url.origin !== new URL(platformOrigin).origin) return null;\n const [, prefix, project = \"\", app] = url.pathname.split(\"/\");\n if (prefix !== \"projects\" || !DNS_LABEL.test(project)) return null;\n // oxlint-disable-next-line iterate/simple-truthiness-check -- `/projects/<project>` and `/projects/<project>/` are both the apex; a present-but-empty next segment is not an app\n if (app === undefined || app === \"\")\n return { app: null, project, basePath: `/projects/${project}` };\n if (!APP_LABEL.test(app)) return null;\n return { app, project, basePath: `/projects/${project}/${app}` };\n}\n\n/** A CUSTOM HOSTNAME — one of a deployment's own that IS a project's apex (os-next\n * `urls.temporaryCustomHostnames`, `{ \"iterate2.com\": \"iterate\" }`): the apex shape, `app: null`, so the\n * project's config worker `fetch` answers exactly as it does on `<project>.<hostname>`. Null for a\n * hostname the map does not name — the map's spelling, case and a trailing dot forgiven, no wildcard\n * under it. Pure. */\nexport function customProjectHostOf(\n hostname: string,\n hostnames: Record<string, string>,\n): { app: null; project: string } | null {\n const project = hostnames[hostname.toLowerCase().replace(/\\.$/, \"\")];\n return project ? { app: null, project } : null;\n}\n\n/** The URL of `app` (null ⇒ the apex, the config worker) in `project` under `routing`, at `path`\n * (default \"/\", must start with \"/\"). Null when there is no ingress, or when the result would not\n * parse back to the same address (a bad label; a `path` that climbs out of its app). subdomains:\n * the protocol and port are `platformOrigin`'s (local dev is `http://localhost:8788`, so\n * `http://<app>--<project>.localhost:8788/…`); paths: `<platformOrigin>/projects/<project>[/<app>]<path>`. Pure. */\nexport function projectUrlOf(\n routing: IngressRouting,\n platformOrigin: string,\n target: { project: string; app?: string | null; path?: string },\n): URL | null {\n if (!routing) return null;\n const path = target.path || \"/\";\n if (!path.startsWith(\"/\")) throw new Error(`projectUrlOf: path must start with \"/\": ${path}`);\n const app = target.app || null;\n const origin = new URL(platformOrigin);\n const url =\n routing.type === \"subdomains\"\n ? new URL(\n path,\n `${origin.protocol}//${app ? `${app}--` : \"\"}${target.project}.${routing.hostname}${origin.port ? `:${origin.port}` : \"\"}`,\n )\n : new URL(`/projects/${target.project}${app ? `/${app}` : \"\"}${path}`, origin.origin);\n const parsed = projectAddressOf(routing, url, platformOrigin);\n return parsed && parsed.project === target.project && parsed.app === app ? url : null;\n}\n"],"mappings":";;AAuBA,MAAM,YAAY;;AAElB,MAAM,YAAY;;;;AAKlB,SAAS,YAAY,MAAc,UAAmC;CACpE,MAAM,OAAO,KAAK,YAAY,CAAC,CAAC,QAAQ,OAAO,EAAE;CACjD,MAAM,SAAS,IAAI,SAAS,YAAY;CACxC,OAAO,KAAK,SAAS,MAAM,IAAI,KAAK,MAAM,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,GAAG,IAAI;AAC5E;;AAGA,SAAgB,iBACd,SACA,KACA,gBACuB;CACvB,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,QAAQ,SAAS,cAAc;EACjC,MAAM,SAAS,YAAY,IAAI,UAAU,QAAQ,QAAQ;EACzD,IAAI,CAAC,UAAU,OAAO,SAAS,GAAG,OAAO;EACzC,MAAM,CAAC,OAAO,UAAU;EACxB,MAAM,YAAY,MAAM,WAAW,MAAM,IAAI,KAAK,MAAM,QAAQ,IAAI;EACpE,MAAM,CAAC,KAAK,WAEV,WAAW,KAAA,IACP,CAAC,OAAO,MAAM,IACd,cAAc,KACZ,CAAC,MAAM,KAAK,IACZ,CAAC,MAAM,MAAM,GAAG,SAAS,GAAG,MAAM,MAAM,YAAY,CAAC,CAAC;EAE9D,IAAI,CAAC,UAAU,KAAK,OAAO,KAAM,QAAQ,QAAQ,CAAC,UAAU,KAAK,GAAG,GAAI,OAAO;EAC/E,OAAO;GAAE;GAAK;GAAS,UAAU;EAAG;CACtC;CACA,IAAI,IAAI,WAAW,IAAI,IAAI,cAAc,CAAC,CAAC,QAAQ,OAAO;CAC1D,MAAM,GAAG,QAAQ,UAAU,IAAI,OAAO,IAAI,SAAS,MAAM,GAAG;CAC5D,IAAI,WAAW,cAAc,CAAC,UAAU,KAAK,OAAO,GAAG,OAAO;CAE9D,IAAI,QAAQ,KAAA,KAAa,QAAQ,IAC/B,OAAO;EAAE,KAAK;EAAM;EAAS,UAAU,aAAa;CAAU;CAChE,IAAI,CAAC,UAAU,KAAK,GAAG,GAAG,OAAO;CACjC,OAAO;EAAE;EAAK;EAAS,UAAU,aAAa,QAAQ,GAAG;CAAM;AACjE;;;;;;AAOA,SAAgB,oBACd,UACA,WACuC;CACvC,MAAM,UAAU,UAAU,SAAS,YAAY,CAAC,CAAC,QAAQ,OAAO,EAAE;CAClE,OAAO,UAAU;EAAE,KAAK;EAAM;CAAQ,IAAI;AAC5C;;;;;;AAOA,SAAgB,aACd,SACA,gBACA,QACY;CACZ,IAAI,CAAC,SAAS,OAAO;CACrB,MAAM,OAAO,OAAO,QAAQ;CAC5B,IAAI,CAAC,KAAK,WAAW,GAAG,GAAG,MAAM,IAAI,MAAM,2CAA2C,MAAM;CAC5F,MAAM,MAAM,OAAO,OAAO;CAC1B,MAAM,SAAS,IAAI,IAAI,cAAc;CACrC,MAAM,MACJ,QAAQ,SAAS,eACb,IAAI,IACF,MACA,GAAG,OAAO,SAAS,IAAI,MAAM,GAAG,IAAI,MAAM,KAAK,OAAO,QAAQ,GAAG,QAAQ,WAAW,OAAO,OAAO,IAAI,OAAO,SAAS,IACxH,IACA,IAAI,IAAI,aAAa,OAAO,UAAU,MAAM,IAAI,QAAQ,KAAK,QAAQ,OAAO,MAAM;CACxF,MAAM,SAAS,iBAAiB,SAAS,KAAK,cAAc;CAC5D,OAAO,UAAU,OAAO,YAAY,OAAO,WAAW,OAAO,QAAQ,MAAM,MAAM;AACnF"}
@@ -0,0 +1,285 @@
1
+ import { connectLiveState } from "./client.mjs";
2
+ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
+ //#region src/next/client/react.tsx
4
+ /** @jsxImportSource react */
5
+ /** Subscribe to a producer's live state and render its latest value. Pass a ready `itx` (a capnweb
6
+ * `api.authenticate(credentials).user` or `.projects.get(id)`), the producer's `key`, and a `door`
7
+ * thunk that reads `{rev, state}` (`() => itx.invoke("itx.facets.get('slug').liveSnapshot()")`).
8
+ * Re-subscribes when the session, `key`, or `name` changes; unmount (and every re-subscribe)
9
+ * disposes the previous server-side subscription. */
10
+ function useLiveState(itx, opts) {
11
+ const [store, setStore] = useState();
12
+ const [status, setStatus] = useState("connecting");
13
+ const [error, setError] = useState();
14
+ const doorRef = useRef(opts.door);
15
+ doorRef.current = opts.door;
16
+ useEffect(() => {
17
+ setStore(void 0);
18
+ setStatus("connecting");
19
+ setError(void 0);
20
+ if (!itx) return;
21
+ const door = doorRef.current;
22
+ let disposed = false;
23
+ let dispose;
24
+ const unmounted = new AbortController();
25
+ connectLiveState(itx, {
26
+ key: opts.key,
27
+ name: opts.name,
28
+ door,
29
+ signal: unmounted.signal,
30
+ onResync: (r) => {
31
+ if (disposed) return;
32
+ if (r === "healed") {
33
+ setStatus("live");
34
+ setError(void 0);
35
+ } else {
36
+ setStatus("error");
37
+ setError(r.message);
38
+ }
39
+ }
40
+ }).then((conn) => {
41
+ dispose = conn.dispose;
42
+ if (disposed) {
43
+ conn.dispose();
44
+ return;
45
+ }
46
+ setStore(conn.store);
47
+ setStatus("live");
48
+ }, (e) => {
49
+ if (disposed) return;
50
+ setError(e instanceof Error ? e.message : String(e));
51
+ setStatus("error");
52
+ });
53
+ return () => {
54
+ disposed = true;
55
+ unmounted.abort();
56
+ dispose?.();
57
+ };
58
+ }, [
59
+ itx,
60
+ opts.key,
61
+ opts.name
62
+ ]);
63
+ return {
64
+ value: useSyncExternalStore(useCallback((cb) => store ? store.subscribe(cb) : () => {}, [store]), () => store?.get(), () => void 0),
65
+ rev: store?.rev() ?? null,
66
+ status,
67
+ error
68
+ };
69
+ }
70
+ /** A wire event (a capnweb proxy value or a plain object) as an `IterateContextEvent`, or null when
71
+ * it is not a committed row. Structural, not a schema: the transport validated it; this only refuses
72
+ * a shape the view cannot place (no offset, type or time). */
73
+ function toIterateContextEvent(raw) {
74
+ const value = JSON.parse(JSON.stringify(raw));
75
+ if (!value || typeof value.offset !== "number" || typeof value.type !== "string" || typeof value.createdAt !== "string") return null;
76
+ return value;
77
+ }
78
+ /** A named live state before its first seed lands — and before the effect that opens it has run. */
79
+ const LIVE_STATE_CONNECTING = {
80
+ value: void 0,
81
+ rev: null,
82
+ status: "connecting"
83
+ };
84
+ /** THE ITERATE CONTEXT, live — one hook, one stream subscription. THE LOG: subscribe to every
85
+ * committed event (or `consumes`) BEFORE the catch-up read, so nothing lands between the two;
86
+ * pushes and pages both dedupe into one map by offset; `caughtUp` once the read reached the head;
87
+ * `error` when the connect failed. Off that same log, THE PROCESSORS TABLE, re-read whenever the
88
+ * log grows a row-changing event (a subscription configured, halted or resumed — the table is core
89
+ * state, one call away, no push of its own), and WHO IS HERE: the rpc stubs lent right now
90
+ * (`itx.rpcStubs.list()` — physical, re-read at every new head, since presence changes are
91
+ * ephemeral facts) and, from the log, every principal that acted, newest first. And named facets'
92
+ * LIVE STATE, each seeded through `itx.facets.get('<name>').liveSnapshot()` — one entry per name,
93
+ * always. `liveState` OMITTED opens `core` (the core reduce answers under that name) plus every
94
+ * hosted facet in the processors table the hook holds, following the table as it loads and changes;
95
+ * `liveState` GIVEN is exactly the names to open, no implicit `core`. Re-connects when `itx`
96
+ * changes; unmount disposes every server-side subscription. */
97
+ function useIterateContext(itx, opts = {}) {
98
+ const [events, setEvents] = useState(() => /* @__PURE__ */ new Map());
99
+ const [caughtUp, setCaughtUp] = useState(false);
100
+ const [error, setError] = useState();
101
+ const consumesKey = JSON.stringify(opts.consumes || ["*"]);
102
+ useEffect(() => {
103
+ setEvents(/* @__PURE__ */ new Map());
104
+ setCaughtUp(false);
105
+ setError(void 0);
106
+ if (!itx) return;
107
+ let disposed = false;
108
+ const merge = (batch) => setEvents((held) => {
109
+ const next = new Map(held);
110
+ for (const raw of batch) {
111
+ const event = toIterateContextEvent(raw);
112
+ if (event) next.set(event.offset, event);
113
+ }
114
+ return next;
115
+ });
116
+ let subscription;
117
+ (async () => {
118
+ const handle = await itx.subscribe({
119
+ consumes: JSON.parse(consumesKey),
120
+ target: (batch) => !disposed && merge(batch)
121
+ });
122
+ if (disposed) {
123
+ handle[Symbol.dispose]();
124
+ return;
125
+ }
126
+ subscription = handle;
127
+ for (let after = 0;;) {
128
+ const page = await itx.readEvents(after, 500);
129
+ if (disposed) return;
130
+ merge(page.events);
131
+ if (page.atHead || page.scannedThroughOffset <= after) break;
132
+ after = page.scannedThroughOffset;
133
+ }
134
+ setCaughtUp(true);
135
+ })().catch((e) => !disposed && setError(e instanceof Error ? e.message : String(e)));
136
+ return () => {
137
+ disposed = true;
138
+ subscription?.[Symbol.dispose]();
139
+ };
140
+ }, [itx, consumesKey]);
141
+ const sorted = useMemo(() => [...events.values()].sort((a, b) => a.offset - b.offset), [events]);
142
+ const [table, setTable] = useState();
143
+ const [failure, setFailure] = useState();
144
+ useEffect(() => {
145
+ if (!itx) return;
146
+ let disposed = false;
147
+ Promise.resolve(itx.processors.list()).then((list) => {
148
+ if (disposed) return;
149
+ setTable({
150
+ itx,
151
+ rows: list
152
+ });
153
+ setFailure(void 0);
154
+ }, (e) => !disposed && setFailure({
155
+ itx,
156
+ message: e instanceof Error ? e.message : String(e)
157
+ }));
158
+ return () => {
159
+ disposed = true;
160
+ };
161
+ }, [itx, sorted.reduce((last, event) => event.type.startsWith("events.iterate.com/stream/subscription-") ? event.offset : last, 0)]);
162
+ const currentTable = itx && table?.itx === itx ? table : void 0;
163
+ const [census, setCensus] = useState();
164
+ useEffect(() => {
165
+ if (!itx?.rpcStubs) return;
166
+ let disposed = false;
167
+ Promise.resolve(itx.rpcStubs.list()).then((list) => !disposed && setCensus({
168
+ itx,
169
+ rpcStubs: list
170
+ }), () => void 0);
171
+ return () => {
172
+ disposed = true;
173
+ };
174
+ }, [itx, sorted.at(-1)?.offset ?? 0]);
175
+ const rpcStubs = itx && census?.itx === itx ? census.rpcStubs : [];
176
+ const actors = useMemo(() => {
177
+ const byActor = /* @__PURE__ */ new Map();
178
+ for (const event of sorted) {
179
+ const principal = event.source?.principal;
180
+ if (!principal) continue;
181
+ byActor.set(principal.actor, {
182
+ actor: principal.actor,
183
+ email: principal.email,
184
+ grant: event.source?.grant,
185
+ lastSeenAt: event.createdAt
186
+ });
187
+ }
188
+ return [...byActor.values()].sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
189
+ }, [sorted]);
190
+ const liveStateKey = JSON.stringify(opts.liveState || ["core", ...(currentTable?.rows || []).flatMap((row) => row.hostedFacet ? [row.hostedFacet.name] : [])]);
191
+ const [liveStates, setLiveStates] = useState();
192
+ useEffect(() => {
193
+ if (!itx) return;
194
+ const names = JSON.parse(liveStateKey);
195
+ if (names.length === 0) return;
196
+ let disposed = false;
197
+ const unmounted = new AbortController();
198
+ const disposers = [];
199
+ const patch = (name, change) => setLiveStates((held) => held && held.itx === itx && held.key === liveStateKey ? {
200
+ ...held,
201
+ entries: {
202
+ ...held.entries,
203
+ [name]: {
204
+ ...held.entries[name],
205
+ ...change
206
+ }
207
+ }
208
+ } : held);
209
+ setLiveStates({
210
+ itx,
211
+ key: liveStateKey,
212
+ entries: Object.fromEntries(names.map((name) => [name, LIVE_STATE_CONNECTING]))
213
+ });
214
+ for (const name of names) connectLiveState(itx, {
215
+ key: name,
216
+ door: async () => await itx.invoke(`itx.facets.get('${name}').liveSnapshot()`),
217
+ signal: unmounted.signal,
218
+ onResync: (result) => {
219
+ if (disposed) return;
220
+ if (result === "healed") patch(name, {
221
+ status: "live",
222
+ error: void 0
223
+ });
224
+ else patch(name, {
225
+ status: "error",
226
+ error: result.message
227
+ });
228
+ }
229
+ }).then((connection) => {
230
+ if (disposed) {
231
+ connection.dispose();
232
+ return;
233
+ }
234
+ disposers.push(connection.dispose);
235
+ disposers.push(connection.store.subscribe(() => patch(name, {
236
+ value: connection.store.get(),
237
+ rev: connection.store.rev()
238
+ })));
239
+ patch(name, {
240
+ value: connection.store.get(),
241
+ rev: connection.store.rev(),
242
+ status: "live"
243
+ });
244
+ }, (e) => {
245
+ if (disposed) return;
246
+ patch(name, {
247
+ status: "error",
248
+ error: e instanceof Error ? e.message : String(e)
249
+ });
250
+ });
251
+ return () => {
252
+ disposed = true;
253
+ unmounted.abort();
254
+ for (const dispose of disposers) dispose();
255
+ };
256
+ }, [itx, liveStateKey]);
257
+ const liveState = useMemo(() => {
258
+ const names = JSON.parse(liveStateKey);
259
+ const held = itx && liveStates?.itx === itx && liveStates.key === liveStateKey ? liveStates.entries : {};
260
+ return Object.fromEntries(names.map((name) => [name, held[name] || LIVE_STATE_CONNECTING]));
261
+ }, [
262
+ itx,
263
+ liveStateKey,
264
+ liveStates
265
+ ]);
266
+ return {
267
+ events: sorted,
268
+ caughtUp,
269
+ error,
270
+ processors: {
271
+ rows: currentTable?.rows || [],
272
+ loaded: Boolean(currentTable),
273
+ error: itx && failure?.itx === itx ? failure.message : void 0
274
+ },
275
+ presence: {
276
+ actors,
277
+ rpcStubs
278
+ },
279
+ liveState
280
+ };
281
+ }
282
+ //#endregion
283
+ export { useIterateContext, useLiveState };
284
+
285
+ //# sourceMappingURL=react.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.mjs","names":[],"sources":["../../src/next/client/react.tsx"],"sourcesContent":["/** @jsxImportSource react */\n// client/react.tsx — the REACT binding for clean-room live state, shared by every UI (the hosted\n// /demo and the control-plane console). `useLiveState` subscribes a component to a producer's live\n// state (a processor slug, a mini-app key), seeds through its door, and re-renders on every synced\n// delta via `useSyncExternalStore` over the LiveStateStore. The transport and the store\n// (client/live-state.ts) stay framework-free, so this is the ONE file that imports React.\n//\n// Adapted from apps/os's `useLiveState` (packages/iterate/src/sdk/capnweb/react.tsx), kept to the one\n// shape a UI/test needs — no reconnect/backoff/ping-watchdog (that policy belongs to whoever owns the\n// capnweb session; here the caller passes a ready `itx`).\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n connectLiveState,\n type LiveStateItx,\n type LiveStateSeed,\n type LiveStateStore,\n} from \"./live-state.ts\";\n\nexport type LiveStateStatus = \"connecting\" | \"live\" | \"error\";\n\n/** One live state as a component reads it: the latest value (undefined until the first seed lands),\n * the revision it is at, whether its subscription is connecting, live or failed, and the failure. */\nexport type LiveStateResult<S = unknown> = {\n value: S | undefined;\n rev: number | null;\n status: LiveStateStatus;\n error?: string;\n};\n\n/** Subscribe to a producer's live state and render its latest value. Pass a ready `itx` (a capnweb\n * `api.authenticate(credentials).user` or `.projects.get(id)`), the producer's `key`, and a `door`\n * thunk that reads `{rev, state}` (`() => itx.invoke(\"itx.facets.get('slug').liveSnapshot()\")`).\n * Re-subscribes when the session, `key`, or `name` changes; unmount (and every re-subscribe)\n * disposes the previous server-side subscription. */\nexport function useLiveState<S>(\n itx: LiveStateItx | undefined,\n opts: { key: string; name?: string; door: () => Promise<LiveStateSeed<S>> },\n): LiveStateResult<S> {\n const [store, setStore] = useState<LiveStateStore<S> | undefined>();\n const [status, setStatus] = useState<LiveStateStatus>(\"connecting\");\n const [error, setError] = useState<string | undefined>();\n // The door thunk is a fresh arrow every render; hold the latest so the effect need not re-run per\n // render. The effect SNAPSHOTS it at connect time, so an old subscription's gap heal can never\n // read a NEWER key's door (cross-key contamination after a key/session switch).\n const doorRef = useRef(opts.door);\n doorRef.current = opts.door;\n\n useEffect(() => {\n setStore(undefined);\n setStatus(\"connecting\");\n setError(undefined);\n if (!itx) return;\n const door = doorRef.current; // pinned to THIS key/session for the connection's whole life\n let disposed = false;\n let dispose: (() => Promise<void>) | undefined;\n const unmounted = new AbortController(); // an unmount while the first seed is pending recalls the row\n connectLiveState<S>(itx, {\n key: opts.key,\n name: opts.name,\n door,\n signal: unmounted.signal,\n onResync: (r) => {\n if (disposed) return;\n if (r === \"healed\") {\n setStatus(\"live\");\n setError(undefined);\n } else {\n // the store keeps its last value; the next delta retries the heal\n setStatus(\"error\");\n setError(r.message);\n }\n },\n }).then(\n (conn) => {\n dispose = conn.dispose;\n if (disposed) {\n void conn.dispose(); // unmounted while connecting — still tear the mount down\n return;\n }\n setStore(conn.store);\n setStatus(\"live\");\n },\n (e: unknown) => {\n if (disposed) return;\n setError(e instanceof Error ? e.message : String(e));\n setStatus(\"error\");\n },\n );\n return () => {\n disposed = true;\n unmounted.abort();\n void dispose?.();\n };\n }, [itx, opts.key, opts.name]);\n\n const subscribe = useCallback(\n (cb: () => void) => (store ? store.subscribe(cb) : () => {}),\n [store],\n );\n const value = useSyncExternalStore(\n subscribe,\n () => store?.get(),\n () => undefined,\n );\n return { value, rev: store?.rev() ?? null, status, error };\n}\n\n// ── the iterate context ── the data half of a general-purpose context view (packages/ui\n// `components/context-view`, the rendering half): every committed event of a context, live; the rows\n// of its processors table; who is here; named facets' live state. ONE hook here, pure components\n// there, so the UI kit stays free of the SDK and any app — the dash, the agents app — composes the two.\n\n/** One committed event as the hook hands it out: the itx envelope, structurally. */\nexport type IterateContextEvent = {\n offset: number;\n type: string;\n createdAt: string;\n payload?: unknown;\n metadata?: Record<string, unknown>;\n idempotencyKey?: string;\n source?: {\n principal?: { actor: string; email?: string };\n grant?: string;\n processor?: { slug: string; version: string };\n };\n};\n\n/** One row of a context's processors table (`itx.processors.list()`), structurally. */\nexport type IterateContextProcessorRow = {\n name: string;\n target: string;\n consumes?: string[];\n configuredAtOffset: number;\n hostedFacet?: { name: string; className: string; cacheKey?: string; restarts: number };\n};\n\n/** One presence: who acted on the context and when last, from the log's stamps. */\nexport type IterateContextPresence = {\n actor: string;\n email?: string;\n grant?: string;\n lastSeenAt: string;\n};\n\n/** The slice of a context handle `useIterateContext` reads — a capnweb `IterateContextApi` stub\n * satisfies it structurally. `rpcStubs` is optional: a handle typed without the census (a project\n * context's client type) still gets the log, the table and the actors. `invoke` seeds a named\n * facet's live state (`itx.facets.get('<name>').liveSnapshot()`, as an expression). */\nexport type IterateContextHandle = LiveStateItx & {\n readEvents(\n afterOffset?: number,\n limit?: number,\n ): Promise<{ events: unknown[]; atHead: boolean; scannedThroughOffset: number }>;\n processors: { list(): Promise<IterateContextProcessorRow[]> | IterateContextProcessorRow[] };\n rpcStubs?: { list(): Promise<string[]> | string[] };\n invoke(call: string): Promise<unknown>;\n};\n\n/** A wire event (a capnweb proxy value or a plain object) as an `IterateContextEvent`, or null when\n * it is not a committed row. Structural, not a schema: the transport validated it; this only refuses\n * a shape the view cannot place (no offset, type or time). */\nfunction toIterateContextEvent(raw: unknown): IterateContextEvent | null {\n const value = JSON.parse(JSON.stringify(raw)) as Record<string, unknown> | null;\n if (\n !value ||\n typeof value.offset !== \"number\" ||\n typeof value.type !== \"string\" ||\n typeof value.createdAt !== \"string\"\n )\n return null;\n return value as unknown as IterateContextEvent; // the three fields checked are all the hook indexes by\n}\n\n/** A named live state before its first seed lands — and before the effect that opens it has run. */\nconst LIVE_STATE_CONNECTING: LiveStateResult = {\n value: undefined,\n rev: null,\n status: \"connecting\",\n};\n\n/** THE ITERATE CONTEXT, live — one hook, one stream subscription. THE LOG: subscribe to every\n * committed event (or `consumes`) BEFORE the catch-up read, so nothing lands between the two;\n * pushes and pages both dedupe into one map by offset; `caughtUp` once the read reached the head;\n * `error` when the connect failed. Off that same log, THE PROCESSORS TABLE, re-read whenever the\n * log grows a row-changing event (a subscription configured, halted or resumed — the table is core\n * state, one call away, no push of its own), and WHO IS HERE: the rpc stubs lent right now\n * (`itx.rpcStubs.list()` — physical, re-read at every new head, since presence changes are\n * ephemeral facts) and, from the log, every principal that acted, newest first. And named facets'\n * LIVE STATE, each seeded through `itx.facets.get('<name>').liveSnapshot()` — one entry per name,\n * always. `liveState` OMITTED opens `core` (the core reduce answers under that name) plus every\n * hosted facet in the processors table the hook holds, following the table as it loads and changes;\n * `liveState` GIVEN is exactly the names to open, no implicit `core`. Re-connects when `itx`\n * changes; unmount disposes every server-side subscription. */\nexport function useIterateContext(\n itx: IterateContextHandle | undefined,\n opts: { consumes?: string[]; liveState?: string[] } = {},\n): {\n events: IterateContextEvent[];\n caughtUp: boolean;\n error?: string;\n processors: { rows: IterateContextProcessorRow[]; loaded: boolean; error?: string };\n presence: { actors: IterateContextPresence[]; rpcStubs: string[] };\n liveState: Record<string, LiveStateResult>;\n} {\n // ── the log ──\n const [events, setEvents] = useState<Map<number, IterateContextEvent>>(() => new Map());\n const [caughtUp, setCaughtUp] = useState(false);\n const [error, setError] = useState<string | undefined>();\n const consumesKey = JSON.stringify(opts.consumes || [\"*\"]);\n useEffect(() => {\n setEvents(new Map());\n setCaughtUp(false);\n setError(undefined);\n if (!itx) return;\n let disposed = false;\n const merge = (batch: unknown[]) =>\n setEvents((held) => {\n const next = new Map(held);\n for (const raw of batch) {\n const event = toIterateContextEvent(raw);\n if (event) next.set(event.offset, event);\n }\n return next;\n });\n let subscription: { [Symbol.dispose](): void } | undefined;\n (async () => {\n const handle = await itx.subscribe({\n consumes: JSON.parse(consumesKey) as string[],\n target: (batch) => !disposed && merge(batch),\n });\n // An unmount while the subscribe was pending ran the cleanup before this handle existed:\n // release it here, or the server keeps delivering to nobody.\n if (disposed) {\n handle[Symbol.dispose]();\n return;\n }\n subscription = handle;\n for (let after = 0; ; ) {\n const page = await itx.readEvents(after, 500);\n if (disposed) return;\n merge(page.events);\n if (page.atHead || page.scannedThroughOffset <= after) break;\n after = page.scannedThroughOffset;\n }\n setCaughtUp(true);\n })().catch((e: unknown) => !disposed && setError(e instanceof Error ? e.message : String(e)));\n return () => {\n disposed = true;\n subscription?.[Symbol.dispose]();\n };\n }, [itx, consumesKey]);\n const sorted = useMemo(() => [...events.values()].sort((a, b) => a.offset - b.offset), [events]);\n\n // ── the processors table ──\n // The table and the last failure remember WHICH itx they came from: a page that swaps contexts\n // (one route, another organization) shows an empty, not-yet-loaded table for the new one rather\n // than the old one's rows or error until the new read lands.\n const [table, setTable] = useState<{\n itx: IterateContextHandle;\n rows: IterateContextProcessorRow[];\n }>();\n const [failure, setFailure] = useState<{ itx: IterateContextHandle; message: string }>();\n const tableVersion = sorted.reduce(\n (last, event) =>\n event.type.startsWith(\"events.iterate.com/stream/subscription-\") ? event.offset : last,\n 0,\n );\n useEffect(() => {\n if (!itx) return;\n let disposed = false;\n Promise.resolve(itx.processors.list()).then(\n (list) => {\n if (disposed) return;\n setTable({ itx, rows: list });\n setFailure(undefined); // a read that recovered clears the last failure\n },\n (e: unknown) =>\n !disposed && setFailure({ itx, message: e instanceof Error ? e.message : String(e) }),\n );\n return () => {\n disposed = true;\n };\n }, [itx, tableVersion]);\n const currentTable = itx && table?.itx === itx ? table : undefined;\n\n // ── who is here ──\n const [census, setCensus] = useState<{ itx: IterateContextHandle; rpcStubs: string[] }>();\n const head = sorted.at(-1)?.offset ?? 0;\n useEffect(() => {\n if (!itx?.rpcStubs) return;\n let disposed = false;\n Promise.resolve(itx.rpcStubs.list()).then(\n (list) => !disposed && setCensus({ itx, rpcStubs: list }),\n () => undefined, // presence is nice to have; a failed census shows nothing\n );\n return () => {\n disposed = true;\n };\n }, [itx, head]);\n // keyed by its itx: a swapped context shows no census until its own lands\n const rpcStubs = itx && census?.itx === itx ? census.rpcStubs : [];\n const actors = useMemo(() => {\n const byActor = new Map<string, IterateContextPresence>();\n for (const event of sorted) {\n const principal = event.source?.principal;\n if (!principal) continue;\n byActor.set(principal.actor, {\n actor: principal.actor,\n email: principal.email,\n grant: event.source?.grant,\n lastSeenAt: event.createdAt,\n });\n }\n return [...byActor.values()].sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));\n }, [sorted]);\n\n // ── named facets' live state ──\n // N subscriptions in ONE effect keyed by the name set — it changes at runtime as the processors\n // table loads (the default set is `core` plus the table's hosted facets) — since hooks cannot run\n // in a loop: client/live-state.ts's store reduces each, and this mirrors every change into React\n // state. The entries remember WHICH itx and name set they came from (as the table does), so a\n // swapped context or a changed set shows fresh connecting entries, never the last one's values.\n const liveStateKey = JSON.stringify(\n opts.liveState || [\n \"core\",\n ...(currentTable?.rows || []).flatMap((row) =>\n row.hostedFacet ? [row.hostedFacet.name] : [],\n ),\n ],\n );\n const [liveStates, setLiveStates] = useState<{\n itx: IterateContextHandle;\n key: string;\n entries: Record<string, LiveStateResult>;\n }>();\n useEffect(() => {\n if (!itx) return;\n const names = JSON.parse(liveStateKey) as string[];\n if (names.length === 0) return;\n let disposed = false;\n const unmounted = new AbortController(); // an unmount while a first seed is pending recalls that row\n const disposers: Array<() => void | Promise<void>> = [];\n const patch = (name: string, change: Partial<LiveStateResult>) =>\n setLiveStates((held) =>\n held && held.itx === itx && held.key === liveStateKey\n ? { ...held, entries: { ...held.entries, [name]: { ...held.entries[name], ...change } } }\n : held,\n );\n setLiveStates({\n itx,\n key: liveStateKey,\n entries: Object.fromEntries(names.map((name) => [name, LIVE_STATE_CONNECTING])),\n });\n for (const name of names) {\n connectLiveState<unknown>(itx, {\n key: name,\n door: async () =>\n // the engine's own `{ rev, state }` seed, as `liveSnapshot()` answers it\n (await itx.invoke(`itx.facets.get('${name}').liveSnapshot()`)) as LiveStateSeed<unknown>,\n signal: unmounted.signal,\n onResync: (result) => {\n if (disposed) return;\n if (result === \"healed\") patch(name, { status: \"live\", error: undefined });\n // the store keeps its last value; the next delta retries the heal\n else patch(name, { status: \"error\", error: result.message });\n },\n }).then(\n (connection) => {\n if (disposed) {\n void connection.dispose(); // unmounted while connecting — still tear the row down\n return;\n }\n disposers.push(connection.dispose);\n disposers.push(\n connection.store.subscribe(() =>\n patch(name, { value: connection.store.get(), rev: connection.store.rev() }),\n ),\n );\n patch(name, {\n value: connection.store.get(),\n rev: connection.store.rev(),\n status: \"live\",\n });\n },\n (e: unknown) => {\n if (disposed) return;\n patch(name, { status: \"error\", error: e instanceof Error ? e.message : String(e) });\n },\n );\n }\n return () => {\n disposed = true;\n unmounted.abort();\n for (const dispose of disposers) void dispose();\n };\n }, [itx, liveStateKey]);\n // One entry per name, always: a name the effect has not reached yet (the render right after the\n // set changed) reads as connecting rather than missing.\n const liveState = useMemo(() => {\n const names = JSON.parse(liveStateKey) as string[];\n const held =\n itx && liveStates?.itx === itx && liveStates.key === liveStateKey ? liveStates.entries : {};\n return Object.fromEntries(names.map((name) => [name, held[name] || LIVE_STATE_CONNECTING]));\n }, [itx, liveStateKey, liveStates]);\n\n return {\n events: sorted,\n caughtUp,\n error,\n processors: {\n rows: currentTable?.rows || [],\n loaded: Boolean(currentTable),\n error: itx && failure?.itx === itx ? failure.message : undefined,\n },\n presence: { actors, rpcStubs },\n liveState,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** Project ingress strips public identity headers and stamps the verified caller.
2
+ * This guard runs locally in the config worker, before it proxies an app. */
3
+ export declare const auth: {
4
+ require(request: Request): Response | null;
5
+ };
@@ -0,0 +1,112 @@
1
+ import { DurableObject, WorkerEntrypoint } from "cloudflare:workers";
2
+ import type { IterateContextApi } from "../api.ts";
3
+ import { type ScannedRange, type StreamProcessor, type StreamEvent, type StreamEventInput } from "../stream/processor.ts";
4
+ import { auth } from "./auth.ts";
5
+ export { auth };
6
+ export { StreamProcessor, defineProcessorContract, jsonEqual, type ConsumedEvent, type EventCatalog, type EventDefinition, type EmittedEventInput, type EventInput, type ProcessorContract, type ProcessorState, type ProcessorStream, type ProcessEventArgs, type ReduceArgs, type ScannedRange, type StreamEvent, type StreamEventInput, } from "../stream/processor.ts";
7
+ export { z } from "zod";
8
+ export { newHttpBatchRpcSession, newWebSocketRpcSession, newWorkersRpcResponse } from "capnweb";
9
+ export { applyPatch, diff, type PatchOp } from "../lib.ts";
10
+ export { LiveState, type LiveStateSink } from "../stream/processor.ts";
11
+ /** What the parent mints the class with — the whole identity. */
12
+ export type StreamProcessorProps = {
13
+ iterateContextName: string;
14
+ name: string;
15
+ };
16
+ /** The itx scope as `env.ITX.get()` hands it over: a context's declared API (api.ts) — a capnweb stub
17
+ * of os-next's `IterateContextRpcTarget`, which satisfies it. */
18
+ export type ItxScope = IterateContextApi;
19
+ /** What hands the scope over: the loopback entrypoint a loaded worker has as `env.ITX`, or the one a
20
+ * class of the platform's own worker mints from `ctx.exports`. */
21
+ export type ItxEntrypointService = {
22
+ get(): ItxScope;
23
+ };
24
+ /** The least a host needs of its scope: the fixed-point log calls the engine makes. The platform's own
25
+ * facets pass the Workers-RPC STUB of a context (every dotted step pipelined; a property there is a
26
+ * promise), which no plain-promise interface can name — so the constraint is this, not `ItxScope`. */
27
+ export type ProcessorScope = {
28
+ append(...events: StreamEventInput[]): Promise<unknown>;
29
+ readEvents(afterOffset?: number, limit?: number): Promise<unknown>;
30
+ /** The engine's claim on the context's alarm (processor.ts rule 3): "come back by `at`", or null. */
31
+ processors: {
32
+ claim(name: string, at: number | null): Promise<unknown>;
33
+ };
34
+ /** Another context of the project — where `appendTo` lands — by its dotted surface (`.append`),
35
+ * which the platform's handle and a loaded worker's alike answer. Through the table like every
36
+ * other word here: a loaded processor's `cd` goes down only (the app wall), the platform's own
37
+ * go anywhere. */
38
+ cd(path: string): {
39
+ append(...events: StreamEventInput[]): Promise<unknown>;
40
+ };
41
+ };
42
+ /** THE SCOPE ACCESSOR a host hands its processor: one pipelined round trip on the context's itx,
43
+ * released after (`StreamProcessorDurableObject.withItx`). A processor that needs an effect —
44
+ * `itx.cfArtifacts.create(path)`, `itx.ai.run(…)` — takes this and nothing else, so a unit test
45
+ * hands it a fake and the e2e lends one by rule on the context. */
46
+ export type WithItx<Scope = ItxScope> = <T>(call: (itx: Scope) => T) => Promise<Awaited<T>>;
47
+ export declare abstract class StreamProcessorDurableObject<State = unknown, Env extends {
48
+ ITX?: ItxEntrypointService;
49
+ } = {
50
+ ITX: ItxEntrypointService;
51
+ }, Scope extends ProcessorScope = ItxScope> extends DurableObject<Env, StreamProcessorProps> {
52
+ #private;
53
+ /** The processor this object hosts — `processor = new PresenceProcessor()` at the top of the subclass. */
54
+ abstract readonly processor: StreamProcessor<State>;
55
+ /** After a runtime field on the processor moved OUTSIDE a batch (an RPC method on this object);
56
+ * inside `processEvent` the engine re-projects on its own. */
57
+ protected publishLiveState(): void;
58
+ /** THE push door: the context hands over each committed batch with its scanned-range proof. */
59
+ processEventBatch(events: StreamEvent[], range: ScannedRange): Promise<void>;
60
+ /** Catch up from the log (the read-your-writes entry after an eviction). */
61
+ catchUpFromLog(): Promise<void>;
62
+ /** THE REVIVE: the context's alarm pass calls it for a due claim — catch up, then run the
63
+ * at-head pass, so an attempt the last incarnation was running is started again from state. */
64
+ revive(): Promise<void>;
65
+ /** Caught up through the log, then `{ offset, state }`. */
66
+ snapshot(): Promise<{
67
+ offset: number;
68
+ state: State;
69
+ }>;
70
+ /** The live-state seed door: `{ rev, state: projectLiveState(reduced) }`. */
71
+ liveSnapshot(): Promise<{
72
+ rev: number;
73
+ state: unknown;
74
+ }>;
75
+ /** The barrier: resolves once processed at least through `offset` (default timeout 10s). */
76
+ waitUntilProcessed(input: {
77
+ offset: number;
78
+ timeoutMs?: number;
79
+ }): Promise<void>;
80
+ /** ONE pipelined round trip on the itx scope, then RELEASE it: `env.ITX.get()` and the call
81
+ * pipelined on it PIN THE PARENT DO until GC (the "GC is too late" defect the DO's facet door
82
+ * fixes in the other direction). Await the answer — plain data, the wire already copied it —
83
+ * then dispose the call AND the get. Protected: a host with methods of its own (the workspace,
84
+ * src/workspace/durable-object.ts) reaches its context the same way. */
85
+ protected withItx<T>(call: (itx: Scope) => T): Promise<Awaited<T>>;
86
+ }
87
+ export type ConfigWorkerItx = ItxScope;
88
+ export type ConfigEventArgs = {
89
+ event: StreamEvent;
90
+ range: ScannedRange;
91
+ itx: ConfigWorkerItx;
92
+ };
93
+ export declare abstract class ConfigWorker<Env extends {
94
+ ITX: ItxEntrypointService;
95
+ } = {
96
+ ITX: ItxEntrypointService;
97
+ }> extends WorkerEntrypoint<Env> {
98
+ /** At fetch entry: `const denied = this.auth.require(request); if (denied) return denied;` */
99
+ protected readonly auth: {
100
+ require(request: Request): Response | null;
101
+ };
102
+ /** Process an explicitly subscribed batch with this worker's context scope. */
103
+ processEventBatch(events: StreamEvent[], range: ScannedRange): Promise<void>;
104
+ /** THE AUTHOR HOOK — one event at a time, in offset order. Append reactions through the itx scope;
105
+ * make them idempotent (a redelivery must be a no-op). Default: ignore the event. */
106
+ processEvent(_args: ConfigEventArgs): void | Promise<void>;
107
+ /** THE WEB ROOT — the Request a project host with no app label rode in on (`x-iterate-app` absent;
108
+ * the project's configured ingress target). Route by hostname to `this.env.ITX.get().apps.<x>.fetch(request)`
109
+ * or answer it here. Default: not found. */
110
+ fetch(_request: Request): Response | Promise<Response>;
111
+ }
112
+ export { RunContract, RunRequested, RunSettled } from "../stream/run.ts";