iterate 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -76
- package/THIRD_PARTY_NOTICES.md +55 -0
- package/bin/iterate.js +18 -3
- package/dist/api-url-B6404M82.mjs +17 -0
- package/dist/api-url-B6404M82.mjs.map +1 -0
- package/dist/app-ref-BipL0feU.mjs +35 -0
- package/dist/app-ref-BipL0feU.mjs.map +1 -0
- package/dist/app-ref-C1CrgXqX.mjs +7 -0
- package/dist/app-ref-C1CrgXqX.mjs.map +1 -0
- package/dist/app-ref-DYai_om1.mjs +7 -0
- package/dist/app-ref-DYai_om1.mjs.map +1 -0
- package/dist/cli-D0c-pDL_.mjs +1010 -0
- package/dist/cli-D0c-pDL_.mjs.map +1 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.mjs +4 -0
- package/dist/cloudflare-BTm90gQ4.mjs +951 -0
- package/dist/cloudflare-BTm90gQ4.mjs.map +1 -0
- package/dist/contract-s4FW4eES.mjs +309 -0
- package/dist/contract-s4FW4eES.mjs.map +1 -0
- package/dist/document-review/index.d.ts +5 -0
- package/dist/document-review/types.d.ts +107 -0
- package/dist/document-review.mjs +7015 -0
- package/dist/document-review.mjs.map +1 -0
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs +205 -0
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +1 -0
- package/dist/idempotency-DleloJNt.mjs +28 -0
- package/dist/idempotency-DleloJNt.mjs.map +1 -0
- package/dist/index.mjs +1 -1
- package/dist/itx/api-url.d.ts +6 -0
- package/dist/itx/itx-node-client.d.ts +65 -0
- package/dist/itx/itx-session.d.ts +215 -0
- package/dist/itx/owned-rpc-session.d.ts +14 -0
- package/dist/itx/query-client.d.ts +10 -0
- package/dist/itx-api.generated.d.ts +6195 -0
- package/dist/itx-session-sjud8GiT.mjs +534 -0
- package/dist/itx-session-sjud8GiT.mjs.map +1 -0
- package/dist/live-state-BJNqOwFw.mjs +299 -0
- package/dist/live-state-BJNqOwFw.mjs.map +1 -0
- package/dist/next/api.d.ts +479 -0
- package/dist/next/api.mjs +0 -0
- package/dist/next/app-server.d.ts +44 -0
- package/dist/next/app-server.mjs +479 -0
- package/dist/next/app-server.mjs.map +1 -0
- package/dist/next/app-session.d.ts +49 -0
- package/dist/next/app-session.mjs +238 -0
- package/dist/next/app-session.mjs.map +1 -0
- package/dist/next/app.d.ts +29 -0
- package/dist/next/app.mjs +141 -0
- package/dist/next/app.mjs.map +1 -0
- package/dist/next/client/live-state.d.ts +63 -0
- package/dist/next/client/oauth.d.ts +12 -0
- package/dist/next/client/react.d.ts +109 -0
- package/dist/next/client/socket.d.ts +6 -0
- package/dist/next/client.mjs +156 -0
- package/dist/next/client.mjs.map +1 -0
- package/dist/next/expression.d.ts +146 -0
- package/dist/next/expression.mjs +399 -0
- package/dist/next/expression.mjs.map +1 -0
- package/dist/next/lib.d.ts +56 -0
- package/dist/next/lib.mjs +199 -0
- package/dist/next/lib.mjs.map +1 -0
- package/dist/next/oauth-scopes.d.ts +32 -0
- package/dist/next/oauth-scopes.mjs +40 -0
- package/dist/next/oauth-scopes.mjs.map +1 -0
- package/dist/next/oauth.mjs +29 -0
- package/dist/next/oauth.mjs.map +1 -0
- package/dist/next/principal.d.ts +64 -0
- package/dist/next/principal.mjs +98 -0
- package/dist/next/principal.mjs.map +1 -0
- package/dist/next/project-ingress.d.ts +37 -0
- package/dist/next/project-ingress.mjs +75 -0
- package/dist/next/project-ingress.mjs.map +1 -0
- package/dist/next/react.mjs +285 -0
- package/dist/next/react.mjs.map +1 -0
- package/dist/next/sdk/auth.d.ts +5 -0
- package/dist/next/sdk/index.d.ts +112 -0
- package/dist/next/sdk.mjs +139 -0
- package/dist/next/sdk.mjs.map +1 -0
- package/dist/next/stream/processor.d.ts +378 -0
- package/dist/next/stream/processor.mjs +582 -0
- package/dist/next/stream/processor.mjs.map +1 -0
- package/dist/next/stream/run.d.ts +58 -0
- package/dist/next/stream/run.mjs +40 -0
- package/dist/next/stream/run.mjs.map +1 -0
- package/dist/next-node.d.ts +15 -0
- package/dist/next-node.mjs +51 -0
- package/dist/next-node.mjs.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.mjs +185 -0
- package/dist/node.mjs.map +1 -0
- package/dist/processor-host-capabilities-BMFH3KTM.mjs +56 -0
- package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +1 -0
- package/dist/processors/cloudflare.d.ts +3 -0
- package/dist/processors/durable-object-processor-durability.d.ts +79 -0
- package/dist/processors/event-consumption-metrics.d.ts +82 -0
- package/dist/processors/idempotency.d.ts +13 -0
- package/dist/processors/index.d.ts +12 -0
- package/dist/processors/processor-contracts.d.ts +342 -0
- package/dist/processors/processor-facet.d.ts +186 -0
- package/dist/processors/processor-host-capabilities.d.ts +60 -0
- package/dist/processors/prompt-sections.d.ts +17 -0
- package/dist/processors/rpc-types.d.ts +515 -0
- package/dist/processors/schemas.d.ts +102 -0
- package/dist/processors/stream-handle.d.ts +45 -0
- package/dist/processors/stream-processor-keepalive.d.ts +95 -0
- package/dist/processors/stream-processor-registry.d.ts +233 -0
- package/dist/processors/stream-processor-runner.d.ts +289 -0
- package/dist/processors/stream-processor.d.ts +339 -0
- package/dist/processors/stream-runtime-metrics.d.ts +107 -0
- package/dist/processors/testing.d.ts +302 -0
- package/dist/processors-BoNyeBfQ.mjs +10 -0
- package/dist/processors-BoNyeBfQ.mjs.map +1 -0
- package/dist/processors-cloudflare.mjs +3 -0
- package/dist/processors-testing.mjs +435 -0
- package/dist/processors-testing.mjs.map +1 -0
- package/dist/processors.mjs +52 -0
- package/dist/processors.mjs.map +1 -0
- package/dist/protocol-DnK_f2m6.mjs +251 -0
- package/dist/protocol-DnK_f2m6.mjs.map +1 -0
- package/dist/sdk/capnweb/index.d.ts +2 -0
- package/dist/sdk/capnweb/live-state/compact.d.ts +5 -0
- package/dist/sdk/capnweb/live-state/diff.d.ts +41 -0
- package/dist/sdk/capnweb/live-state/engine.d.ts +44 -0
- package/dist/sdk/capnweb/live-state/index.d.ts +41 -0
- package/dist/sdk/capnweb/live-state/protocol.d.ts +87 -0
- package/dist/sdk/capnweb/live-state/retain.d.ts +23 -0
- package/dist/sdk/capnweb/live-state/store.d.ts +20 -0
- package/dist/sdk/capnweb/live-state/types.d.ts +11 -0
- package/dist/sdk/capnweb/react.d.ts +45 -0
- package/dist/sdk/capnweb/react.mjs +316 -0
- package/dist/sdk/capnweb/react.mjs.map +1 -0
- package/dist/sdk/capnweb.mjs +4 -0
- package/dist/sdk/itx/react.d.ts +191 -0
- package/dist/sdk/itx/react.mjs +383 -0
- package/dist/sdk/itx/react.mjs.map +1 -0
- package/dist/sdk-DMB-IM11.mjs +933 -0
- package/dist/sdk-DMB-IM11.mjs.map +1 -0
- package/dist/sdk.d.ts +339 -0
- package/dist/sdk.mjs +2 -0
- package/dist/serve-itx.d.ts +46 -0
- package/dist/starter-apps/flake-dashboard/app-ref.d.ts +31 -0
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs +1055 -0
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/flake-dashboard/contract.d.ts +4839 -0
- package/dist/starter-apps/flake-dashboard/contract.mjs +2 -0
- package/dist/starter-apps/flake-dashboard/index.d.ts +17 -0
- package/dist/starter-apps/flake-dashboard/index.mjs +56 -0
- package/dist/starter-apps/flake-dashboard/index.mjs.map +1 -0
- package/dist/starter-apps/flake-dashboard/worker.d.ts +4607 -0
- package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +8914 -0
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs +17987 -0
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/github-ai-linter/contract.d.ts +9193 -0
- package/dist/starter-apps/github-ai-linter/index.d.ts +10 -0
- package/dist/starter-apps/github-ai-linter/index.mjs +36 -0
- package/dist/starter-apps/github-ai-linter/index.mjs.map +1 -0
- package/dist/starter-apps/github-ai-linter/prompt.d.ts +13 -0
- package/dist/starter-apps/github-ai-linter/review-bot.d.ts +808 -0
- package/dist/starter-apps/github-ai-linter/rules.d.ts +34 -0
- package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +19 -0
- package/dist/starter-apps/github-ai-linter/worker.d.ts +19 -0
- package/dist/starter-apps/github-ai-linter/worker.mjs +947 -0
- package/dist/starter-apps/github-ai-linter/worker.mjs.map +1 -0
- package/dist/starter-apps/guestbook/app-ref.d.ts +27 -0
- package/dist/starter-apps/guestbook/client.d.ts +7 -0
- package/dist/starter-apps/guestbook/client.mjs +59 -0
- package/dist/starter-apps/guestbook/configured-worker.mjs +205 -0
- package/dist/starter-apps/guestbook/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/guestbook/index.d.ts +9 -0
- package/dist/starter-apps/guestbook/index.mjs +31 -0
- package/dist/starter-apps/guestbook/index.mjs.map +1 -0
- package/dist/starter-apps/guestbook/processor.d.ts +2267 -0
- package/dist/starter-apps/guestbook/worker.d.ts +26 -0
- package/dist/starter-apps/guestbook/worker.mjs +191 -0
- package/dist/starter-apps/guestbook/worker.mjs.map +1 -0
- package/dist/starter-apps/media/configured-worker.mjs +577 -0
- package/dist/starter-apps/media/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/media/index.mjs +36 -0
- package/dist/starter-apps/media/index.mjs.map +1 -0
- package/dist/starter-apps/media/ref.mjs +20 -0
- package/dist/starter-apps/media/ref.mjs.map +1 -0
- package/dist/starter-apps/media/worker.mjs +579 -0
- package/dist/starter-apps/media/worker.mjs.map +1 -0
- package/dist/starter-apps/notes/configured-worker.mjs +6134 -0
- package/dist/starter-apps/notes/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/notes/index.mjs +23 -0
- package/dist/starter-apps/notes/index.mjs.map +1 -0
- package/dist/starter-apps/notes/ref.mjs +21 -0
- package/dist/starter-apps/notes/ref.mjs.map +1 -0
- package/dist/starter-apps/notes/worker.mjs +427 -0
- package/dist/starter-apps/notes/worker.mjs.map +1 -0
- package/dist/starter-apps/todo/client.mjs +59 -0
- package/dist/starter-apps/todo/configured-worker.mjs +2864 -0
- package/dist/starter-apps/todo/configured-worker.mjs.map +1 -0
- package/dist/starter-apps/todo/index.d.ts +8 -0
- package/dist/starter-apps/todo/index.mjs +29 -0
- package/dist/starter-apps/todo/index.mjs.map +1 -0
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs +2082 -0
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +1 -0
- package/dist/usingCtx-inzbY1Qz.mjs +57 -0
- package/dist/usingCtx-mZx5nsAW.mjs +11800 -0
- package/dist/usingCtx-mZx5nsAW.mjs.map +1 -0
- package/dist/worker-ref-DZxPDmb_.mjs +390 -0
- package/dist/worker-ref-DZxPDmb_.mjs.map +1 -0
- package/menubar/Iterate.entitlements +12 -0
- package/menubar/Iterate.swift +914 -0
- package/menubar/IterateIcon.swift +145 -0
- package/menubar/README.md +28 -0
- package/menubar/build-menubar-app.sh +59 -0
- package/package.json +235 -18
- package/dist/cli-DMS4kJph.mjs +0 -868
- package/dist/cli-DMS4kJph.mjs.map +0 -1
- package/dist/config-DtnR7Lv7.mjs +0 -170
- package/dist/config-DtnR7Lv7.mjs.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/stream-tui/agent-chat-terminal.d.mts +0 -1
- package/dist/stream-tui/agent-chat-terminal.mjs +0 -933
- package/dist/stream-tui/agent-chat-terminal.mjs.map +0 -1
|
@@ -0,0 +1,383 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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":"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"}
|