iterate 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/README.md +164 -82
  2. package/dist/{next/api.d.ts → api.d.ts} +197 -33
  3. package/dist/{next/app-server.d.ts → app-server.d.ts} +7 -0
  4. package/dist/{next/app-server.mjs → app-server.mjs} +17 -15
  5. package/dist/app-server.mjs.map +1 -0
  6. package/dist/{next/app-session.mjs → app-session.mjs} +12 -15
  7. package/dist/app-session.mjs.map +1 -0
  8. package/dist/{next/app.mjs → app.mjs} +53 -14
  9. package/dist/app.mjs.map +1 -0
  10. package/dist/{next/client → client}/live-state.d.ts +11 -11
  11. package/dist/client/oauth.d.ts +17 -0
  12. package/dist/{next/client → client}/react.d.ts +10 -42
  13. package/dist/{next/client → client}/socket.d.ts +1 -0
  14. package/dist/client.mjs +156 -4
  15. package/dist/client.mjs.map +1 -0
  16. package/dist/{next/expression.d.ts → expression.d.ts} +11 -69
  17. package/dist/{next/expression.mjs → expression.mjs} +11 -109
  18. package/dist/expression.mjs.map +1 -0
  19. package/dist/lib-BWr-5mFO.mjs +36 -0
  20. package/dist/lib-BWr-5mFO.mjs.map +1 -0
  21. package/dist/{next/lib.d.ts → lib.d.ts} +16 -2
  22. package/dist/{next/lib.mjs → lib.mjs} +32 -3
  23. package/dist/lib.mjs.map +1 -0
  24. package/dist/node.d.ts +15 -3
  25. package/dist/node.mjs +36 -174
  26. package/dist/node.mjs.map +1 -1
  27. package/dist/{next/oauth-scopes.mjs → oauth-scopes.mjs} +1 -1
  28. package/dist/oauth-scopes.mjs.map +1 -0
  29. package/dist/{next/oauth.mjs → oauth.mjs} +14 -2
  30. package/dist/oauth.mjs.map +1 -0
  31. package/dist/principal.d.ts +8 -0
  32. package/dist/principal.mjs +8 -0
  33. package/dist/principal.mjs.map +1 -0
  34. package/dist/project-ingress.d.ts +58 -0
  35. package/dist/project-ingress.mjs +104 -0
  36. package/dist/project-ingress.mjs.map +1 -0
  37. package/dist/{next/react.mjs → react.mjs} +12 -12
  38. package/dist/react.mjs.map +1 -0
  39. package/dist/sdk/auth.d.ts +25 -0
  40. package/dist/sdk/index.d.ts +155 -0
  41. package/dist/sdk/record-pipelined-steps.d.ts +19 -0
  42. package/dist/sdk.mjs +245 -2
  43. package/dist/sdk.mjs.map +1 -0
  44. package/dist/{next/stream → stream}/processor.d.ts +28 -23
  45. package/dist/{next/stream → stream}/processor.mjs +48 -25
  46. package/dist/stream/processor.mjs.map +1 -0
  47. package/dist/{next/stream → stream}/run.d.ts +9 -6
  48. package/dist/{next/stream → stream}/run.mjs +13 -8
  49. package/dist/stream/run.mjs.map +1 -0
  50. package/dist/stream/test-support.d.ts +45 -0
  51. package/dist/stream/test-support.mjs +196 -0
  52. package/dist/stream/test-support.mjs.map +1 -0
  53. package/package.json +65 -219
  54. package/THIRD_PARTY_NOTICES.md +0 -55
  55. package/bin/iterate.js +0 -94
  56. package/dist/api-url-B6404M82.mjs +0 -17
  57. package/dist/api-url-B6404M82.mjs.map +0 -1
  58. package/dist/app-ref-BipL0feU.mjs +0 -35
  59. package/dist/app-ref-BipL0feU.mjs.map +0 -1
  60. package/dist/app-ref-C1CrgXqX.mjs +0 -7
  61. package/dist/app-ref-C1CrgXqX.mjs.map +0 -1
  62. package/dist/app-ref-DYai_om1.mjs +0 -7
  63. package/dist/app-ref-DYai_om1.mjs.map +0 -1
  64. package/dist/cli-D0c-pDL_.mjs +0 -1010
  65. package/dist/cli-D0c-pDL_.mjs.map +0 -1
  66. package/dist/client.d.ts +0 -3
  67. package/dist/cloudflare-BTm90gQ4.mjs +0 -951
  68. package/dist/cloudflare-BTm90gQ4.mjs.map +0 -1
  69. package/dist/contract-s4FW4eES.mjs +0 -309
  70. package/dist/contract-s4FW4eES.mjs.map +0 -1
  71. package/dist/document-review/index.d.ts +0 -5
  72. package/dist/document-review/types.d.ts +0 -107
  73. package/dist/document-review.mjs +0 -7015
  74. package/dist/document-review.mjs.map +0 -1
  75. package/dist/durable-object-processor-durability-CNsTjAJS.mjs +0 -205
  76. package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +0 -1
  77. package/dist/idempotency-DleloJNt.mjs +0 -28
  78. package/dist/idempotency-DleloJNt.mjs.map +0 -1
  79. package/dist/index.d.mts +0 -5
  80. package/dist/index.mjs +0 -8
  81. package/dist/index.mjs.map +0 -1
  82. package/dist/itx/api-url.d.ts +0 -6
  83. package/dist/itx/itx-node-client.d.ts +0 -65
  84. package/dist/itx/itx-session.d.ts +0 -215
  85. package/dist/itx/owned-rpc-session.d.ts +0 -14
  86. package/dist/itx/query-client.d.ts +0 -10
  87. package/dist/itx-api.generated.d.ts +0 -6195
  88. package/dist/itx-session-sjud8GiT.mjs +0 -534
  89. package/dist/itx-session-sjud8GiT.mjs.map +0 -1
  90. package/dist/live-state-BJNqOwFw.mjs +0 -299
  91. package/dist/live-state-BJNqOwFw.mjs.map +0 -1
  92. package/dist/next/app-server.mjs.map +0 -1
  93. package/dist/next/app-session.mjs.map +0 -1
  94. package/dist/next/app.mjs.map +0 -1
  95. package/dist/next/client/oauth.d.ts +0 -12
  96. package/dist/next/client.mjs +0 -156
  97. package/dist/next/client.mjs.map +0 -1
  98. package/dist/next/expression.mjs.map +0 -1
  99. package/dist/next/lib.mjs.map +0 -1
  100. package/dist/next/oauth-scopes.mjs.map +0 -1
  101. package/dist/next/oauth.mjs.map +0 -1
  102. package/dist/next/principal.d.ts +0 -64
  103. package/dist/next/principal.mjs +0 -98
  104. package/dist/next/principal.mjs.map +0 -1
  105. package/dist/next/project-ingress.d.ts +0 -37
  106. package/dist/next/project-ingress.mjs +0 -75
  107. package/dist/next/project-ingress.mjs.map +0 -1
  108. package/dist/next/react.mjs.map +0 -1
  109. package/dist/next/sdk/auth.d.ts +0 -5
  110. package/dist/next/sdk/index.d.ts +0 -112
  111. package/dist/next/sdk.mjs +0 -139
  112. package/dist/next/sdk.mjs.map +0 -1
  113. package/dist/next/stream/processor.mjs.map +0 -1
  114. package/dist/next/stream/run.mjs.map +0 -1
  115. package/dist/next-node.d.ts +0 -15
  116. package/dist/next-node.mjs +0 -51
  117. package/dist/next-node.mjs.map +0 -1
  118. package/dist/processor-host-capabilities-BMFH3KTM.mjs +0 -56
  119. package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +0 -1
  120. package/dist/processors/cloudflare.d.ts +0 -3
  121. package/dist/processors/durable-object-processor-durability.d.ts +0 -79
  122. package/dist/processors/event-consumption-metrics.d.ts +0 -82
  123. package/dist/processors/idempotency.d.ts +0 -13
  124. package/dist/processors/index.d.ts +0 -12
  125. package/dist/processors/processor-contracts.d.ts +0 -342
  126. package/dist/processors/processor-facet.d.ts +0 -186
  127. package/dist/processors/processor-host-capabilities.d.ts +0 -60
  128. package/dist/processors/prompt-sections.d.ts +0 -17
  129. package/dist/processors/rpc-types.d.ts +0 -515
  130. package/dist/processors/schemas.d.ts +0 -102
  131. package/dist/processors/stream-handle.d.ts +0 -45
  132. package/dist/processors/stream-processor-keepalive.d.ts +0 -95
  133. package/dist/processors/stream-processor-registry.d.ts +0 -233
  134. package/dist/processors/stream-processor-runner.d.ts +0 -289
  135. package/dist/processors/stream-processor.d.ts +0 -339
  136. package/dist/processors/stream-runtime-metrics.d.ts +0 -107
  137. package/dist/processors/testing.d.ts +0 -302
  138. package/dist/processors-BoNyeBfQ.mjs +0 -10
  139. package/dist/processors-BoNyeBfQ.mjs.map +0 -1
  140. package/dist/processors-cloudflare.mjs +0 -3
  141. package/dist/processors-testing.mjs +0 -435
  142. package/dist/processors-testing.mjs.map +0 -1
  143. package/dist/processors.mjs +0 -52
  144. package/dist/processors.mjs.map +0 -1
  145. package/dist/protocol-DnK_f2m6.mjs +0 -251
  146. package/dist/protocol-DnK_f2m6.mjs.map +0 -1
  147. package/dist/sdk/capnweb/index.d.ts +0 -2
  148. package/dist/sdk/capnweb/live-state/compact.d.ts +0 -5
  149. package/dist/sdk/capnweb/live-state/diff.d.ts +0 -41
  150. package/dist/sdk/capnweb/live-state/engine.d.ts +0 -44
  151. package/dist/sdk/capnweb/live-state/index.d.ts +0 -41
  152. package/dist/sdk/capnweb/live-state/protocol.d.ts +0 -87
  153. package/dist/sdk/capnweb/live-state/retain.d.ts +0 -23
  154. package/dist/sdk/capnweb/live-state/store.d.ts +0 -20
  155. package/dist/sdk/capnweb/live-state/types.d.ts +0 -11
  156. package/dist/sdk/capnweb/react.d.ts +0 -45
  157. package/dist/sdk/capnweb/react.mjs +0 -316
  158. package/dist/sdk/capnweb/react.mjs.map +0 -1
  159. package/dist/sdk/capnweb.mjs +0 -4
  160. package/dist/sdk/itx/react.d.ts +0 -191
  161. package/dist/sdk/itx/react.mjs +0 -383
  162. package/dist/sdk/itx/react.mjs.map +0 -1
  163. package/dist/sdk-DMB-IM11.mjs +0 -933
  164. package/dist/sdk-DMB-IM11.mjs.map +0 -1
  165. package/dist/sdk.d.ts +0 -339
  166. package/dist/serve-itx.d.ts +0 -46
  167. package/dist/starter-apps/flake-dashboard/app-ref.d.ts +0 -31
  168. package/dist/starter-apps/flake-dashboard/configured-worker.mjs +0 -1055
  169. package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +0 -1
  170. package/dist/starter-apps/flake-dashboard/contract.d.ts +0 -4839
  171. package/dist/starter-apps/flake-dashboard/contract.mjs +0 -2
  172. package/dist/starter-apps/flake-dashboard/index.d.ts +0 -17
  173. package/dist/starter-apps/flake-dashboard/index.mjs +0 -56
  174. package/dist/starter-apps/flake-dashboard/index.mjs.map +0 -1
  175. package/dist/starter-apps/flake-dashboard/worker.d.ts +0 -4607
  176. package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +0 -8914
  177. package/dist/starter-apps/github-ai-linter/configured-worker.mjs +0 -17987
  178. package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +0 -1
  179. package/dist/starter-apps/github-ai-linter/contract.d.ts +0 -9193
  180. package/dist/starter-apps/github-ai-linter/index.d.ts +0 -10
  181. package/dist/starter-apps/github-ai-linter/index.mjs +0 -36
  182. package/dist/starter-apps/github-ai-linter/index.mjs.map +0 -1
  183. package/dist/starter-apps/github-ai-linter/prompt.d.ts +0 -13
  184. package/dist/starter-apps/github-ai-linter/review-bot.d.ts +0 -808
  185. package/dist/starter-apps/github-ai-linter/rules.d.ts +0 -34
  186. package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +0 -19
  187. package/dist/starter-apps/github-ai-linter/worker.d.ts +0 -19
  188. package/dist/starter-apps/github-ai-linter/worker.mjs +0 -947
  189. package/dist/starter-apps/github-ai-linter/worker.mjs.map +0 -1
  190. package/dist/starter-apps/guestbook/app-ref.d.ts +0 -27
  191. package/dist/starter-apps/guestbook/client.d.ts +0 -7
  192. package/dist/starter-apps/guestbook/client.mjs +0 -59
  193. package/dist/starter-apps/guestbook/configured-worker.mjs +0 -205
  194. package/dist/starter-apps/guestbook/configured-worker.mjs.map +0 -1
  195. package/dist/starter-apps/guestbook/index.d.ts +0 -9
  196. package/dist/starter-apps/guestbook/index.mjs +0 -31
  197. package/dist/starter-apps/guestbook/index.mjs.map +0 -1
  198. package/dist/starter-apps/guestbook/processor.d.ts +0 -2267
  199. package/dist/starter-apps/guestbook/worker.d.ts +0 -26
  200. package/dist/starter-apps/guestbook/worker.mjs +0 -191
  201. package/dist/starter-apps/guestbook/worker.mjs.map +0 -1
  202. package/dist/starter-apps/media/configured-worker.mjs +0 -577
  203. package/dist/starter-apps/media/configured-worker.mjs.map +0 -1
  204. package/dist/starter-apps/media/index.mjs +0 -36
  205. package/dist/starter-apps/media/index.mjs.map +0 -1
  206. package/dist/starter-apps/media/ref.mjs +0 -20
  207. package/dist/starter-apps/media/ref.mjs.map +0 -1
  208. package/dist/starter-apps/media/worker.mjs +0 -579
  209. package/dist/starter-apps/media/worker.mjs.map +0 -1
  210. package/dist/starter-apps/notes/configured-worker.mjs +0 -6134
  211. package/dist/starter-apps/notes/configured-worker.mjs.map +0 -1
  212. package/dist/starter-apps/notes/index.mjs +0 -23
  213. package/dist/starter-apps/notes/index.mjs.map +0 -1
  214. package/dist/starter-apps/notes/ref.mjs +0 -21
  215. package/dist/starter-apps/notes/ref.mjs.map +0 -1
  216. package/dist/starter-apps/notes/worker.mjs +0 -427
  217. package/dist/starter-apps/notes/worker.mjs.map +0 -1
  218. package/dist/starter-apps/todo/client.mjs +0 -59
  219. package/dist/starter-apps/todo/configured-worker.mjs +0 -2864
  220. package/dist/starter-apps/todo/configured-worker.mjs.map +0 -1
  221. package/dist/starter-apps/todo/index.d.ts +0 -8
  222. package/dist/starter-apps/todo/index.mjs +0 -29
  223. package/dist/starter-apps/todo/index.mjs.map +0 -1
  224. package/dist/stream-processor-keepalive-DAQTP6m3.mjs +0 -2082
  225. package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +0 -1
  226. package/dist/usingCtx-mZx5nsAW.mjs +0 -11800
  227. package/dist/usingCtx-mZx5nsAW.mjs.map +0 -1
  228. package/dist/worker-ref-DZxPDmb_.mjs +0 -390
  229. package/dist/worker-ref-DZxPDmb_.mjs.map +0 -1
  230. package/dist/worker.d.mts +0 -33
  231. package/dist/worker.mjs +0 -18
  232. package/dist/worker.mjs.map +0 -1
  233. package/menubar/Iterate.entitlements +0 -12
  234. package/menubar/Iterate.swift +0 -914
  235. package/menubar/IterateIcon.swift +0 -145
  236. package/menubar/README.md +0 -28
  237. package/menubar/build-menubar-app.sh +0 -59
  238. /package/dist/{next/api.mjs → api.mjs} +0 -0
  239. /package/dist/{next/app-session.d.ts → app-session.d.ts} +0 -0
  240. /package/dist/{next/app.d.ts → app.d.ts} +0 -0
  241. /package/dist/{next/oauth-scopes.d.ts → oauth-scopes.d.ts} +0 -0
@@ -1,383 +0,0 @@
1
- import { a as disconnectIterateSession, c as reconnectIterateSession, d as retryFailedIterateSession, f as serverSnapshot, i as currentSnapshot, l as releaseItxConnection, m as watchItxConnection, n as connectIterateSession, o as isItxTransportError, p as subscribeSession, r as connectItx, s as projectStubFor, t as configureIterateSession, u as reportTransportSuspicion } from "../../itx-session-sjud8GiT.mjs";
2
- import { CapnWebProvider, useLiveState as useLiveState$1 } from "../capnweb/react.mjs";
3
- import { createContext, createElement, use, useCallback, useContext, useEffect, useState, useSyncExternalStore } from "react";
4
- import { QueryClient, useQuery, useSuspenseQuery } from "@tanstack/react-query";
5
- //#region src/itx/query-client.ts
6
- /**
7
- * One Query client policy for every Iterate React renderer.
8
- *
9
- * The browser dashboard and the OpenTUI client intentionally share these
10
- * defaults. Renderer-specific entrypoints own only where the provider mounts;
11
- * cache lifetime, refetch behavior, and mutation retries must not drift by
12
- * surface.
13
- */
14
- function createIterateQueryClient() {
15
- return new QueryClient({ defaultOptions: {
16
- queries: {
17
- staleTime: 60 * 1e3,
18
- gcTime: 300 * 1e3,
19
- retry: 1,
20
- refetchOnWindowFocus: false,
21
- refetchOnMount: true,
22
- refetchOnReconnect: true
23
- },
24
- mutations: { retry: 0 }
25
- } });
26
- }
27
- //#endregion
28
- //#region src/sdk/itx/react.ts
29
- /**
30
- * `iterate/sdk/itx/react` — hooks over the framework-free session keeper in
31
- * ../../itx/itx-session.ts (one WebSocket per tab, generations, invisible
32
- * reconnect — the session model lives there) plus TanStack Query for reads.
33
- *
34
- * Renderer-agnostic on purpose: nothing here touches the DOM, so the same
35
- * hooks run under react-dom (the OS dashboard), @opentui/react (the chat TUI),
36
- * and React Native — TanStack Query itself only uses React primitives. The
37
- * browser-specific pieces (cookie auth, window-derived URL, visibility/online
38
- * wake probes) live in the keeper behind runtime guards; non-browser apps
39
- * point the keeper at a deployment with `configureIterateSession`.
40
- *
41
- * ───────────────────────────────────────────────────────────────────────────
42
- * THE PRIMITIVES
43
- * ───────────────────────────────────────────────────────────────────────────
44
- *
45
- * SESSION (the catalog authenticate() returned)
46
- * useIterateSession() in render; suspends once on first connect
47
- * connectIterateSession() imperative (handlers/closures); a Promise
48
- *
49
- * ITX (a project capability handle — session.projects.get(slug))
50
- * useItx(slug?) in render; slug (or prj_ id) from the arg or <ProjectScope>
51
- * connectItx(slug) imperative; a Promise
52
- *
53
- * READ ONCE useItxQuery({ key, query }) project read; SUSPENDS
54
- * useIterateSessionQuery({ key, query }) session read; NON-suspending (shell)
55
- * LIVE STATE useLiveState((itx) => itx.liveState, selector) project snapshot + diffs
56
- * useIterateSessionLiveState(...) session snapshot + diffs
57
- * CONNECT useStreamConnection((itx) => handle, deps) raw event batches (escape hatch)
58
- * MOUNT <ProjectScope slug> ambient project + reconnectable Cap'n Web provider
59
- *
60
- * ACTIONS (mutations) — imperative on the handle, no extra primitive:
61
- * const itx = useItx();
62
- * <button onClick={() => itx.chat.sendMessage(text)} />
63
- *
64
- * RECONNECT, AS REACT SEES IT: components read an immutable Snapshot via
65
- * `useSyncExternalStore`; `snapshot.session` holds the LAST live session and is
66
- * kept across a transport gap. So `useIterateSession()` suspends exactly once —
67
- * first load, on the stable first-connect promise — and never again: when the
68
- * socket dies we keep showing the last session while a fresh generation connects
69
- * in the background, then swap when it establishes. TanStack keeps cached read
70
- * data through a reconnect (no re-suspend, no spinner); only an in-flight read
71
- * retries, on the transport-only policy — see useItxQuery. A stray action fired
72
- * during the sub-second gap rides the dead stub and rejects — the one accepted
73
- * edge.
74
- *
75
- * CONNECTING THROWS ON THE SERVER (never SSRs): a forever-pending `use()`
76
- * during streaming SSR would hang the response. Render itx consumers under an
77
- * `ssr: false` route or `<ClientOnly>` + `<Suspense>`.
78
- */
79
- /** The ambient project slug for `useItx()` / `useItxQuery()` — set by <ProjectScope>. */
80
- const ProjectScopeContext = createContext(void 0);
81
- /**
82
- * The Session — the catalog `authenticate()` returned (`projects.list/create`,
83
- * admin `streams`). Suspends exactly ONCE, on first connect; a later reconnect
84
- * keeps returning the last session (no re-suspend — see module header).
85
- */
86
- function useIterateSession() {
87
- const snap = useSyncExternalStore(subscribeSession, currentSnapshot, serverSnapshot);
88
- if (snap.session !== void 0) return snap.session;
89
- return use(snap.connecting);
90
- }
91
- /**
92
- * The project itx for `slug` — `session.projects.get(slug)`, the real capnweb
93
- * stub. `slug` comes from the argument or the nearest <ProjectScope>; an
94
- * explicit argument wins. The returned stub's identity is stable within a socket
95
- * and changes once per reconnect (which re-runs effects/memos keyed on it).
96
- *
97
- * const itx = useItx(); // ambient project (under <ProjectScope>)
98
- * const itx = useItx("other-slug"); // a specific project
99
- * const onSend = () => itx.chat.sendMessage(text);
100
- */
101
- function useItx(explicitSlug) {
102
- const scopedSlug = useContext(ProjectScopeContext);
103
- const slug = explicitSlug ?? scopedSlug;
104
- if (slug === void 0) throw new Error("useItx() needs a project: pass useItx(slug) or render under <ProjectScope slug>.");
105
- return projectStubFor(useIterateSession(), slug);
106
- }
107
- /**
108
- * Set the ambient project for a subtree and provide its reconnectable Cap'n Web
109
- * root. The project-specific layer is deliberately only a connection factory;
110
- * live-state ownership and recovery live in `iterate/sdk/capnweb/react`.
111
- *
112
- * The provider dials in an effect, never during render, so this context remains
113
- * safe to render on the server. The duplicate belongs to the provider; the
114
- * lower-level session keeper keeps owning and reconnecting the shared socket
115
- * beneath it.
116
- */
117
- function ProjectScope({ slug, children }) {
118
- const makeConnection = useCallback(async () => (await connectItx(slug)).dup(), [slug]);
119
- return createElement(ProjectScopeContext, { value: slug }, createElement(CapnWebProvider, { makeConnection }, children));
120
- }
121
- /**
122
- * The shared TanStack retry policy for itx reads: retry ONLY transport-close
123
- * failures (a fresh generation is already reconnecting), briefly — application
124
- * errors surface immediately.
125
- */
126
- const itxTransportRetry = {
127
- retry: (failureCount, error) => isItxTransportError(error) && failureCount < 3,
128
- retryDelay: (failureCount) => Math.min(250 * 2 ** failureCount, 2e3)
129
- };
130
- /**
131
- * Read once through a project itx, suspending until it resolves. A thin adapter
132
- * over TanStack Query's `useSuspenseQuery`. `key` (prefixed with "itx"
133
- * internally) is the cache key — it must encode what the result is scoped to,
134
- * INCLUDING the project, so two projects' data can't collide: a per-project read
135
- * keys by the project, e.g. `["repo-files", projectId, repoPath]`. (The ambient
136
- * slug drives the CONNECTION, not the key — so a mutation invalidates with the
137
- * same `["itx", ...key]` it was written under.)
138
- *
139
- * const files = useItxQuery({
140
- * key: ["repo-files", projectId, repoPath],
141
- * query: (itx) => itx.repos.get(repoPath).listFiles(),
142
- * });
143
- *
144
- * The connection is resolved PER FETCH (never a render-captured stub — that
145
- * would pin a dead socket after a reconnect), and the per-fetch project stub is
146
- * disposed once the read resolves. A resolved query keeps its cached data across
147
- * a reconnect (no re-suspend, no spinner); only an in-flight read retries, on a
148
- * finite transport-only policy. Errors with no cached data throw to the nearest
149
- * error boundary; refetch after a mutation with
150
- * `queryClient.invalidateQueries({ queryKey: ["itx", ...key] })`.
151
- */
152
- function useItxQuery({ key, query }) {
153
- const slug = useContext(ProjectScopeContext);
154
- if (slug === void 0) throw new Error("useItxQuery needs a project: render it under <ProjectScope slug>.");
155
- return useSuspenseQuery({
156
- queryKey: ["itx", ...key],
157
- queryFn: async () => {
158
- const itx = (await connectIterateSession()).projects.get(slug);
159
- try {
160
- return await query(itx);
161
- } finally {
162
- itx[Symbol.dispose]?.();
163
- }
164
- },
165
- ...itxTransportRetry
166
- }).data;
167
- }
168
- /**
169
- * Read once through the SESSION (the catalog — `projects.list`, admin `streams`),
170
- * the session-scoped sibling of {@link useItxQuery}. NON-suspending: session
171
- * reads live in the always-mounted shell (sidebar, ⌘K, admin) which must not
172
- * suspend on the socket, so this returns the full TanStack result (`data`,
173
- * `isPending`, …) and accepts the usual options (`enabled`, `staleTime`). Same
174
- * `["itx", ...key]` namespace and the same transport-only retry as useItxQuery,
175
- * resolved per fetch (never a render-captured session stub).
176
- *
177
- * const { data } = useIterateSessionQuery({ key: ["projects"], query: (s) => s.projects.list() });
178
- */
179
- function useIterateSessionQuery({ key, query, enabled, staleTime }) {
180
- return useQuery({
181
- queryKey: ["itx", ...key],
182
- queryFn: () => connectIterateSession().then(query),
183
- ...itxTransportRetry,
184
- enabled,
185
- staleTime
186
- });
187
- }
188
- /**
189
- * Set up a live itx subscription (or any mount-scoped async itx work) and tear
190
- * it down on unmount. The itx is awaited INSIDE the effect (never in render),
191
- * so this hook NEVER suspends — and a reconnect silently re-runs the effect
192
- * (the effect is keyed on the session generation), whose first server push is
193
- * the recovery. A hand-rolled `useEffect` reaching itx through a closure would
194
- * omit that dep and not recover on reconnect. That generation dep is also the
195
- * whole retry story for a failed connection attempt: the failing connection attempt has already published
196
- * its (paced) successor, which re-runs the effect — no timer needed here.
197
- *
198
- * useReconnectableEffect(async (itx, signal) => {
199
- * const sub = await itx.streams.get("/logs").openConnection({ processEventBatch });
200
- * if (signal.disposed) { sub.unsubscribe(); return; }
201
- * return () => sub.unsubscribe();
202
- * }, []);
203
- *
204
- * A late cleanup (setup resolved after this run was superseded) still executes.
205
- * `enabled: false` renders it fully inert.
206
- */
207
- function useReconnectableEffect(setup, deps, connection, opts) {
208
- const enabled = opts?.enabled ?? true;
209
- const generation = useSyncExternalStore(subscribeSession, () => enabled ? currentSnapshot().generation : 0, () => 0);
210
- useEffect(() => {
211
- if (!enabled) return;
212
- const signal = { disposed: false };
213
- let cleanup;
214
- if (connection.connect !== void 0) connection.connect().then((root) => {
215
- if (signal.disposed) return;
216
- setup(root, signal).then((late) => {
217
- if (signal.disposed) late?.();
218
- else cleanup = late;
219
- }, (error) => {
220
- if (!signal.disposed) console.error("reconnectable itx effect setup failed", error);
221
- });
222
- }, (error) => {
223
- if (signal.disposed) return;
224
- if (opts?.onConnectionError) opts.onConnectionError(error);
225
- else console.error("reconnectable itx effect connect failed", error);
226
- });
227
- else {
228
- const error = new Error(connection.missingMessage);
229
- if (opts?.onConnectionError) opts.onConnectionError(error);
230
- else console.error(error.message);
231
- }
232
- return () => {
233
- signal.disposed = true;
234
- cleanup?.();
235
- };
236
- }, [
237
- enabled,
238
- connection.key,
239
- generation,
240
- ...deps
241
- ]);
242
- }
243
- /** How long useStreamConnection waits before retrying a failed open. */
244
- const CONNECTION_RETRY_MS = 1e4;
245
- /** An open RPC must either establish or give recovery ownership back to the hook. */
246
- const CONNECTION_TIMEOUT_MS = 15e3;
247
- const CONNECTION_TIMED_OUT = Symbol("itx-connection-timed-out");
248
- /** Shared reconnect and watchdog engine behind stream and live-state push APIs. */
249
- function useRecoveringConnection(open, deps, connection, enabled = true) {
250
- const [epoch, setEpoch] = useState(0);
251
- const [state, setState] = useState({ status: "connecting" });
252
- useEffect(() => {
253
- if (!enabled) setState({ status: "connecting" });
254
- }, [enabled]);
255
- useReconnectableEffect(async (root, signal) => {
256
- setState({ status: "connecting" });
257
- let openedConnection;
258
- try {
259
- const pending = open(root);
260
- let timeout;
261
- const result = await Promise.race([pending, new Promise((resolve) => {
262
- timeout = setTimeout(() => resolve(CONNECTION_TIMED_OUT), CONNECTION_TIMEOUT_MS);
263
- })]).finally(() => clearTimeout(timeout));
264
- if (result === CONNECTION_TIMED_OUT) {
265
- pending.then((late) => releaseItxConnection(late), () => {});
266
- if (signal.disposed) return;
267
- reportTransportSuspicion();
268
- setState({ status: "connecting" });
269
- const retry = setTimeout(() => setEpoch((current) => current + 1), CONNECTION_RETRY_MS);
270
- return () => clearTimeout(retry);
271
- }
272
- openedConnection = result;
273
- } catch (error) {
274
- if (signal.disposed) return;
275
- if (!isItxTransportError(error)) {
276
- setState({
277
- status: "error",
278
- error: error instanceof Error ? error.message : String(error)
279
- });
280
- return;
281
- }
282
- setState({ status: "connecting" });
283
- const retry = setTimeout(() => setEpoch((current) => current + 1), CONNECTION_RETRY_MS);
284
- return () => clearTimeout(retry);
285
- }
286
- const dispose = () => releaseItxConnection(openedConnection);
287
- if (signal.disposed) {
288
- dispose();
289
- return;
290
- }
291
- setState({ status: "live" });
292
- const stopWatchdog = watchItxConnection(() => openedConnection.ping(), () => {
293
- if (signal.disposed) return;
294
- setState({ status: "connecting" });
295
- setEpoch((current) => current + 1);
296
- });
297
- return () => {
298
- stopWatchdog();
299
- dispose();
300
- };
301
- }, [epoch, ...deps], connection, {
302
- enabled,
303
- onConnectionError: (error) => {
304
- setState(isItxTransportError(error) ? { status: "connecting" } : {
305
- status: "error",
306
- error: error instanceof Error ? error.message : String(error)
307
- });
308
- }
309
- });
310
- const refresh = useCallback(() => {
311
- setState({ status: "connecting" });
312
- setEpoch((current) => current + 1);
313
- }, []);
314
- return {
315
- ...state,
316
- refresh
317
- };
318
- }
319
- /**
320
- * Hold one raw stream callback connection for the component's lifetime.
321
- * Reconnects, silent connection death, and transport-failed open attempts recover
322
- * through the shared watchdog; permanent failures remain in `"error"` until
323
- * `refresh()` or a reconnect. Reopening replays the first push, so callbacks
324
- * must be replay-tolerant. Teardown closes and disposes the handle.
325
- *
326
- * `enabled: false` is inert; `deps` reopen on change. `opts.slug` targets
327
- * a project outside the ambient {@link ProjectScope} without suspending.
328
- */
329
- function useStreamConnection(open, deps, opts) {
330
- const scopedSlug = useContext(ProjectScopeContext);
331
- const slug = opts?.slug ?? scopedSlug;
332
- return useRecoveringConnection(open, deps, {
333
- key: slug,
334
- ...slug === void 0 ? { missingMessage: "useStreamConnection needs a project: pass { slug } or render under <ProjectScope slug>." } : { connect: () => connectItx(slug) }
335
- }, opts?.enabled);
336
- }
337
- /**
338
- * THE live-state primitive: subscribe to any `.liveState` node, render the slice
339
- * you pick. The server pushes a snapshot then minimal diffs; this hook
340
- * reassembles them and hands back `selector(state)`.
341
- *
342
- * const streams = useLiveState((itx) => itx.liveState, (s) => s.streamsIndex);
343
- *
344
- * THE SELECTOR CONTRACT — a pure function of the state, nothing else:
345
- * - Return a STABLE slice (`s => s.rows`), not a fresh object; map downstream.
346
- * - Do NOT close over props/state (`s => s.rows[props.id]`): selection is cached
347
- * by STATE identity, so a closure-captured value going stale is invisible until
348
- * the next push. Select the broader slice and index in render, or route the
349
- * changing input through `deps`.
350
- *
351
- * `value` is `undefined` between mount and the first snapshot (one round trip);
352
- * render a loading row for that window. It never suspends — a reconnect keeps the
353
- * last value while the subscription silently re-establishes. `deps` (or a changed
354
- * `opts.slug`) re-point the hook at a different node, and the held value drops to
355
- * `undefined` immediately — the previous node's state is never shown against the
356
- * new one. To read a DIFFERENT project's live state from OUTSIDE its scope (⌘K in
357
- * the app shell), pass `opts.slug`.
358
- */
359
- function useLiveState(live, selector, deps = [], opts) {
360
- const scopedSlug = useContext(ProjectScopeContext);
361
- const slug = opts?.slug ?? scopedSlug;
362
- const useAmbientProvider = scopedSlug !== void 0 && (opts?.slug === void 0 || slug === scopedSlug);
363
- const makeConnection = useCallback(async () => {
364
- if (slug === void 0) throw new Error("useLiveState needs a project: pass { slug } or render under <ProjectScope slug>.");
365
- return (await connectItx(slug)).dup();
366
- }, [slug]);
367
- return useLiveState$1(live, selector, deps, useAmbientProvider ? { enabled: opts?.enabled } : {
368
- enabled: opts?.enabled,
369
- makeConnection
370
- });
371
- }
372
- /** Session-scoped sibling of {@link useLiveState}, for deployment-wide live nodes. */
373
- function useIterateSessionLiveState(live, selector, deps = [], opts) {
374
- const makeConnection = useCallback(async () => (await connectIterateSession()).dup(), []);
375
- return useLiveState$1(live, selector, deps, {
376
- enabled: opts?.enabled,
377
- makeConnection
378
- });
379
- }
380
- //#endregion
381
- export { ProjectScope, configureIterateSession, connectIterateSession, connectItx, createIterateQueryClient, disconnectIterateSession, isItxTransportError, reconnectIterateSession, reportTransportSuspicion, retryFailedIterateSession, useIterateSession, useIterateSessionLiveState, useIterateSessionQuery, useItx, useItxQuery, useLiveState, useStreamConnection };
382
-
383
- //# sourceMappingURL=react.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"react.mjs","names":["useCapnWebLiveState"],"sources":["../../../src/itx/query-client.ts","../../../src/sdk/itx/react.ts"],"sourcesContent":["import { QueryClient } from \"@tanstack/react-query\";\n\n/**\n * One Query client policy for every Iterate React renderer.\n *\n * The browser dashboard and the OpenTUI client intentionally share these\n * defaults. Renderer-specific entrypoints own only where the provider mounts;\n * cache lifetime, refetch behavior, and mutation retries must not drift by\n * surface.\n */\nexport function createIterateQueryClient(): QueryClient {\n return new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 60 * 1000,\n gcTime: 5 * 60 * 1000,\n retry: 1,\n refetchOnWindowFocus: false,\n refetchOnMount: true,\n refetchOnReconnect: true,\n },\n mutations: {\n retry: 0,\n },\n },\n });\n}\n","/**\n * `iterate/sdk/itx/react` — hooks over the framework-free session keeper in\n * ../../itx/itx-session.ts (one WebSocket per tab, generations, invisible\n * reconnect — the session model lives there) plus TanStack Query for reads.\n *\n * Renderer-agnostic on purpose: nothing here touches the DOM, so the same\n * hooks run under react-dom (the OS dashboard), @opentui/react (the chat TUI),\n * and React Native — TanStack Query itself only uses React primitives. The\n * browser-specific pieces (cookie auth, window-derived URL, visibility/online\n * wake probes) live in the keeper behind runtime guards; non-browser apps\n * point the keeper at a deployment with `configureIterateSession`.\n *\n * ───────────────────────────────────────────────────────────────────────────\n * THE PRIMITIVES\n * ───────────────────────────────────────────────────────────────────────────\n *\n * SESSION (the catalog authenticate() returned)\n * useIterateSession() in render; suspends once on first connect\n * connectIterateSession() imperative (handlers/closures); a Promise\n *\n * ITX (a project capability handle — session.projects.get(slug))\n * useItx(slug?) in render; slug (or prj_ id) from the arg or <ProjectScope>\n * connectItx(slug) imperative; a Promise\n *\n * READ ONCE useItxQuery({ key, query }) project read; SUSPENDS\n * useIterateSessionQuery({ key, query }) session read; NON-suspending (shell)\n * LIVE STATE useLiveState((itx) => itx.liveState, selector) project snapshot + diffs\n * useIterateSessionLiveState(...) session snapshot + diffs\n * CONNECT useStreamConnection((itx) => handle, deps) raw event batches (escape hatch)\n * MOUNT <ProjectScope slug> ambient project + reconnectable Cap'n Web provider\n *\n * ACTIONS (mutations) — imperative on the handle, no extra primitive:\n * const itx = useItx();\n * <button onClick={() => itx.chat.sendMessage(text)} />\n *\n * RECONNECT, AS REACT SEES IT: components read an immutable Snapshot via\n * `useSyncExternalStore`; `snapshot.session` holds the LAST live session and is\n * kept across a transport gap. So `useIterateSession()` suspends exactly once —\n * first load, on the stable first-connect promise — and never again: when the\n * socket dies we keep showing the last session while a fresh generation connects\n * in the background, then swap when it establishes. TanStack keeps cached read\n * data through a reconnect (no re-suspend, no spinner); only an in-flight read\n * retries, on the transport-only policy — see useItxQuery. A stray action fired\n * during the sub-second gap rides the dead stub and rejects — the one accepted\n * edge.\n *\n * CONNECTING THROWS ON THE SERVER (never SSRs): a forever-pending `use()`\n * during streaming SSR would hang the response. Render itx consumers under an\n * `ssr: false` route or `<ClientOnly>` + `<Suspense>`.\n */\n\n// oxlint-disable react/only-export-components -- the itx hooks are colocated with <ProjectScope> by design (see module header); this file is the whole itx React surface, not a Fast Refresh component module.\nimport {\n createContext,\n createElement,\n use,\n useCallback,\n useContext,\n useEffect,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\";\nimport {\n useQuery,\n useSuspenseQuery,\n type QueryKey,\n type UseQueryResult,\n} from \"@tanstack/react-query\";\nimport type { LiveStateRpc } from \"../../itx-api.generated.ts\";\nimport {\n CapnWebProvider,\n useLiveState as useCapnWebLiveState,\n type LiveStateStatus,\n} from \"../capnweb/react.tsx\";\nimport {\n connectIterateSession,\n connectItx,\n currentSnapshot,\n isItxTransportError,\n projectStubFor,\n releaseItxConnection,\n reportTransportSuspicion,\n serverSnapshot,\n subscribeSession,\n watchItxConnection,\n type ItxRecoverableConnectionHandle,\n type ProjectStub,\n type SessionStub,\n} from \"../../itx/itx-session.ts\";\n\n// The React entry is one-stop: everything a component file needs — hooks AND\n// the imperative/keeper surface — importable from one place.\nexport {\n configureIterateSession,\n connectIterateSession,\n connectItx,\n disconnectIterateSession,\n isItxTransportError,\n reconnectIterateSession,\n retryFailedIterateSession,\n reportTransportSuspicion,\n type Itx,\n type ItxRecoverableConnectionHandle,\n type IterateSessionConfig,\n type ProjectStub,\n type SessionStub,\n} from \"../../itx/itx-session.ts\";\nexport { createIterateQueryClient } from \"../../itx/query-client.ts\";\nexport type * from \"../../itx-api.generated.ts\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// 1. Connection: <ProjectScope> (ambient slug + pre-warm) + useIterateSession/useItx\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** The ambient project slug for `useItx()` / `useItxQuery()` — set by <ProjectScope>. */\nconst ProjectScopeContext = createContext<string | undefined>(undefined);\n\n/**\n * The Session — the catalog `authenticate()` returned (`projects.list/create`,\n * admin `streams`). Suspends exactly ONCE, on first connect; a later reconnect\n * keeps returning the last session (no re-suspend — see module header).\n */\nexport function useIterateSession(): SessionStub {\n const snap = useSyncExternalStore(subscribeSession, currentSnapshot, serverSnapshot);\n if (snap.session !== undefined) return snap.session;\n return use(snap.connecting);\n}\n\n/**\n * The project itx for `slug` — `session.projects.get(slug)`, the real capnweb\n * stub. `slug` comes from the argument or the nearest <ProjectScope>; an\n * explicit argument wins. The returned stub's identity is stable within a socket\n * and changes once per reconnect (which re-runs effects/memos keyed on it).\n *\n * const itx = useItx(); // ambient project (under <ProjectScope>)\n * const itx = useItx(\"other-slug\"); // a specific project\n * const onSend = () => itx.chat.sendMessage(text);\n */\nexport function useItx(explicitSlug?: string): ProjectStub {\n // Read the scope UNCONDITIONALLY (rules-of-hooks): never behind a default arg.\n const scopedSlug = useContext(ProjectScopeContext);\n const slug = explicitSlug ?? scopedSlug;\n if (slug === undefined) {\n throw new Error(\n \"useItx() needs a project: pass useItx(slug) or render under <ProjectScope slug>.\",\n );\n }\n return projectStubFor(useIterateSession(), slug);\n}\n\n/**\n * Set the ambient project for a subtree and provide its reconnectable Cap'n Web\n * root. The project-specific layer is deliberately only a connection factory;\n * live-state ownership and recovery live in `iterate/sdk/capnweb/react`.\n *\n * The provider dials in an effect, never during render, so this context remains\n * safe to render on the server. The duplicate belongs to the provider; the\n * lower-level session keeper keeps owning and reconnecting the shared socket\n * beneath it.\n */\nexport function ProjectScope({ slug, children }: { slug: string; children?: ReactNode }) {\n const makeConnection = useCallback(async () => (await connectItx(slug)).dup(), [slug]);\n return createElement(\n ProjectScopeContext,\n { value: slug },\n createElement(CapnWebProvider, { makeConnection }, children),\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// 2. Reads: useItxQuery() — suspends until resolved, then stale-while-revalidate\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The shared TanStack retry policy for itx reads: retry ONLY transport-close\n * failures (a fresh generation is already reconnecting), briefly — application\n * errors surface immediately.\n */\nconst itxTransportRetry = {\n retry: (failureCount: number, error: Error) => isItxTransportError(error) && failureCount < 3,\n retryDelay: (failureCount: number) => Math.min(250 * 2 ** failureCount, 2_000),\n};\n\n/**\n * Read once through a project itx, suspending until it resolves. A thin adapter\n * over TanStack Query's `useSuspenseQuery`. `key` (prefixed with \"itx\"\n * internally) is the cache key — it must encode what the result is scoped to,\n * INCLUDING the project, so two projects' data can't collide: a per-project read\n * keys by the project, e.g. `[\"repo-files\", projectId, repoPath]`. (The ambient\n * slug drives the CONNECTION, not the key — so a mutation invalidates with the\n * same `[\"itx\", ...key]` it was written under.)\n *\n * const files = useItxQuery({\n * key: [\"repo-files\", projectId, repoPath],\n * query: (itx) => itx.repos.get(repoPath).listFiles(),\n * });\n *\n * The connection is resolved PER FETCH (never a render-captured stub — that\n * would pin a dead socket after a reconnect), and the per-fetch project stub is\n * disposed once the read resolves. A resolved query keeps its cached data across\n * a reconnect (no re-suspend, no spinner); only an in-flight read retries, on a\n * finite transport-only policy. Errors with no cached data throw to the nearest\n * error boundary; refetch after a mutation with\n * `queryClient.invalidateQueries({ queryKey: [\"itx\", ...key] })`.\n */\nexport function useItxQuery<T>({\n key,\n query,\n}: {\n key: QueryKey;\n query: (itx: ProjectStub) => Promise<T>;\n}): T {\n const slug = useContext(ProjectScopeContext);\n if (slug === undefined) {\n throw new Error(\"useItxQuery needs a project: render it under <ProjectScope slug>.\");\n }\n return useSuspenseQuery({\n queryKey: [\"itx\", ...key],\n queryFn: async () => {\n const session = await connectIterateSession();\n const itx = session.projects.get(slug);\n // `return await` is load-bearing: dispose only AFTER the RPC result has\n // fully resolved (the serialized result is already pulled, so disposing\n // the short-lived stub can't invalidate it).\n try {\n return await query(itx);\n } finally {\n (itx as Partial<Disposable>)[Symbol.dispose]?.();\n }\n },\n ...itxTransportRetry,\n }).data;\n}\n\n/**\n * Read once through the SESSION (the catalog — `projects.list`, admin `streams`),\n * the session-scoped sibling of {@link useItxQuery}. NON-suspending: session\n * reads live in the always-mounted shell (sidebar, ⌘K, admin) which must not\n * suspend on the socket, so this returns the full TanStack result (`data`,\n * `isPending`, …) and accepts the usual options (`enabled`, `staleTime`). Same\n * `[\"itx\", ...key]` namespace and the same transport-only retry as useItxQuery,\n * resolved per fetch (never a render-captured session stub).\n *\n * const { data } = useIterateSessionQuery({ key: [\"projects\"], query: (s) => s.projects.list() });\n */\nexport function useIterateSessionQuery<T>({\n key,\n query,\n enabled,\n staleTime,\n}: {\n key: QueryKey;\n query: (session: SessionStub) => Promise<T>;\n enabled?: boolean;\n staleTime?: number;\n}): UseQueryResult<T> {\n return useQuery({\n queryKey: [\"itx\", ...key],\n queryFn: () => connectIterateSession().then(query),\n ...itxTransportRetry,\n enabled,\n staleTime,\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// 3. Live subscriptions: a reconnect-aware effect\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The cancellation contract an async reconnectable setup\n * runs under. `disposed` flips the moment THIS run is superseded — unmount,\n * deps change, or a reconnect re-run — and it flips BEFORE the run's own\n * cleanup executes. Everything a setup does after an `await` must be gated on\n * it: without the shared signal, a run cancelled mid-await can't know, and its\n * late continuation would overwrite the successor's state.\n */\ntype ItxEffectSignal = { readonly disposed: boolean };\n\ntype RootConnection<Root> =\n | { key: unknown; connect: () => Promise<Root> }\n | { key: unknown; connect?: undefined; missingMessage: string };\n\n/**\n * Set up a live itx subscription (or any mount-scoped async itx work) and tear\n * it down on unmount. The itx is awaited INSIDE the effect (never in render),\n * so this hook NEVER suspends — and a reconnect silently re-runs the effect\n * (the effect is keyed on the session generation), whose first server push is\n * the recovery. A hand-rolled `useEffect` reaching itx through a closure would\n * omit that dep and not recover on reconnect. That generation dep is also the\n * whole retry story for a failed connection attempt: the failing connection attempt has already published\n * its (paced) successor, which re-runs the effect — no timer needed here.\n *\n * useReconnectableEffect(async (itx, signal) => {\n * const sub = await itx.streams.get(\"/logs\").openConnection({ processEventBatch });\n * if (signal.disposed) { sub.unsubscribe(); return; }\n * return () => sub.unsubscribe();\n * }, []);\n *\n * A late cleanup (setup resolved after this run was superseded) still executes.\n * `enabled: false` renders it fully inert.\n */\nfunction useReconnectableEffect<Root>(\n setup: (root: Root, signal: ItxEffectSignal) => Promise<void | (() => void)>,\n deps: unknown[],\n connection: RootConnection<Root>,\n opts?: {\n enabled?: boolean;\n onConnectionError?: (error: unknown) => void;\n },\n): void {\n const enabled = opts?.enabled ?? true;\n // A socket death replaces the generation; that number in the deps re-runs the\n // effect on it (the reconnect recovery).\n const generation = useSyncExternalStore(\n subscribeSession,\n () => (enabled ? currentSnapshot().generation : 0),\n () => 0,\n );\n useEffect(() => {\n if (!enabled) return;\n const signal = { disposed: false };\n let cleanup: void | (() => void);\n if (connection.connect !== undefined) {\n // Await the connection INSIDE the effect: mounting never suspends the tree.\n connection.connect().then(\n (root) => {\n if (signal.disposed) return;\n setup(root, signal).then(\n (late) => {\n // Setup resolved after this run was superseded: run its cleanup now.\n if (signal.disposed) late?.();\n else cleanup = late;\n },\n (error: unknown) => {\n if (!signal.disposed) {\n console.error(\"reconnectable itx effect setup failed\", error);\n }\n },\n );\n },\n (error: unknown) => {\n if (signal.disposed) return;\n if (opts?.onConnectionError) opts.onConnectionError(error);\n else console.error(\"reconnectable itx effect connect failed\", error);\n },\n );\n } else {\n const error = new Error(connection.missingMessage);\n if (opts?.onConnectionError) opts.onConnectionError(error);\n else console.error(error.message);\n }\n return () => {\n signal.disposed = true;\n cleanup?.();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- connection key + caller's deps; setup/read factory are fresh per run\n }, [enabled, connection.key, generation, ...deps]);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// 4. Push connections: useStreamConnection() — recovery + watchdog; useLiveState()\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** How long useStreamConnection waits before retrying a failed open. */\nconst CONNECTION_RETRY_MS = 10_000;\n/** An open RPC must either establish or give recovery ownership back to the hook. */\nconst CONNECTION_TIMEOUT_MS = 15_000;\nconst CONNECTION_TIMED_OUT = Symbol(\"itx-connection-timed-out\");\n\nexport type ItxConnectionStatus = LiveStateStatus;\n\n/** Shared reconnect and watchdog engine behind stream and live-state push APIs. */\nfunction useRecoveringConnection<Root>(\n open: (root: Root) => Promise<ItxRecoverableConnectionHandle>,\n deps: unknown[],\n connection: RootConnection<Root>,\n enabled = true,\n): { status: ItxConnectionStatus; error?: string; refresh: () => void } {\n const [epoch, setEpoch] = useState(0);\n const [state, setState] = useState<{ status: ItxConnectionStatus; error?: string }>({\n status: \"connecting\",\n });\n\n // Disabling makes the whole effect inert; reset status so a connection\n // disabled after a live period doesn't keep reporting \"live\".\n useEffect(() => {\n if (!enabled) setState({ status: \"connecting\" });\n }, [enabled]);\n\n useReconnectableEffect(\n async (root, signal) => {\n setState({ status: \"connecting\" });\n\n let openedConnection: ItxRecoverableConnectionHandle;\n try {\n const pending = open(root);\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const result = await Promise.race([\n pending,\n new Promise<typeof CONNECTION_TIMED_OUT>((resolve) => {\n timeout = setTimeout(() => resolve(CONNECTION_TIMED_OUT), CONNECTION_TIMEOUT_MS);\n }),\n ]).finally(() => clearTimeout(timeout));\n if (result === CONNECTION_TIMED_OUT) {\n // A transport can disappear after the server accepted the open but\n // before the browser receives its handle. No handle means no ping\n // watchdog, and a half-open WebSocket emits no close event: without\n // this bound the UI stays \"connecting\" forever. REPORT the suspicion\n // (never close the shared socket ourselves — the verifier two-strikes\n // and, if genuinely half-open, reconnects, whose generation re-runs this\n // effect); a straggler handle is closed AND disposed.\n void pending.then(\n (late) => releaseItxConnection(late),\n () => {},\n );\n if (signal.disposed) return;\n reportTransportSuspicion();\n // This attempt is bounded and a retry is already scheduled, so the\n // consumer remains in a recoverable connecting state rather than\n // replacing already-loaded data with terminal error UI.\n setState({ status: \"connecting\" });\n // Retry regardless of the verifier's verdict: a wedged-but-alive\n // server (cold DO) recovers on the next attempt, not on a reconnect.\n const retry = setTimeout(() => setEpoch((current) => current + 1), CONNECTION_RETRY_MS);\n return () => clearTimeout(retry);\n }\n openedConnection = result;\n } catch (error) {\n // The ONE cancellation signal: a run superseded mid-await (unmount,\n // deps, reconnect) must not touch state its successor now owns.\n if (signal.disposed) return;\n if (!isItxTransportError(error)) {\n setState({\n status: \"error\",\n error: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n // Recoverable transport failures never become terminal UI errors. Keep\n // already-loaded consumers rendered while this hook retries.\n setState({ status: \"connecting\" });\n const retry = setTimeout(() => setEpoch((current) => current + 1), CONNECTION_RETRY_MS);\n return () => clearTimeout(retry);\n }\n const dispose = () => releaseItxConnection(openedConnection);\n if (signal.disposed) {\n dispose();\n return;\n }\n setState({ status: \"live\" });\n\n const stopWatchdog = watchItxConnection(\n () => openedConnection.ping(),\n () => {\n if (signal.disposed) return;\n setState({ status: \"connecting\" });\n // Open again unconditionally. When only the server-side callback\n // connection died, the shared transport is still fine and this is\n // the whole recovery; on a transport timeout the\n // verifier may be reconnecting, and the generation dep will also re-run\n // this effect — the epoch bump covers both, and a doubled\n // second open is idempotent by connection key.\n setEpoch((current) => current + 1);\n },\n );\n\n return () => {\n stopWatchdog();\n dispose();\n };\n },\n [epoch, ...deps],\n connection,\n {\n enabled,\n // A failed connect never reaches setup, so without this the hook would sit\n // on \"connecting\" forever. No timer: the failed connection attempt has already published\n // a paced successor, and that generation dep re-runs the effect.\n onConnectionError: (error) => {\n setState(\n isItxTransportError(error)\n ? { status: \"connecting\" }\n : {\n status: \"error\",\n error: error instanceof Error ? error.message : String(error),\n },\n );\n },\n },\n );\n\n // Stable identity: `refresh` lands in consumer deps and memoized children.\n const refresh = useCallback(() => {\n setState({ status: \"connecting\" });\n setEpoch((current) => current + 1);\n }, []);\n\n return { ...state, refresh };\n}\n\n/**\n * Hold one raw stream callback connection for the component's lifetime.\n * Reconnects, silent connection death, and transport-failed open attempts recover\n * through the shared watchdog; permanent failures remain in `\"error\"` until\n * `refresh()` or a reconnect. Reopening replays the first push, so callbacks\n * must be replay-tolerant. Teardown closes and disposes the handle.\n *\n * `enabled: false` is inert; `deps` reopen on change. `opts.slug` targets\n * a project outside the ambient {@link ProjectScope} without suspending.\n */\nexport function useStreamConnection(\n open: (itx: ProjectStub) => Promise<ItxRecoverableConnectionHandle>,\n deps: unknown[],\n opts?: { enabled?: boolean; slug?: string },\n): { status: ItxConnectionStatus; error?: string; refresh: () => void } {\n const scopedSlug = useContext(ProjectScopeContext);\n const slug = opts?.slug ?? scopedSlug;\n return useRecoveringConnection(\n open,\n deps,\n {\n key: slug,\n ...(slug === undefined\n ? {\n missingMessage:\n \"useStreamConnection needs a project: pass { slug } or render under <ProjectScope slug>.\",\n }\n : { connect: () => connectItx(slug) }),\n },\n opts?.enabled,\n );\n}\n\n/**\n * THE live-state primitive: subscribe to any `.liveState` node, render the slice\n * you pick. The server pushes a snapshot then minimal diffs; this hook\n * reassembles them and hands back `selector(state)`.\n *\n * const streams = useLiveState((itx) => itx.liveState, (s) => s.streamsIndex);\n *\n * THE SELECTOR CONTRACT — a pure function of the state, nothing else:\n * - Return a STABLE slice (`s => s.rows`), not a fresh object; map downstream.\n * - Do NOT close over props/state (`s => s.rows[props.id]`): selection is cached\n * by STATE identity, so a closure-captured value going stale is invisible until\n * the next push. Select the broader slice and index in render, or route the\n * changing input through `deps`.\n *\n * `value` is `undefined` between mount and the first snapshot (one round trip);\n * render a loading row for that window. It never suspends — a reconnect keeps the\n * last value while the subscription silently re-establishes. `deps` (or a changed\n * `opts.slug`) re-point the hook at a different node, and the held value drops to\n * `undefined` immediately — the previous node's state is never shown against the\n * new one. To read a DIFFERENT project's live state from OUTSIDE its scope (⌘K in\n * the app shell), pass `opts.slug`.\n */\nexport function useLiveState<State, Selected = State>(\n live: (itx: ProjectStub) => LiveStateRpc<State>,\n selector: (state: State) => Selected,\n deps: unknown[] = [],\n opts?: { slug?: string; enabled?: boolean },\n): {\n value: Selected | undefined;\n status: ItxConnectionStatus;\n error?: string;\n refresh: () => void;\n} {\n const scopedSlug = useContext(ProjectScopeContext);\n const slug = opts?.slug ?? scopedSlug;\n const useAmbientProvider =\n scopedSlug !== undefined && (opts?.slug === undefined || slug === scopedSlug);\n const makeConnection = useCallback(async () => {\n if (slug === undefined) {\n throw new Error(\n \"useLiveState needs a project: pass { slug } or render under <ProjectScope slug>.\",\n );\n }\n return (await connectItx(slug)).dup();\n }, [slug]);\n return useCapnWebLiveState(\n live,\n selector,\n deps,\n useAmbientProvider ? { enabled: opts?.enabled } : { enabled: opts?.enabled, makeConnection },\n );\n}\n\n/** Session-scoped sibling of {@link useLiveState}, for deployment-wide live nodes. */\nexport function useIterateSessionLiveState<State, Selected = State>(\n live: (session: SessionStub) => LiveStateRpc<State>,\n selector: (state: State) => Selected,\n deps: unknown[] = [],\n opts?: { enabled?: boolean },\n): {\n value: Selected | undefined;\n status: ItxConnectionStatus;\n error?: string;\n refresh: () => void;\n} {\n const makeConnection = useCallback(async () => (await connectIterateSession()).dup(), []);\n return useCapnWebLiveState(live, selector, deps, { enabled: opts?.enabled, makeConnection });\n}\n"],"mappings":";;;;;;;;;;;;;AAUA,SAAgB,2BAAwC;CACtD,OAAO,IAAI,YAAY,EACrB,gBAAgB;EACd,SAAS;GACP,WAAW,KAAK;GAChB,QAAQ,MAAS;GACjB,OAAO;GACP,sBAAsB;GACtB,gBAAgB;GAChB,oBAAoB;EACtB;EACA,WAAW,EACT,OAAO,EACT;CACF,EACF,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC0FA,MAAM,sBAAsB,cAAkC,KAAA,CAAS;;;;;;AAOvE,SAAgB,oBAAiC;CAC/C,MAAM,OAAO,qBAAqB,kBAAkB,iBAAiB,cAAc;CACnF,IAAI,KAAK,YAAY,KAAA,GAAW,OAAO,KAAK;CAC5C,OAAO,IAAI,KAAK,UAAU;AAC5B;;;;;;;;;;;AAYA,SAAgB,OAAO,cAAoC;CAEzD,MAAM,aAAa,WAAW,mBAAmB;CACjD,MAAM,OAAO,gBAAgB;CAC7B,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MACR,kFACF;CAEF,OAAO,eAAe,kBAAkB,GAAG,IAAI;AACjD;;;;;;;;;;;AAYA,SAAgB,aAAa,EAAE,MAAM,YAAoD;CACvF,MAAM,iBAAiB,YAAY,aAAa,MAAM,WAAW,IAAI,EAAA,CAAG,IAAI,GAAG,CAAC,IAAI,CAAC;CACrF,OAAO,cACL,qBACA,EAAE,OAAO,KAAK,GACd,cAAc,iBAAiB,EAAE,eAAe,GAAG,QAAQ,CAC7D;AACF;;;;;;AAWA,MAAM,oBAAoB;CACxB,QAAQ,cAAsB,UAAiB,oBAAoB,KAAK,KAAK,eAAe;CAC5F,aAAa,iBAAyB,KAAK,IAAI,MAAM,KAAK,cAAc,GAAK;AAC/E;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,YAAe,EAC7B,KACA,SAII;CACJ,MAAM,OAAO,WAAW,mBAAmB;CAC3C,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,mEAAmE;CAErF,OAAO,iBAAiB;EACtB,UAAU,CAAC,OAAO,GAAG,GAAG;EACxB,SAAS,YAAY;GAEnB,MAAM,OAAM,MADU,sBAAsB,EAAA,CACxB,SAAS,IAAI,IAAI;GAIrC,IAAI;IACF,OAAO,MAAM,MAAM,GAAG;GACxB,UAAU;IACR,IAA6B,OAAO,QAAQ,GAAG;GACjD;EACF;EACA,GAAG;CACL,CAAC,CAAC,CAAC;AACL;;;;;;;;;;;;AAaA,SAAgB,uBAA0B,EACxC,KACA,OACA,SACA,aAMoB;CACpB,OAAO,SAAS;EACd,UAAU,CAAC,OAAO,GAAG,GAAG;EACxB,eAAe,sBAAsB,CAAC,CAAC,KAAK,KAAK;EACjD,GAAG;EACH;EACA;CACF,CAAC;AACH;;;;;;;;;;;;;;;;;;;;AAuCA,SAAS,uBACP,OACA,MACA,YACA,MAIM;CACN,MAAM,UAAU,MAAM,WAAW;CAGjC,MAAM,aAAa,qBACjB,wBACO,UAAU,gBAAgB,CAAC,CAAC,aAAa,SAC1C,CACR;CACA,gBAAgB;EACd,IAAI,CAAC,SAAS;EACd,MAAM,SAAS,EAAE,UAAU,MAAM;EACjC,IAAI;EACJ,IAAI,WAAW,YAAY,KAAA,GAEzB,WAAW,QAAQ,CAAC,CAAC,MAClB,SAAS;GACR,IAAI,OAAO,UAAU;GACrB,MAAM,MAAM,MAAM,CAAC,CAAC,MACjB,SAAS;IAER,IAAI,OAAO,UAAU,OAAO;SACvB,UAAU;GACjB,IACC,UAAmB;IAClB,IAAI,CAAC,OAAO,UACV,QAAQ,MAAM,yCAAyC,KAAK;GAEhE,CACF;EACF,IACC,UAAmB;GAClB,IAAI,OAAO,UAAU;GACrB,IAAI,MAAM,mBAAmB,KAAK,kBAAkB,KAAK;QACpD,QAAQ,MAAM,2CAA2C,KAAK;EACrE,CACF;OACK;GACL,MAAM,QAAQ,IAAI,MAAM,WAAW,cAAc;GACjD,IAAI,MAAM,mBAAmB,KAAK,kBAAkB,KAAK;QACpD,QAAQ,MAAM,MAAM,OAAO;EAClC;EACA,aAAa;GACX,OAAO,WAAW;GAClB,UAAU;EACZ;CAEF,GAAG;EAAC;EAAS,WAAW;EAAK;EAAY,GAAG;CAAI,CAAC;AACnD;;AAOA,MAAM,sBAAsB;;AAE5B,MAAM,wBAAwB;AAC9B,MAAM,uBAAuB,OAAO,0BAA0B;;AAK9D,SAAS,wBACP,MACA,MACA,YACA,UAAU,MAC4D;CACtE,MAAM,CAAC,OAAO,YAAY,SAAS,CAAC;CACpC,MAAM,CAAC,OAAO,YAAY,SAA0D,EAClF,QAAQ,aACV,CAAC;CAID,gBAAgB;EACd,IAAI,CAAC,SAAS,SAAS,EAAE,QAAQ,aAAa,CAAC;CACjD,GAAG,CAAC,OAAO,CAAC;CAEZ,uBACE,OAAO,MAAM,WAAW;EACtB,SAAS,EAAE,QAAQ,aAAa,CAAC;EAEjC,IAAI;EACJ,IAAI;GACF,MAAM,UAAU,KAAK,IAAI;GACzB,IAAI;GACJ,MAAM,SAAS,MAAM,QAAQ,KAAK,CAChC,SACA,IAAI,SAAsC,YAAY;IACpD,UAAU,iBAAiB,QAAQ,oBAAoB,GAAG,qBAAqB;GACjF,CAAC,CACH,CAAC,CAAC,CAAC,cAAc,aAAa,OAAO,CAAC;GACtC,IAAI,WAAW,sBAAsB;IAQnC,QAAa,MACV,SAAS,qBAAqB,IAAI,SAC7B,CAAC,CACT;IACA,IAAI,OAAO,UAAU;IACrB,yBAAyB;IAIzB,SAAS,EAAE,QAAQ,aAAa,CAAC;IAGjC,MAAM,QAAQ,iBAAiB,UAAU,YAAY,UAAU,CAAC,GAAG,mBAAmB;IACtF,aAAa,aAAa,KAAK;GACjC;GACA,mBAAmB;EACrB,SAAS,OAAO;GAGd,IAAI,OAAO,UAAU;GACrB,IAAI,CAAC,oBAAoB,KAAK,GAAG;IAC/B,SAAS;KACP,QAAQ;KACR,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAC9D,CAAC;IACD;GACF;GAGA,SAAS,EAAE,QAAQ,aAAa,CAAC;GACjC,MAAM,QAAQ,iBAAiB,UAAU,YAAY,UAAU,CAAC,GAAG,mBAAmB;GACtF,aAAa,aAAa,KAAK;EACjC;EACA,MAAM,gBAAgB,qBAAqB,gBAAgB;EAC3D,IAAI,OAAO,UAAU;GACnB,QAAQ;GACR;EACF;EACA,SAAS,EAAE,QAAQ,OAAO,CAAC;EAE3B,MAAM,eAAe,yBACb,iBAAiB,KAAK,SACtB;GACJ,IAAI,OAAO,UAAU;GACrB,SAAS,EAAE,QAAQ,aAAa,CAAC;GAOjC,UAAU,YAAY,UAAU,CAAC;EACnC,CACF;EAEA,aAAa;GACX,aAAa;GACb,QAAQ;EACV;CACF,GACA,CAAC,OAAO,GAAG,IAAI,GACf,YACA;EACE;EAIA,oBAAoB,UAAU;GAC5B,SACE,oBAAoB,KAAK,IACrB,EAAE,QAAQ,aAAa,IACvB;IACE,QAAQ;IACR,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D,CACN;EACF;CACF,CACF;CAGA,MAAM,UAAU,kBAAkB;EAChC,SAAS,EAAE,QAAQ,aAAa,CAAC;EACjC,UAAU,YAAY,UAAU,CAAC;CACnC,GAAG,CAAC,CAAC;CAEL,OAAO;EAAE,GAAG;EAAO;CAAQ;AAC7B;;;;;;;;;;;AAYA,SAAgB,oBACd,MACA,MACA,MACsE;CACtE,MAAM,aAAa,WAAW,mBAAmB;CACjD,MAAM,OAAO,MAAM,QAAQ;CAC3B,OAAO,wBACL,MACA,MACA;EACE,KAAK;EACL,GAAI,SAAS,KAAA,IACT,EACE,gBACE,0FACJ,IACA,EAAE,eAAe,WAAW,IAAI,EAAE;CACxC,GACA,MAAM,OACR;AACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,aACd,MACA,UACA,OAAkB,CAAC,GACnB,MAMA;CACA,MAAM,aAAa,WAAW,mBAAmB;CACjD,MAAM,OAAO,MAAM,QAAQ;CAC3B,MAAM,qBACJ,eAAe,KAAA,MAAc,MAAM,SAAS,KAAA,KAAa,SAAS;CACpE,MAAM,iBAAiB,YAAY,YAAY;EAC7C,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MACR,kFACF;EAEF,QAAQ,MAAM,WAAW,IAAI,EAAA,CAAG,IAAI;CACtC,GAAG,CAAC,IAAI,CAAC;CACT,OAAOA,eACL,MACA,UACA,MACA,qBAAqB,EAAE,SAAS,MAAM,QAAQ,IAAI;EAAE,SAAS,MAAM;EAAS;CAAe,CAC7F;AACF;;AAGA,SAAgB,2BACd,MACA,UACA,OAAkB,CAAC,GACnB,MAMA;CACA,MAAM,iBAAiB,YAAY,aAAa,MAAM,sBAAsB,EAAA,CAAG,IAAI,GAAG,CAAC,CAAC;CACxF,OAAOA,eAAoB,MAAM,UAAU,MAAM;EAAE,SAAS,MAAM;EAAS;CAAe,CAAC;AAC7F"}