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.
Files changed (218) hide show
  1. package/README.md +86 -76
  2. package/THIRD_PARTY_NOTICES.md +55 -0
  3. package/bin/iterate.js +18 -3
  4. package/dist/api-url-B6404M82.mjs +17 -0
  5. package/dist/api-url-B6404M82.mjs.map +1 -0
  6. package/dist/app-ref-BipL0feU.mjs +35 -0
  7. package/dist/app-ref-BipL0feU.mjs.map +1 -0
  8. package/dist/app-ref-C1CrgXqX.mjs +7 -0
  9. package/dist/app-ref-C1CrgXqX.mjs.map +1 -0
  10. package/dist/app-ref-DYai_om1.mjs +7 -0
  11. package/dist/app-ref-DYai_om1.mjs.map +1 -0
  12. package/dist/cli-D0c-pDL_.mjs +1010 -0
  13. package/dist/cli-D0c-pDL_.mjs.map +1 -0
  14. package/dist/client.d.ts +3 -0
  15. package/dist/client.mjs +4 -0
  16. package/dist/cloudflare-BTm90gQ4.mjs +951 -0
  17. package/dist/cloudflare-BTm90gQ4.mjs.map +1 -0
  18. package/dist/contract-s4FW4eES.mjs +309 -0
  19. package/dist/contract-s4FW4eES.mjs.map +1 -0
  20. package/dist/document-review/index.d.ts +5 -0
  21. package/dist/document-review/types.d.ts +107 -0
  22. package/dist/document-review.mjs +7015 -0
  23. package/dist/document-review.mjs.map +1 -0
  24. package/dist/durable-object-processor-durability-CNsTjAJS.mjs +205 -0
  25. package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +1 -0
  26. package/dist/idempotency-DleloJNt.mjs +28 -0
  27. package/dist/idempotency-DleloJNt.mjs.map +1 -0
  28. package/dist/index.mjs +1 -1
  29. package/dist/itx/api-url.d.ts +6 -0
  30. package/dist/itx/itx-node-client.d.ts +65 -0
  31. package/dist/itx/itx-session.d.ts +215 -0
  32. package/dist/itx/owned-rpc-session.d.ts +14 -0
  33. package/dist/itx/query-client.d.ts +10 -0
  34. package/dist/itx-api.generated.d.ts +6195 -0
  35. package/dist/itx-session-sjud8GiT.mjs +534 -0
  36. package/dist/itx-session-sjud8GiT.mjs.map +1 -0
  37. package/dist/live-state-BJNqOwFw.mjs +299 -0
  38. package/dist/live-state-BJNqOwFw.mjs.map +1 -0
  39. package/dist/next/api.d.ts +479 -0
  40. package/dist/next/api.mjs +0 -0
  41. package/dist/next/app-server.d.ts +44 -0
  42. package/dist/next/app-server.mjs +479 -0
  43. package/dist/next/app-server.mjs.map +1 -0
  44. package/dist/next/app-session.d.ts +49 -0
  45. package/dist/next/app-session.mjs +238 -0
  46. package/dist/next/app-session.mjs.map +1 -0
  47. package/dist/next/app.d.ts +29 -0
  48. package/dist/next/app.mjs +141 -0
  49. package/dist/next/app.mjs.map +1 -0
  50. package/dist/next/client/live-state.d.ts +63 -0
  51. package/dist/next/client/oauth.d.ts +12 -0
  52. package/dist/next/client/react.d.ts +109 -0
  53. package/dist/next/client/socket.d.ts +6 -0
  54. package/dist/next/client.mjs +156 -0
  55. package/dist/next/client.mjs.map +1 -0
  56. package/dist/next/expression.d.ts +146 -0
  57. package/dist/next/expression.mjs +399 -0
  58. package/dist/next/expression.mjs.map +1 -0
  59. package/dist/next/lib.d.ts +56 -0
  60. package/dist/next/lib.mjs +199 -0
  61. package/dist/next/lib.mjs.map +1 -0
  62. package/dist/next/oauth-scopes.d.ts +32 -0
  63. package/dist/next/oauth-scopes.mjs +40 -0
  64. package/dist/next/oauth-scopes.mjs.map +1 -0
  65. package/dist/next/oauth.mjs +29 -0
  66. package/dist/next/oauth.mjs.map +1 -0
  67. package/dist/next/principal.d.ts +64 -0
  68. package/dist/next/principal.mjs +98 -0
  69. package/dist/next/principal.mjs.map +1 -0
  70. package/dist/next/project-ingress.d.ts +37 -0
  71. package/dist/next/project-ingress.mjs +75 -0
  72. package/dist/next/project-ingress.mjs.map +1 -0
  73. package/dist/next/react.mjs +285 -0
  74. package/dist/next/react.mjs.map +1 -0
  75. package/dist/next/sdk/auth.d.ts +5 -0
  76. package/dist/next/sdk/index.d.ts +112 -0
  77. package/dist/next/sdk.mjs +139 -0
  78. package/dist/next/sdk.mjs.map +1 -0
  79. package/dist/next/stream/processor.d.ts +378 -0
  80. package/dist/next/stream/processor.mjs +582 -0
  81. package/dist/next/stream/processor.mjs.map +1 -0
  82. package/dist/next/stream/run.d.ts +58 -0
  83. package/dist/next/stream/run.mjs +40 -0
  84. package/dist/next/stream/run.mjs.map +1 -0
  85. package/dist/next-node.d.ts +15 -0
  86. package/dist/next-node.mjs +51 -0
  87. package/dist/next-node.mjs.map +1 -0
  88. package/dist/node.d.ts +3 -0
  89. package/dist/node.mjs +185 -0
  90. package/dist/node.mjs.map +1 -0
  91. package/dist/processor-host-capabilities-BMFH3KTM.mjs +56 -0
  92. package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +1 -0
  93. package/dist/processors/cloudflare.d.ts +3 -0
  94. package/dist/processors/durable-object-processor-durability.d.ts +79 -0
  95. package/dist/processors/event-consumption-metrics.d.ts +82 -0
  96. package/dist/processors/idempotency.d.ts +13 -0
  97. package/dist/processors/index.d.ts +12 -0
  98. package/dist/processors/processor-contracts.d.ts +342 -0
  99. package/dist/processors/processor-facet.d.ts +186 -0
  100. package/dist/processors/processor-host-capabilities.d.ts +60 -0
  101. package/dist/processors/prompt-sections.d.ts +17 -0
  102. package/dist/processors/rpc-types.d.ts +515 -0
  103. package/dist/processors/schemas.d.ts +102 -0
  104. package/dist/processors/stream-handle.d.ts +45 -0
  105. package/dist/processors/stream-processor-keepalive.d.ts +95 -0
  106. package/dist/processors/stream-processor-registry.d.ts +233 -0
  107. package/dist/processors/stream-processor-runner.d.ts +289 -0
  108. package/dist/processors/stream-processor.d.ts +339 -0
  109. package/dist/processors/stream-runtime-metrics.d.ts +107 -0
  110. package/dist/processors/testing.d.ts +302 -0
  111. package/dist/processors-BoNyeBfQ.mjs +10 -0
  112. package/dist/processors-BoNyeBfQ.mjs.map +1 -0
  113. package/dist/processors-cloudflare.mjs +3 -0
  114. package/dist/processors-testing.mjs +435 -0
  115. package/dist/processors-testing.mjs.map +1 -0
  116. package/dist/processors.mjs +52 -0
  117. package/dist/processors.mjs.map +1 -0
  118. package/dist/protocol-DnK_f2m6.mjs +251 -0
  119. package/dist/protocol-DnK_f2m6.mjs.map +1 -0
  120. package/dist/sdk/capnweb/index.d.ts +2 -0
  121. package/dist/sdk/capnweb/live-state/compact.d.ts +5 -0
  122. package/dist/sdk/capnweb/live-state/diff.d.ts +41 -0
  123. package/dist/sdk/capnweb/live-state/engine.d.ts +44 -0
  124. package/dist/sdk/capnweb/live-state/index.d.ts +41 -0
  125. package/dist/sdk/capnweb/live-state/protocol.d.ts +87 -0
  126. package/dist/sdk/capnweb/live-state/retain.d.ts +23 -0
  127. package/dist/sdk/capnweb/live-state/store.d.ts +20 -0
  128. package/dist/sdk/capnweb/live-state/types.d.ts +11 -0
  129. package/dist/sdk/capnweb/react.d.ts +45 -0
  130. package/dist/sdk/capnweb/react.mjs +316 -0
  131. package/dist/sdk/capnweb/react.mjs.map +1 -0
  132. package/dist/sdk/capnweb.mjs +4 -0
  133. package/dist/sdk/itx/react.d.ts +191 -0
  134. package/dist/sdk/itx/react.mjs +383 -0
  135. package/dist/sdk/itx/react.mjs.map +1 -0
  136. package/dist/sdk-DMB-IM11.mjs +933 -0
  137. package/dist/sdk-DMB-IM11.mjs.map +1 -0
  138. package/dist/sdk.d.ts +339 -0
  139. package/dist/sdk.mjs +2 -0
  140. package/dist/serve-itx.d.ts +46 -0
  141. package/dist/starter-apps/flake-dashboard/app-ref.d.ts +31 -0
  142. package/dist/starter-apps/flake-dashboard/configured-worker.mjs +1055 -0
  143. package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +1 -0
  144. package/dist/starter-apps/flake-dashboard/contract.d.ts +4839 -0
  145. package/dist/starter-apps/flake-dashboard/contract.mjs +2 -0
  146. package/dist/starter-apps/flake-dashboard/index.d.ts +17 -0
  147. package/dist/starter-apps/flake-dashboard/index.mjs +56 -0
  148. package/dist/starter-apps/flake-dashboard/index.mjs.map +1 -0
  149. package/dist/starter-apps/flake-dashboard/worker.d.ts +4607 -0
  150. package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +8914 -0
  151. package/dist/starter-apps/github-ai-linter/configured-worker.mjs +17987 -0
  152. package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +1 -0
  153. package/dist/starter-apps/github-ai-linter/contract.d.ts +9193 -0
  154. package/dist/starter-apps/github-ai-linter/index.d.ts +10 -0
  155. package/dist/starter-apps/github-ai-linter/index.mjs +36 -0
  156. package/dist/starter-apps/github-ai-linter/index.mjs.map +1 -0
  157. package/dist/starter-apps/github-ai-linter/prompt.d.ts +13 -0
  158. package/dist/starter-apps/github-ai-linter/review-bot.d.ts +808 -0
  159. package/dist/starter-apps/github-ai-linter/rules.d.ts +34 -0
  160. package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +19 -0
  161. package/dist/starter-apps/github-ai-linter/worker.d.ts +19 -0
  162. package/dist/starter-apps/github-ai-linter/worker.mjs +947 -0
  163. package/dist/starter-apps/github-ai-linter/worker.mjs.map +1 -0
  164. package/dist/starter-apps/guestbook/app-ref.d.ts +27 -0
  165. package/dist/starter-apps/guestbook/client.d.ts +7 -0
  166. package/dist/starter-apps/guestbook/client.mjs +59 -0
  167. package/dist/starter-apps/guestbook/configured-worker.mjs +205 -0
  168. package/dist/starter-apps/guestbook/configured-worker.mjs.map +1 -0
  169. package/dist/starter-apps/guestbook/index.d.ts +9 -0
  170. package/dist/starter-apps/guestbook/index.mjs +31 -0
  171. package/dist/starter-apps/guestbook/index.mjs.map +1 -0
  172. package/dist/starter-apps/guestbook/processor.d.ts +2267 -0
  173. package/dist/starter-apps/guestbook/worker.d.ts +26 -0
  174. package/dist/starter-apps/guestbook/worker.mjs +191 -0
  175. package/dist/starter-apps/guestbook/worker.mjs.map +1 -0
  176. package/dist/starter-apps/media/configured-worker.mjs +577 -0
  177. package/dist/starter-apps/media/configured-worker.mjs.map +1 -0
  178. package/dist/starter-apps/media/index.mjs +36 -0
  179. package/dist/starter-apps/media/index.mjs.map +1 -0
  180. package/dist/starter-apps/media/ref.mjs +20 -0
  181. package/dist/starter-apps/media/ref.mjs.map +1 -0
  182. package/dist/starter-apps/media/worker.mjs +579 -0
  183. package/dist/starter-apps/media/worker.mjs.map +1 -0
  184. package/dist/starter-apps/notes/configured-worker.mjs +6134 -0
  185. package/dist/starter-apps/notes/configured-worker.mjs.map +1 -0
  186. package/dist/starter-apps/notes/index.mjs +23 -0
  187. package/dist/starter-apps/notes/index.mjs.map +1 -0
  188. package/dist/starter-apps/notes/ref.mjs +21 -0
  189. package/dist/starter-apps/notes/ref.mjs.map +1 -0
  190. package/dist/starter-apps/notes/worker.mjs +427 -0
  191. package/dist/starter-apps/notes/worker.mjs.map +1 -0
  192. package/dist/starter-apps/todo/client.mjs +59 -0
  193. package/dist/starter-apps/todo/configured-worker.mjs +2864 -0
  194. package/dist/starter-apps/todo/configured-worker.mjs.map +1 -0
  195. package/dist/starter-apps/todo/index.d.ts +8 -0
  196. package/dist/starter-apps/todo/index.mjs +29 -0
  197. package/dist/starter-apps/todo/index.mjs.map +1 -0
  198. package/dist/stream-processor-keepalive-DAQTP6m3.mjs +2082 -0
  199. package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +1 -0
  200. package/dist/usingCtx-inzbY1Qz.mjs +57 -0
  201. package/dist/usingCtx-mZx5nsAW.mjs +11800 -0
  202. package/dist/usingCtx-mZx5nsAW.mjs.map +1 -0
  203. package/dist/worker-ref-DZxPDmb_.mjs +390 -0
  204. package/dist/worker-ref-DZxPDmb_.mjs.map +1 -0
  205. package/menubar/Iterate.entitlements +12 -0
  206. package/menubar/Iterate.swift +914 -0
  207. package/menubar/IterateIcon.swift +145 -0
  208. package/menubar/README.md +28 -0
  209. package/menubar/build-menubar-app.sh +59 -0
  210. package/package.json +235 -18
  211. package/dist/cli-DMS4kJph.mjs +0 -868
  212. package/dist/cli-DMS4kJph.mjs.map +0 -1
  213. package/dist/config-DtnR7Lv7.mjs +0 -170
  214. package/dist/config-DtnR7Lv7.mjs.map +0 -1
  215. package/dist/index.d.mts.map +0 -1
  216. package/dist/stream-tui/agent-chat-terminal.d.mts +0 -1
  217. package/dist/stream-tui/agent-chat-terminal.mjs +0 -933
  218. package/dist/stream-tui/agent-chat-terminal.mjs.map +0 -1
@@ -0,0 +1,205 @@
1
+ import { F as isStreamIdMismatchError, M as StreamIdMismatchError, d as STREAM_PROCESSOR_REVIVED_EVENT_TYPE, r as ProcessorKeepalive } from "./stream-processor-keepalive-DAQTP6m3.mjs";
2
+ //#region src/processors/durable-object-processor-durability.ts
3
+ /** The two-cursor progress record ({@link ProcessorProgress}). */
4
+ const processorProgressKey = (name) => `stream-processor:${name}:progress`;
5
+ /** The per-runner keepalive record ({@link KeepaliveRecord}). */
6
+ const processorKeepaliveKey = (name) => `stream-processor:${name}:keepalive`;
7
+ function isStreamKeepaliveRecord(value) {
8
+ if (typeof value !== "object" || value === null) return false;
9
+ const candidate = value;
10
+ return Number.isInteger(candidate.revivals) && (candidate.revivals ?? -1) >= 0 && typeof candidate.lastRevivalAt === "number" && Number.isFinite(candidate.lastRevivalAt) && typeof candidate.version === "string" && candidate.version.trim().length > 0 && (candidate.armedAtMs === null || typeof candidate.armedAtMs === "number" && Number.isFinite(candidate.armedAtMs)) && typeof candidate.streamId === "string" && candidate.streamId.trim().length > 0;
11
+ }
12
+ function sameKeepaliveAttempt(stored, streamId, attempt) {
13
+ return stored.streamId === streamId && stored.revivals === attempt.revivals && stored.lastRevivalAt === attempt.lastRevivalAt && stored.version === attempt.version && stored.armedAtMs === attempt.armedAtMs;
14
+ }
15
+ /**
16
+ * The runner's durable progress store over DO KV (`storage.kv` — synchronous,
17
+ * single-threaded isolate, so read-check-write is atomic without awaits).
18
+ */
19
+ function durableObjectProgressStore(args) {
20
+ const { storage, name } = args;
21
+ const progressKey = processorProgressKey(name);
22
+ const cachedProgress = (progress) => {
23
+ const reductionCache = args.reductionCache;
24
+ if (!reductionCache || reductionCache.shouldCacheReduction(progress.reduction.state)) return progress;
25
+ return {
26
+ ...progress,
27
+ reduction: {
28
+ ...progress.reduction,
29
+ reducedThroughOffset: 0,
30
+ state: reductionCache.initialState()
31
+ }
32
+ };
33
+ };
34
+ return {
35
+ read: () => storage.kv.get(progressKey),
36
+ commit: (progress, opts) => {
37
+ const persisted = storage.kv.get(progressKey);
38
+ if (persisted?.streamId !== opts.expectedStreamId) throw new Error(`stream processor "${name}" progress commit fenced: expected stream ID ${String(opts.expectedStreamId)}, persisted ${String(persisted?.streamId)} — the stream lifetime changed after this continuation began`);
39
+ const persistedRevision = persisted?.processing.cursorRevision ?? 0;
40
+ if (opts.expectedCursorRevision !== persistedRevision) throw new Error(`stream processor "${name}" progress commit fenced: expected cursorRevision ${opts.expectedCursorRevision}, persisted ${persistedRevision} — a cursor rewind landed after this continuation began`);
41
+ if (persisted !== void 0 && progress.processing.acknowledgedThroughOffset < persisted.processing.acknowledgedThroughOffset && progress.processing.cursorRevision <= persistedRevision) throw new Error(`stream processor "${name}" progress commit fenced: acknowledgedThroughOffset would move backward (${persisted.processing.acknowledgedThroughOffset} -> ${progress.processing.acknowledgedThroughOffset}) without a cursorRevision bump — a stale incarnation is rolling the cursor back`);
42
+ storage.kv.put(progressKey, cachedProgress(progress));
43
+ },
44
+ replaceForStream: (progress, opts) => {
45
+ const persisted = storage.kv.get(progressKey);
46
+ if (persisted?.streamId !== opts.expectedStreamId || persisted.processing.cursorRevision !== opts.expectedCursorRevision) throw new Error(`stream processor "${name}" stream replacement fenced: expected ${opts.expectedStreamId}@${opts.expectedCursorRevision}, persisted ${String(persisted?.streamId)}@${persisted?.processing.cursorRevision ?? 0}`);
47
+ args.resetForStream?.();
48
+ storage.kv.delete(processorKeepaliveKey(name));
49
+ storage.kv.put(progressKey, cachedProgress(progress));
50
+ }
51
+ };
52
+ }
53
+ /**
54
+ * The runner's recovery adapter for a Durable Object: wraps ONE
55
+ * {@link ProcessorKeepalive} for THIS runner (per-runner recovery identity, so
56
+ * a revival names exactly which processor owed work). The keepalive machinery
57
+ * — mark-before-work, bounded backoff, quiet-clean reset,
58
+ * deploy-version reset, wedged-work detection — is reused wholesale, never
59
+ * reimplemented.
60
+ *
61
+ * DO-shaped seams are INJECTED, not reached for:
62
+ * - `armAlarm` — the hosting registry's alarm slice for this runner. A DO
63
+ * has ONE alarm; the registry merges every runner's desire (plus its own)
64
+ * and arms the earliest, exactly like the host's `setAlarmSlice`. This
65
+ * adapter never touches `storage.setAlarm`.
66
+ * - `waitUntil` — calls the hosting DO's `ctx.waitUntil`, keeping the
67
+ * incarnation alive while tracked work runs.
68
+ *
69
+ * Revival appends the core `stream/processor-revived` fact (ONE type for
70
+ * every processor — {@link STREAM_PROCESSOR_REVIVED_EVENT_TYPE}; the payload's
71
+ * `processorSlug` and the idempotency key carry the per-processor identity)
72
+ * to the stream and STOPS: the append wakes the source stream's event sender (its
73
+ * `woken` handlers cold-boot the stream DO if the deploy evicted it too), and
74
+ * wake-mode delivery reaches head and guarantees a turn: either the contract
75
+ * consumes the fact and receives it, or the runner supplies its eventless
76
+ * `processEvent(event: null, caughtUp: true)` pass. No self-driven catch-up
77
+ * here, unlike the host's `catchUpInternal` loop: delivery has ONE entrypoint.
78
+ *
79
+ * Construction re-issues a persisted armed desire through `armAlarm` (the
80
+ * host's boot-time reconcile): a platform `setAlarm` that failed after the KV
81
+ * record committed — or an eviction in the fire→re-arm window — would
82
+ * otherwise leave the only thing that revives this DO permanently lost.
83
+ */
84
+ function durableObjectRecovery(args) {
85
+ const now = args.now ?? (() => Date.now());
86
+ const recordKey = processorKeepaliveKey(args.name);
87
+ const progressKey = processorProgressKey(args.name);
88
+ const readProgress = () => args.storage.kv.get(progressKey);
89
+ const discardStoredRecord = (reason) => {
90
+ args.storage.kv.delete(recordKey);
91
+ args.armAlarm(null);
92
+ console.warn(`stream processor "${args.name}" discarded its recovery record: ${reason}`);
93
+ };
94
+ const readStoredRecord = (context) => {
95
+ const value = args.storage.kv.get(recordKey);
96
+ if (value === void 0) return void 0;
97
+ if (!isStreamKeepaliveRecord(value)) {
98
+ discardStoredRecord(`invalid record found while ${context}`);
99
+ return;
100
+ }
101
+ return value;
102
+ };
103
+ const requireProgressStreamId = () => {
104
+ const streamId = readProgress()?.streamId;
105
+ if (streamId === void 0 || streamId.trim().length === 0) throw new Error(`stream processor "${args.name}" cannot arm recovery before its stream lifetime is bound`);
106
+ return streamId;
107
+ };
108
+ const assertProgressStreamId = (expectedStreamId) => {
109
+ const currentStreamId = readProgress()?.streamId;
110
+ if (currentStreamId !== expectedStreamId) throw new StreamIdMismatchError(`stream processor "${args.name}" recovery belongs to stream ID ${expectedStreamId}, but current progress belongs to ${String(currentStreamId)}`);
111
+ };
112
+ const readCurrentStoredRecord = (context) => {
113
+ const record = readStoredRecord(context);
114
+ if (record === void 0) return void 0;
115
+ const progressStreamId = readProgress()?.streamId;
116
+ if (progressStreamId !== record.streamId) {
117
+ discardStoredRecord(`record belongs to stream ID ${record.streamId}, current progress belongs to ${String(progressStreamId)}`);
118
+ return;
119
+ }
120
+ return record;
121
+ };
122
+ const appendRevived = async (streamId, record) => {
123
+ assertProgressStreamId(streamId);
124
+ await args.stream.appendIfStreamId({
125
+ streamId,
126
+ events: [{
127
+ type: STREAM_PROCESSOR_REVIVED_EVENT_TYPE,
128
+ idempotencyKey: `processor-revived:${args.name}@${record.version}:${record.revivals}:${record.lastRevivalAt}`,
129
+ payload: {
130
+ processorSlug: args.name,
131
+ revivals: record.revivals,
132
+ version: record.version
133
+ }
134
+ }]
135
+ });
136
+ };
137
+ let activeKeepalive;
138
+ const keepaliveFor = (streamId) => {
139
+ if (activeKeepalive?.streamId === streamId) return activeKeepalive.keepalive;
140
+ const readRecord = () => {
141
+ const record = readCurrentStoredRecord("reading it");
142
+ return record?.streamId === streamId ? record : void 0;
143
+ };
144
+ const keepalive = new ProcessorKeepalive({
145
+ now,
146
+ readRecord,
147
+ writeRecord: (record) => {
148
+ assertProgressStreamId(streamId);
149
+ const persisted = readStoredRecord("arming recovery");
150
+ if (persisted !== void 0 && persisted.streamId !== streamId) throw new StreamIdMismatchError(`stream processor "${args.name}" cannot arm recovery for stream ID ${streamId} over the record for ${persisted.streamId}`);
151
+ args.storage.kv.put(recordKey, {
152
+ ...record,
153
+ streamId
154
+ });
155
+ },
156
+ armAlarm: (atMs) => args.armAlarm(atMs),
157
+ keepAlive: (work) => args.waitUntil(work),
158
+ revive: (record) => appendRevived(streamId, record),
159
+ discardFailedRevival: (error, record) => {
160
+ if (!isStreamIdMismatchError(error)) return false;
161
+ const stored = readStoredRecord("discarding a stale revival");
162
+ if (stored === void 0) args.armAlarm(null);
163
+ else if (sameKeepaliveAttempt(stored, streamId, record)) discardStoredRecord(`stream ID ${streamId} was replaced`);
164
+ return true;
165
+ },
166
+ appendFact: (event) => {
167
+ Promise.resolve().then(() => {
168
+ assertProgressStreamId(streamId);
169
+ return args.stream.appendIfStreamId({
170
+ streamId,
171
+ events: [event]
172
+ });
173
+ }).catch((error) => {
174
+ console.error(`stream processor "${args.name}" keepalive evidence append failed`, error);
175
+ });
176
+ },
177
+ version: args.version
178
+ });
179
+ activeKeepalive = {
180
+ streamId,
181
+ keepalive
182
+ };
183
+ return keepalive;
184
+ };
185
+ const recovery = {
186
+ keepAliveWhile: (work) => keepaliveFor(requireProgressStreamId()).track(work()),
187
+ handleAlarm: async () => {
188
+ const record = readCurrentStoredRecord("handling an alarm");
189
+ if (record === void 0) return;
190
+ await keepaliveFor(record.streamId).onAlarm();
191
+ },
192
+ resetBackoff: () => {
193
+ const record = readCurrentStoredRecord("resetting the backoff");
194
+ if (record === void 0) return;
195
+ keepaliveFor(record.streamId).resetBackoff();
196
+ }
197
+ };
198
+ const persisted = readCurrentStoredRecord("booting");
199
+ if (persisted !== void 0 && persisted.armedAtMs !== null) args.armAlarm(persisted.armedAtMs);
200
+ return recovery;
201
+ }
202
+ //#endregion
203
+ export { processorProgressKey as i, durableObjectRecovery as n, processorKeepaliveKey as r, durableObjectProgressStore as t };
204
+
205
+ //# sourceMappingURL=durable-object-processor-durability-CNsTjAJS.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-object-processor-durability-CNsTjAJS.mjs","names":[],"sources":["../src/processors/durable-object-processor-durability.ts"],"sourcesContent":["// The Cloudflare Durable Object implementations of the StreamProcessorRunner's\n// two durability adapters (stream-processor-runner.ts):\n//\n// - `durableObjectProgressStore` — the CAS-fenced two-cursor\n// {@link ProcessorProgressStore} over the DO's synchronous KV facade.\n// - `durableObjectRecovery` — the {@link ProcessorRecovery} adapter for a\n// durable processor that owns background work: ONE ProcessorKeepalive per\n// runner, storage-backed per registered NAME, with the DO-shaped seams\n// (alarm slice, waitUntil) INJECTED so nothing here touches\n// `storage.setAlarm` or a Cloudflare ctx directly.\n//\n// This file is deliberately runtime-light so its tests run in plain-node\n// vitest over an in-memory `storage.kv` fake\n// (durable-object-processor-durability.test.ts).\n\nimport type { ProcessorStream } from \"./stream-handle.ts\";\nimport { STREAM_PROCESSOR_REVIVED_EVENT_TYPE } from \"./processor-contracts.ts\";\nimport { isStreamIdMismatchError, StreamIdMismatchError } from \"./rpc-types.ts\";\nimport { ProcessorKeepalive, type KeepaliveRecord } from \"./stream-processor-keepalive.ts\";\nimport type {\n ProcessorProgress,\n ProcessorProgressStore,\n ProcessorRecovery,\n} from \"./stream-processor-runner.ts\";\n\n// -----------------------------------------------------------------------------\n// Key layout. Per registered NAME (the subscription name, which equals the\n// contract slug — one identity), all under the `stream-processor:` prefix.\n// -----------------------------------------------------------------------------\n\n/** The two-cursor progress record ({@link ProcessorProgress}). */\nexport const processorProgressKey = (name: string) => `stream-processor:${name}:progress`;\n\n/** The per-runner keepalive record ({@link KeepaliveRecord}). */\nexport const processorKeepaliveKey = (name: string) => `stream-processor:${name}:keepalive`;\n\n/** A recovery alarm belongs to one stream lifetime, not merely one path. */\ntype StreamKeepaliveRecord = KeepaliveRecord & { streamId: string };\n\nfunction isStreamKeepaliveRecord(value: unknown): value is StreamKeepaliveRecord {\n if (typeof value !== \"object\" || value === null) return false;\n const candidate = value as Partial<StreamKeepaliveRecord>;\n return (\n Number.isInteger(candidate.revivals) &&\n (candidate.revivals ?? -1) >= 0 &&\n typeof candidate.lastRevivalAt === \"number\" &&\n Number.isFinite(candidate.lastRevivalAt) &&\n typeof candidate.version === \"string\" &&\n candidate.version.trim().length > 0 &&\n (candidate.armedAtMs === null ||\n (typeof candidate.armedAtMs === \"number\" && Number.isFinite(candidate.armedAtMs))) &&\n typeof candidate.streamId === \"string\" &&\n candidate.streamId.trim().length > 0\n );\n}\n\nfunction sameKeepaliveAttempt(\n stored: StreamKeepaliveRecord,\n streamId: string,\n attempt: KeepaliveRecord,\n): boolean {\n return (\n stored.streamId === streamId &&\n stored.revivals === attempt.revivals &&\n stored.lastRevivalAt === attempt.lastRevivalAt &&\n stored.version === attempt.version &&\n stored.armedAtMs === attempt.armedAtMs\n );\n}\n\n/**\n * The runner's durable progress store over DO KV (`storage.kv` — synchronous,\n * single-threaded isolate, so read-check-write is atomic without awaits).\n */\nexport function durableObjectProgressStore<State>(args: {\n storage: DurableObjectStorage;\n /** The registered processor name (subscription name = contract slug) —\n * keys the progress record. */\n name: string;\n /** Synchronously clear related projections when the source stream is replaced. */\n resetForStream?: () => void;\n /**\n * An optional bounded reduction cache. Processing cursors always remain\n * durable; a cold runner refolds reduce-only from initialState when the\n * predicate declines a large cache.\n */\n reductionCache?: {\n shouldCacheReduction(state: State): boolean;\n initialState(): State;\n };\n}): ProcessorProgressStore<State> {\n const { storage, name } = args;\n const progressKey = processorProgressKey(name);\n const cachedProgress = (progress: ProcessorProgress<State>): ProcessorProgress<State> => {\n const reductionCache = args.reductionCache;\n if (!reductionCache || reductionCache.shouldCacheReduction(progress.reduction.state))\n return progress;\n return {\n ...progress,\n reduction: {\n ...progress.reduction,\n reducedThroughOffset: 0,\n state: reductionCache.initialState(),\n },\n };\n };\n\n return {\n read: () => storage.kv.get<ProcessorProgress<State>>(progressKey),\n commit: (progress, opts) => {\n // CAS fence: read-check-write with NO intervening awaits — DO storage is\n // synchronous and the isolate single-threaded, so this whole block is\n // atomic. An absent record reads as revision 0.\n const persisted = storage.kv.get<ProcessorProgress<State>>(progressKey);\n if (persisted?.streamId !== opts.expectedStreamId) {\n throw new Error(\n `stream processor \"${name}\" progress commit fenced: expected stream ID ` +\n `${String(opts.expectedStreamId)}, persisted ${String(persisted?.streamId)} — ` +\n `the stream lifetime changed after this continuation began`,\n );\n }\n const persistedRevision = persisted?.processing.cursorRevision ?? 0;\n if (opts.expectedCursorRevision !== persistedRevision) {\n throw new Error(\n `stream processor \"${name}\" progress commit fenced: expected cursorRevision ` +\n `${opts.expectedCursorRevision}, persisted ${persistedRevision} — ` +\n `a cursor rewind landed after this continuation began`,\n );\n }\n // MONOTONIC fence: the revision CAS alone cannot stop a stale\n // incarnation at the SAME revision from rolling acknowledgement (and\n // state) backward past progress a newer incarnation already committed —\n // re-running effects that were durably acknowledged. Only an explicit\n // revision-bumping cursor rewind may move acked backward.\n if (\n persisted !== undefined &&\n progress.processing.acknowledgedThroughOffset <\n persisted.processing.acknowledgedThroughOffset &&\n progress.processing.cursorRevision <= persistedRevision\n ) {\n throw new Error(\n `stream processor \"${name}\" progress commit fenced: acknowledgedThroughOffset would ` +\n `move backward (${persisted.processing.acknowledgedThroughOffset} -> ` +\n `${progress.processing.acknowledgedThroughOffset}) without a cursorRevision bump — ` +\n `a stale incarnation is rolling the cursor back`,\n );\n }\n storage.kv.put(progressKey, cachedProgress(progress));\n },\n replaceForStream: (progress, opts) => {\n const persisted = storage.kv.get<ProcessorProgress<State>>(progressKey);\n if (\n persisted?.streamId !== opts.expectedStreamId ||\n persisted.processing.cursorRevision !== opts.expectedCursorRevision\n ) {\n throw new Error(\n `stream processor \"${name}\" stream replacement fenced: expected ` +\n `${opts.expectedStreamId}@${opts.expectedCursorRevision}, persisted ` +\n `${String(persisted?.streamId)}@${persisted?.processing.cursorRevision ?? 0}`,\n );\n }\n // Old-lifetime recovery desires must not append revival facts into the\n // recreated stream. A new obligation will arm a fresh record.\n args.resetForStream?.();\n storage.kv.delete(processorKeepaliveKey(name));\n storage.kv.put(progressKey, cachedProgress(progress));\n },\n };\n}\n\n/**\n * The runner's recovery adapter for a Durable Object: wraps ONE\n * {@link ProcessorKeepalive} for THIS runner (per-runner recovery identity, so\n * a revival names exactly which processor owed work). The keepalive machinery\n * — mark-before-work, bounded backoff, quiet-clean reset,\n * deploy-version reset, wedged-work detection — is reused wholesale, never\n * reimplemented.\n *\n * DO-shaped seams are INJECTED, not reached for:\n * - `armAlarm` — the hosting registry's alarm slice for this runner. A DO\n * has ONE alarm; the registry merges every runner's desire (plus its own)\n * and arms the earliest, exactly like the host's `setAlarmSlice`. This\n * adapter never touches `storage.setAlarm`.\n * - `waitUntil` — calls the hosting DO's `ctx.waitUntil`, keeping the\n * incarnation alive while tracked work runs.\n *\n * Revival appends the core `stream/processor-revived` fact (ONE type for\n * every processor — {@link STREAM_PROCESSOR_REVIVED_EVENT_TYPE}; the payload's\n * `processorSlug` and the idempotency key carry the per-processor identity)\n * to the stream and STOPS: the append wakes the source stream's event sender (its\n * `woken` handlers cold-boot the stream DO if the deploy evicted it too), and\n * wake-mode delivery reaches head and guarantees a turn: either the contract\n * consumes the fact and receives it, or the runner supplies its eventless\n * `processEvent(event: null, caughtUp: true)` pass. No self-driven catch-up\n * here, unlike the host's `catchUpInternal` loop: delivery has ONE entrypoint.\n *\n * Construction re-issues a persisted armed desire through `armAlarm` (the\n * host's boot-time reconcile): a platform `setAlarm` that failed after the KV\n * record committed — or an eviction in the fire→re-arm window — would\n * otherwise leave the only thing that revives this DO permanently lost.\n */\nexport function durableObjectRecovery(args: {\n storage: DurableObjectStorage;\n /** The registered processor name (subscription name = contract slug) —\n * keys the per-runner keepalive record and the revival fact's idempotency\n * key, and fills the revival payload's `processorSlug`. */\n name: string;\n /** The processor's home stream: revived facts and crash-loop evidence land here. */\n stream: ProcessorStream;\n /** Worker deploy version; a change resets the keepalive's crash-loop budget\n * (the antidote deploy). Pass `workerVersion(env)`. REQUIRED for the same\n * reason the host requires it: a silent default could never take the\n * version-reset code path. */\n version: string;\n /** The registry's alarm-slice seam for this runner (null = disarm). */\n armAlarm: (atMs: number | null) => void;\n /** The hosting DO's `ctx.waitUntil` — keeps the incarnation alive while\n * tracked work runs. */\n waitUntil: (work: Promise<unknown>) => void;\n /** Injected clock for the test harness; production uses Date.now. */\n now?: () => number;\n}): ProcessorRecovery {\n const now = args.now ?? (() => Date.now());\n const recordKey = processorKeepaliveKey(args.name);\n const progressKey = processorProgressKey(args.name);\n const readProgress = () => args.storage.kv.get<ProcessorProgress<unknown>>(progressKey);\n\n const discardStoredRecord = (reason: string): void => {\n args.storage.kv.delete(recordKey);\n args.armAlarm(null);\n console.warn(`stream processor \"${args.name}\" discarded its recovery record: ${reason}`);\n };\n\n const readStoredRecord = (context: string): StreamKeepaliveRecord | undefined => {\n const value = args.storage.kv.get<unknown>(recordKey);\n if (value === undefined) return undefined;\n if (!isStreamKeepaliveRecord(value)) {\n discardStoredRecord(`invalid record found while ${context}`);\n return undefined;\n }\n return value;\n };\n\n const requireProgressStreamId = (): string => {\n const streamId = readProgress()?.streamId;\n if (streamId === undefined || streamId.trim().length === 0) {\n throw new Error(\n `stream processor \"${args.name}\" cannot arm recovery before its stream lifetime is bound`,\n );\n }\n return streamId;\n };\n\n const assertProgressStreamId = (expectedStreamId: string): void => {\n const currentStreamId = readProgress()?.streamId;\n if (currentStreamId !== expectedStreamId) {\n throw new StreamIdMismatchError(\n `stream processor \"${args.name}\" recovery belongs to stream ID ${expectedStreamId}, ` +\n `but current progress belongs to ${String(currentStreamId)}`,\n );\n }\n };\n\n const readCurrentStoredRecord = (context: string): StreamKeepaliveRecord | undefined => {\n const record = readStoredRecord(context);\n if (record === undefined) return undefined;\n const progressStreamId = readProgress()?.streamId;\n if (progressStreamId !== record.streamId) {\n discardStoredRecord(\n `record belongs to stream ID ${record.streamId}, current progress belongs to ${String(\n progressStreamId,\n )}`,\n );\n return undefined;\n }\n return record;\n };\n\n const appendRevived = async (streamId: string, record: KeepaliveRecord): Promise<void> => {\n // Check both sides of the RPC boundary. The progress check stops an old\n // local continuation immediately; appendIfStreamId closes the remaining\n // race if the path is deleted and recreated while the RPC is in flight.\n assertProgressStreamId(streamId);\n await args.stream.appendIfStreamId({\n streamId,\n events: [\n {\n type: STREAM_PROCESSOR_REVIVED_EVENT_TYPE,\n idempotencyKey:\n `processor-revived:${args.name}` +\n `@${record.version}:${record.revivals}:${record.lastRevivalAt}`,\n payload: {\n // The registered name IS the contract slug (one identity).\n processorSlug: args.name,\n revivals: record.revivals,\n version: record.version,\n },\n },\n ],\n });\n };\n\n let activeKeepalive: { streamId: string; keepalive: ProcessorKeepalive } | undefined;\n\n const keepaliveFor = (streamId: string): ProcessorKeepalive => {\n if (activeKeepalive?.streamId === streamId) return activeKeepalive.keepalive;\n\n const readRecord = (): KeepaliveRecord | undefined => {\n const record = readCurrentStoredRecord(\"reading it\");\n return record?.streamId === streamId ? record : undefined;\n };\n const keepalive = new ProcessorKeepalive({\n now,\n readRecord,\n writeRecord: (record) => {\n assertProgressStreamId(streamId);\n const persisted = readStoredRecord(\"arming recovery\");\n if (persisted !== undefined && persisted.streamId !== streamId) {\n throw new StreamIdMismatchError(\n `stream processor \"${args.name}\" cannot arm recovery for stream ID ${streamId} ` +\n `over the record for ${persisted.streamId}`,\n );\n }\n args.storage.kv.put(recordKey, { ...record, streamId } satisfies StreamKeepaliveRecord);\n },\n armAlarm: (atMs) => args.armAlarm(atMs),\n keepAlive: (work) => args.waitUntil(work),\n // Append the journaled fact and stop — the wake delivery of that fact is\n // the recovery turn. Failures throw: the keepalive's breaker owns retries.\n revive: (record) => appendRevived(streamId, record),\n // A stream ID never becomes current again. Retrying its revival would\n // wake forever, so discard exactly this attempt. If a newer lifetime or\n // attempt already replaced the record, leave its alarm desire untouched.\n discardFailedRevival: (error, record) => {\n if (!isStreamIdMismatchError(error)) return false;\n const stored = readStoredRecord(\"discarding a stale revival\");\n if (stored === undefined) {\n args.armAlarm(null);\n } else if (sameKeepaliveAttempt(stored, streamId, record)) {\n discardStoredRecord(`stream ID ${streamId} was replaced`);\n }\n return true;\n },\n // Best-effort journal evidence (crash-loop warnings). The stream-ID\n // guard prevents an old lifetime's evidence from landing in its replacement.\n appendFact: (event) => {\n void Promise.resolve()\n .then(() => {\n assertProgressStreamId(streamId);\n return args.stream.appendIfStreamId({ streamId, events: [event] });\n })\n .catch((error: unknown) => {\n console.error(\n `stream processor \"${args.name}\" keepalive evidence append failed`,\n error,\n );\n });\n },\n version: args.version,\n });\n activeKeepalive = { streamId, keepalive };\n return keepalive;\n };\n\n const recovery: ProcessorRecovery = {\n // The host's wiring verbatim (stream-processor-host.ts:445): every\n // registered closure — blocking, background, and the runner's whole-batch\n // attempt — rides the keepalive, so \"the DO died owing work\" is exactly\n // \"the DO died with the alarm armed\".\n keepAliveWhile: (work) => keepaliveFor(requireProgressStreamId()).track(work()),\n handleAlarm: async () => {\n // Delegation IS the whole handler: every alarm action drives the\n // injected seams from inside onAlarm before it returns — busy_rearmed\n // re-arms at the lead, clean_disarmed disarms via armAlarm(null), the\n // revival outcomes record + arm the backoff and run the revive hook (which\n // appends the revived fact). The keepalive self-gates on its persisted\n // armed time, so a fire belonging to another slice of the shared DO\n // alarm is a no-op here — route every fire to every runner.\n const record = readCurrentStoredRecord(\"handling an alarm\");\n if (record === undefined) return;\n await keepaliveFor(record.streamId).onAlarm();\n },\n // The operator's no-deploy antidote for a 3-strikes plateau: clear the\n // crash-loop budget and pull the owed retry to the lead. Delegates\n // wholesale to the keepalive (the budget's one owner).\n resetBackoff: () => {\n const record = readCurrentStoredRecord(\"resetting the backoff\");\n if (record === undefined) return;\n keepaliveFor(record.streamId).resetBackoff();\n },\n };\n\n // Boot-time reconcile (the host's post-construction block verbatim): a\n // fresh incarnation restores — and RE-ISSUES — the persisted alarm desire,\n // so a lost platform alarm heals when the host next opens instead of never.\n const persisted = readCurrentStoredRecord(\"booting\");\n if (persisted !== undefined && persisted.armedAtMs !== null) {\n args.armAlarm(persisted.armedAtMs);\n }\n\n return recovery;\n}\n"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ //#region src/processors/idempotency.ts
2
+ const IDEMPOTENCY_CONFLICT_FRAGMENT = " already names a different event at offset ";
3
+ function idempotencyConflictMessage(idempotencyKey, existingOffset) {
4
+ return `idempotency key "${idempotencyKey}"${IDEMPOTENCY_CONFLICT_FRAGMENT}${existingOffset}`;
5
+ }
6
+ function isIdempotencyConflict(error) {
7
+ return (error instanceof Error ? error.message : String(error)).includes(IDEMPOTENCY_CONFLICT_FRAGMENT);
8
+ }
9
+ /** Whether a requested append names the SAME event an idempotency key already committed. */
10
+ function sameIdempotentEvent(existing, requested) {
11
+ return existing.type === requested.type && jsonValuesEqual(existing.payload, requested.payload) && jsonValuesEqual(existing.metadata, requested.metadata) && existing.ephemeral === requested.ephemeral;
12
+ }
13
+ /** Structural JSON equality (key-order-insensitive). */
14
+ function jsonValuesEqual(left, right) {
15
+ if (Object.is(left, right)) return true;
16
+ if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => jsonValuesEqual(value, right[index]));
17
+ if (typeof left === "object" && typeof right === "object" && left !== null && right !== null) {
18
+ const leftKeys = Object.keys(left).sort();
19
+ const rightKeys = Object.keys(right).sort();
20
+ if (leftKeys.length !== rightKeys.length) return false;
21
+ return leftKeys.every((key, index) => key === rightKeys[index] && jsonValuesEqual(left[key], right[key]));
22
+ }
23
+ return false;
24
+ }
25
+ //#endregion
26
+ export { sameIdempotentEvent as i, isIdempotencyConflict as n, jsonValuesEqual as r, idempotencyConflictMessage as t };
27
+
28
+ //# sourceMappingURL=idempotency-DleloJNt.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency-DleloJNt.mjs","names":[],"sources":["../src/processors/idempotency.ts"],"sourcesContent":["// The Stream DO's idempotency semantics, importable: a same-key append is a\n// dedup ONLY when its body is structurally identical — otherwise it must be\n// rejected. Lives here so the production Stream Durable Object and the\n// MemoryStream test double share ONE predicate and cannot drift.\n\ntype EventBody = {\n type: string;\n payload?: unknown;\n metadata?: unknown;\n ephemeral?: boolean | undefined;\n};\n\n// The wording is a live wire contract: the rejection crosses Workers RPC,\n// which preserves the message but not class identity, so deployed processors\n// classify the conflict by this text. Mint with the builder, match with the\n// predicate — never spell the wording anywhere else.\nconst IDEMPOTENCY_CONFLICT_FRAGMENT = \" already names a different event at offset \";\n\nexport function idempotencyConflictMessage(idempotencyKey: string, existingOffset: number) {\n return `idempotency key \"${idempotencyKey}\"${IDEMPOTENCY_CONFLICT_FRAGMENT}${existingOffset}`;\n}\n\nexport function isIdempotencyConflict(error: unknown) {\n const message = error instanceof Error ? error.message : String(error);\n return message.includes(IDEMPOTENCY_CONFLICT_FRAGMENT);\n}\n\n/** Whether a requested append names the SAME event an idempotency key already committed. */\nexport function sameIdempotentEvent(existing: EventBody, requested: EventBody): boolean {\n return (\n existing.type === requested.type &&\n jsonValuesEqual(existing.payload, requested.payload) &&\n jsonValuesEqual(existing.metadata, requested.metadata) &&\n existing.ephemeral === requested.ephemeral\n );\n}\n\n/** Structural JSON equality (key-order-insensitive). */\nexport function jsonValuesEqual(left: unknown, right: unknown): boolean {\n if (Object.is(left, right)) return true;\n if (Array.isArray(left) || Array.isArray(right)) {\n return (\n Array.isArray(left) &&\n Array.isArray(right) &&\n left.length === right.length &&\n left.every((value, index) => jsonValuesEqual(value, right[index]))\n );\n }\n if (typeof left === \"object\" && typeof right === \"object\" && left !== null && right !== null) {\n const leftKeys = Object.keys(left).sort();\n const rightKeys = Object.keys(right).sort();\n if (leftKeys.length !== rightKeys.length) return false;\n return leftKeys.every(\n (key, index) =>\n key === rightKeys[index] &&\n jsonValuesEqual(\n (left as Record<string, unknown>)[key],\n (right as Record<string, unknown>)[key],\n ),\n );\n }\n return false;\n}\n"],"mappings":";AAgBA,MAAM,gCAAgC;AAEtC,SAAgB,2BAA2B,gBAAwB,gBAAwB;CACzF,OAAO,oBAAoB,eAAe,GAAG,gCAAgC;AAC/E;AAEA,SAAgB,sBAAsB,OAAgB;CAEpD,QADgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAA,CACtD,SAAS,6BAA6B;AACvD;;AAGA,SAAgB,oBAAoB,UAAqB,WAA+B;CACtF,OACE,SAAS,SAAS,UAAU,QAC5B,gBAAgB,SAAS,SAAS,UAAU,OAAO,KACnD,gBAAgB,SAAS,UAAU,UAAU,QAAQ,KACrD,SAAS,cAAc,UAAU;AAErC;;AAGA,SAAgB,gBAAgB,MAAe,OAAyB;CACtE,IAAI,OAAO,GAAG,MAAM,KAAK,GAAG,OAAO;CACnC,IAAI,MAAM,QAAQ,IAAI,KAAK,MAAM,QAAQ,KAAK,GAC5C,OACE,MAAM,QAAQ,IAAI,KAClB,MAAM,QAAQ,KAAK,KACnB,KAAK,WAAW,MAAM,UACtB,KAAK,OAAO,OAAO,UAAU,gBAAgB,OAAO,MAAM,MAAM,CAAC;CAGrE,IAAI,OAAO,SAAS,YAAY,OAAO,UAAU,YAAY,SAAS,QAAQ,UAAU,MAAM;EAC5F,MAAM,WAAW,OAAO,KAAK,IAAI,CAAC,CAAC,KAAK;EACxC,MAAM,YAAY,OAAO,KAAK,KAAK,CAAC,CAAC,KAAK;EAC1C,IAAI,SAAS,WAAW,UAAU,QAAQ,OAAO;EACjD,OAAO,SAAS,OACb,KAAK,UACJ,QAAQ,UAAU,UAClB,gBACG,KAAiC,MACjC,MAAkC,IACrC,CACJ;CACF;CACA,OAAO;AACT"}
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  //#region src/index.ts
2
2
  async function runCli() {
3
- await (await import("./cli-DMS4kJph.mjs")).runCli();
3
+ await (await import("./cli-D0c-pDL_.mjs")).runCli();
4
4
  }
5
5
  //#endregion
6
6
  export { runCli };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * An OS deployment's `/api` WebSocket URL from its http(s) base — the ONE
3
+ * place that knows the path and the ws/wss protocol swap, shared by the
4
+ * shared session and the one-shot Node client.
5
+ */
6
+ export declare function apiWebSocketUrl(baseUrl: string): URL;
@@ -0,0 +1,65 @@
1
+ import { type RpcStub as CapnRpcStub } from "@iterate-com/capnweb";
2
+ import type { Agent, ItxAuthCredentials, Project, Session, UnauthenticatedOs } from "../itx-api.generated.ts";
3
+ export type ItxWebSocketMessage = [timestamp: number, direction: "in" | "out", data: unknown];
4
+ type ConnectItxBaseInput = {
5
+ /** OS deployment base URL, e.g. the config's APP_CONFIG_BASE_URL. */
6
+ baseUrl: string;
7
+ /** Node WebSocket handshake headers, used by CLI/server callers with cookies. */
8
+ headers?: Record<string, string>;
9
+ /** Observe every decoded ws frame (e.g. the e2e suite's frame recorder). */
10
+ onWebSocketMessage?: (message: ItxWebSocketMessage) => void;
11
+ /**
12
+ * Observe the returned connection closing. Failed pre-ready dials use onRetry.
13
+ *
14
+ * A client whose job is to stay connected (a device, a long-lived agent)
15
+ * reconnects from HERE — the moment the transport dies — not lazily when
16
+ * its next call fails. The hook is a passive observer: this client stays
17
+ * vanilla capnweb and never reconnects, pings, or retries by itself; the
18
+ * consumer owns that loop.
19
+ */
20
+ onWebSocketClose?: (close: {
21
+ code: number;
22
+ reason: string;
23
+ }) => void;
24
+ };
25
+ type ConnectItxAuthenticatedInput = ConnectItxBaseInput & {
26
+ auth: ItxAuthCredentials;
27
+ };
28
+ type ConnectProjectItxInput = ConnectItxAuthenticatedInput & {
29
+ projectId: string;
30
+ };
31
+ type ConnectAgentItxInput = ConnectItxAuthenticatedInput & {
32
+ agentPath: string;
33
+ projectId: string;
34
+ };
35
+ export type ItxInitialConnectionRetry = {
36
+ attemptDurationMs: number;
37
+ delayMs: number;
38
+ error: Error;
39
+ failedAttempt: 1;
40
+ nextAttempt: 2;
41
+ startedAt: string;
42
+ };
43
+ export type ConnectItxReadyOptions = {
44
+ /**
45
+ * Permit exactly one fresh dial while establishing the initial WebSocket.
46
+ *
47
+ * The retry boundary ends before the RPC session exists, so it can never
48
+ * replay authentication or a caller operation.
49
+ */
50
+ retryInitialConnection?: {
51
+ /** Delay before the one retry. Defaults to 250ms; maximum 5s. */
52
+ delayMs?: number;
53
+ /** Observe the failed first dial before the retry begins. */
54
+ onRetry?: (retry: ItxInitialConnectionRetry) => Promise<void> | void;
55
+ };
56
+ };
57
+ export declare function connectItx(input: ConnectAgentItxInput): CapnRpcStub<Agent>;
58
+ export declare function connectItx(input: ConnectProjectItxInput): CapnRpcStub<Project>;
59
+ export declare function connectItx(input: ConnectItxAuthenticatedInput): CapnRpcStub<Session>;
60
+ export declare function connectItx(input: ConnectItxBaseInput): CapnRpcStub<UnauthenticatedOs>;
61
+ export declare function connectItxReady(input: ConnectAgentItxInput, options?: ConnectItxReadyOptions): Promise<CapnRpcStub<Agent>>;
62
+ export declare function connectItxReady(input: ConnectProjectItxInput, options?: ConnectItxReadyOptions): Promise<CapnRpcStub<Project>>;
63
+ export declare function connectItxReady(input: ConnectItxAuthenticatedInput, options?: ConnectItxReadyOptions): Promise<CapnRpcStub<Session>>;
64
+ export declare function connectItxReady(input: ConnectItxBaseInput, options?: ConnectItxReadyOptions): Promise<CapnRpcStub<UnauthenticatedOs>>;
65
+ export {};
@@ -0,0 +1,215 @@
1
+ /**
2
+ * itx-session — the framework-free half of the itx client: ONE WebSocket per
3
+ * process (browser tab, TUI, phone), connected to an OS deployment's `/api`,
4
+ * `authenticate()`d into a **Session** (the catalog that vends project itxs via
5
+ * `session.projects.get(slug)`), and kept alive through transport gaps.
6
+ *
7
+ * React never appears in this module. The hooks in ../sdk/itx/react.ts are a thin
8
+ * binding over the exact surface exported here (`subscribeSession` +
9
+ * `currentSnapshot` feed `useSyncExternalStore`; everything else is shared
10
+ * verbatim), so a non-React consumer — a node script, a future runtime —
11
+ * gets the same one-socket semantics by importing `iterate/client`.
12
+ *
13
+ * WHERE THE CONNECTION TARGET COMES FROM
14
+ * • In a browser, nothing to configure: the keeper connects
15
+ * `window.location`'s `/api` and authenticates with the session cookie
16
+ * riding the WebSocket handshake.
17
+ * • Anywhere else (the chat TUI, tests, scripts that want the KEEPER rather
18
+ * than the one-shot node dial), call {@link configureIterateSession} with a
19
+ * base URL and credentials. Calling it again for the same deployment
20
+ * refreshes the credential source without disturbing the socket; changing
21
+ * deployments deliberately replaces the socket. The runtime's global
22
+ * WebSocket carries the dial — node ≥ 22, bun, and React Native all satisfy
23
+ * capnweb's WebSocket needs.
24
+ *
25
+ * ───────────────────────────────────────────────────────────────────────────
26
+ * THE SESSION MODEL — one socket, generations, invisible reconnect
27
+ * ───────────────────────────────────────────────────────────────────────────
28
+ *
29
+ * • ONE WebSocket for the whole process, kept in module state (so in a
30
+ * browser it persists across client-side navigation). One connection attempt is a
31
+ * GENERATION ({@link Generation}): its WebSocket and its connecting
32
+ * promise. The session is the AWAITED `authenticate()` result — one settled
33
+ * stub identity shared by the snapshot, imperative awaiters, and the
34
+ * project-stub cache (resolving with the raw pipelined RpcPromise would
35
+ * fork identities: native promises assimilate thenables). The connection timeout
36
+ * spans the whole handshake (TCP/TLS/upgrade AND authenticate), and a REAL
37
+ * auth rejection over a working socket is terminal — it surfaces from the
38
+ * connecting promise instead of looping.
39
+ *
40
+ * • RECONNECT IS INVISIBLE. Readers see an immutable {@link Snapshot};
41
+ * `snapshot.session` holds the LAST live session and is kept across a
42
+ * transport gap. Before the FIRST session, awaiters share the STABLE
43
+ * {@link firstConnect} promise, which survives failed connection attempts (paced
44
+ * reconnects happen behind it; an individual attempt's rejection never reaches a suspended
45
+ * React tree) and rejects only on a TERMINAL failure. Kept-across-the-gap
46
+ * applies to TRANSPORT gaps only: a terminal auth rejection on a reconnect is
47
+ * an AUTHORITY loss — the halted snapshot drops the (already dead) session
48
+ * so the real error surfaces instead of zombie stubs.
49
+ *
50
+ * • PROJECT STUBS ARE SESSION-OWNED. `session.projects.get` allocates a
51
+ * capnweb import-table entry, so deriving stubs ad hoc (or inside React
52
+ * renders that may be discarded) would leak them. The module
53
+ * {@link projectStubCaches} WeakMap keyed by the session stub caches one
54
+ * real stub per (session, slug); a retired generation disposes them. The
55
+ * stub stays the REAL capnweb stub (a lazy wrapper that awaited the session
56
+ * per call would break pipelining — capnweb fork v0.8.0), and its identity
57
+ * changes exactly once per successful reconnect.
58
+ *
59
+ * • TRANSPORT HEALTH IS SOCKET-OWNED and GENERATION-GUARDED. A half-open
60
+ * socket (laptop sleep, network switch — no `close` event) is detected by
61
+ * one verifier per generation ({@link verifyTransport}: periodic +
62
+ * visibility/online probes, two-strike). Consumers REPORT suspicion
63
+ * ({@link reportTransportSuspicion}); they never close the shared socket
64
+ * themselves. Only two failed probes against the SAME generation retire it,
65
+ * and {@link reconnectIfCurrent} is a compare-and-swap on generation OBJECT
66
+ * IDENTITY — a late verdict against a superseded generation can never close
67
+ * its healthy successor. {@link reconnectIterateSession} is the separate,
68
+ * deliberate *semantic* reset (new claims after create/unlock).
69
+ */
70
+ import { type RpcStub } from "@iterate-com/capnweb";
71
+ import type { ItxAuthCredentials, Project, Session } from "../itx-api.generated.ts";
72
+ /** The Session catalog (what `authenticate()` returns): vends project itxs. */
73
+ export type SessionStub = RpcStub<Session>;
74
+ /** A project capability handle — `session.projects.get(slug)`. */
75
+ export type ProjectStub = RpcStub<Project>;
76
+ export type { ProjectStub as Itx };
77
+ export type IterateSessionConfig = {
78
+ /** OS deployment base URL, e.g. `https://os.iterate.com`; `/api` is appended. */
79
+ baseUrl: string;
80
+ /**
81
+ * How `authenticate()` identifies the caller. A provider is resolved for
82
+ * every dial, so rotating credentials stay fresh across transport reconnects.
83
+ * If authentication rejects with an auth-shaped error, providers get one
84
+ * forced-refresh attempt before the failure becomes terminal. Default: the
85
+ * browser session cookie.
86
+ */
87
+ credentials?: ItxAuthCredentials | ((options: {
88
+ forceRefresh: boolean;
89
+ }) => ItxAuthCredentials | Promise<ItxAuthCredentials>);
90
+ };
91
+ /**
92
+ * Point the keeper at a deployment explicitly — the non-browser entry into the
93
+ * one-socket model (the chat TUI, React Native, keeper-based scripts). Repeating
94
+ * the same target updates its credential source without disturbing the live
95
+ * socket. A different target retires the old deployment immediately and connects
96
+ * the new one, so authority can never cross deployments. In a browser this is
97
+ * optional (the default is `window.location`'s `/api` with cookie auth).
98
+ */
99
+ export declare function configureIterateSession(config: IterateSessionConfig): void;
100
+ /**
101
+ * The immutable value readers see (React reads it via `useSyncExternalStore` +
102
+ * `use()`). Replaced wholesale on every transition, always BEFORE listeners are
103
+ * notified, so a concurrent render can never tear. `session` is the last live
104
+ * session and survives transport gaps — that is what makes reconnect
105
+ * invisible. `generation` is a monotonic number whose only job is being the
106
+ * reconnect dep for the React layer's reconnect-aware effect (the CAS is
107
+ * generation object identity, not this number). `connecting` is what
108
+ * first-load callers await/suspend on: before the FIRST session it is the
109
+ * stable {@link firstConnect} promise (survives closed-before-open retries
110
+ * without rejecting a suspended tree); afterwards `session` is always defined.
111
+ */
112
+ type Snapshot = {
113
+ generation: number;
114
+ session: SessionStub | undefined;
115
+ connecting: Promise<SessionStub>;
116
+ };
117
+ export declare const subscribeSession: (onChange: () => void) => () => undefined;
118
+ export declare function projectStubFor(session: SessionStub, slug: string): ProjectStub;
119
+ /** getSnapshot for useSyncExternalStore: stable between transitions; connects when idle. */
120
+ export declare function currentSnapshot(): Snapshot;
121
+ export declare const serverSnapshot: () => never;
122
+ /**
123
+ * Ensure a live-or-connecting session and return its connecting promise. The
124
+ * imperative sibling of the React `useIterateSession()`: for handlers,
125
+ * `mutationFn`s, scripts, and lazy closures. Same one socket the hooks use.
126
+ * After a TERMINAL auth rejection this keeps returning that failure (the
127
+ * halted generation — see Generation.failed) until {@link reconnectIterateSession}.
128
+ */
129
+ export declare function connectIterateSession(): Promise<SessionStub>;
130
+ /**
131
+ * The project itx for a slug (or `prj_…` id), imperatively. Pipelines through
132
+ * the returned promise — `(await connectItx(slug)).streams.get(path)`. Returns
133
+ * the session-owned cached stub, re-derived automatically after a reconnect.
134
+ */
135
+ export declare function connectItx(slug: string): Promise<ProjectStub>;
136
+ /**
137
+ * Report that the shared transport may be half-open (a call hung). Consumers —
138
+ * the subscription watchdog, any long-lived reader — call this instead of
139
+ * closing the socket themselves — {@link verifyTransport} probes and, only on
140
+ * two strikes against the SAME generation, retires it. A stale report is a
141
+ * no-op.
142
+ */
143
+ export declare function reportTransportSuspicion(): void;
144
+ /**
145
+ * The SEMANTIC reset (not a transport reconnect): drop the live socket and connection attempt
146
+ * a fresh one so the next reads run under the caller's CURRENT claims. Call
147
+ * after creating a project or unlocking admin — the live socket carries the
148
+ * connect-time principal. React callers that need already-cached data refreshed
149
+ * should also `invalidateQueries({ queryKey: ["itx"] })`.
150
+ */
151
+ export declare function reconnectIterateSession(): void;
152
+ /**
153
+ * Revive only a generation parked by a terminal authentication failure. This
154
+ * is the imperative retry boundary for query/mutation clients: calling it
155
+ * before a repeated operation lets refreshed credentials dial again without
156
+ * disturbing a healthy or merely reconnecting session.
157
+ */
158
+ export declare function retryFailedIterateSession(): void;
159
+ /**
160
+ * Release the current transport and authority without reconnecting. Intended
161
+ * for process-local lifecycle boundaries such as signing out of a native app;
162
+ * mounted consumers should be removed in the same transition. The explicit
163
+ * deployment configuration remains, so a later connect can dial it again.
164
+ */
165
+ export declare function disconnectIterateSession(): void;
166
+ /**
167
+ * The four — and only four — transport-close rejections a caller may treat as
168
+ * "the socket died, retry on a fresh one": our own connection-closed rejection, capnweb's
169
+ * two WebSocket aborts (`Peer closed WebSocket: <code> <reason>` and
170
+ * `WebSocket connection failed.`), and capnweb's exact local-shutdown error
171
+ * from our forced bootstrap disposal. Deliberately NARROW: an
172
+ * application/auth/validation error that merely mentions "WebSocket" must never
173
+ * be mistaken for a transport failure and retried. This is the one discriminant
174
+ * shared by the query retry, the subscribe retry, and the liveness verifier.
175
+ */
176
+ export declare function isItxTransportError(error: unknown): boolean;
177
+ /**
178
+ * The common lifecycle used by reconnecting stream and live-state clients.
179
+ * Concrete APIs adapt their own handle (`StreamConnectionHandle.close()` or
180
+ * `LiveStateSubscriptionHandle.unsubscribe()`) to `close()`. Real handles are
181
+ * capnweb stubs and therefore Disposable: `close()` closes the server-side
182
+ * resource, while `[Symbol.dispose]` releases the caller-owned stub —
183
+ * on the process-long shared socket, skipping the dispose leaks one
184
+ * import-table entry per subscribe cycle, so holders always do both.
185
+ */
186
+ export type ItxRecoverableConnectionHandle = {
187
+ ping(): boolean | Promise<boolean>;
188
+ close(): unknown;
189
+ [Symbol.dispose]?(): void;
190
+ };
191
+ /**
192
+ * Release a push connection completely: `close()` closes the server side
193
+ * (already-dead is fine — the rejection is swallowed), then
194
+ * `[Symbol.dispose]` frees the caller-owned stub. One helper because
195
+ * forgetting either half leaks (see {@link ItxRecoverableConnectionHandle}).
196
+ */
197
+ export declare function releaseItxConnection(handle: ItxRecoverableConnectionHandle): void;
198
+ /**
199
+ * Poll a push connection's `ping()` until it stops answering `true`, then
200
+ * recover. Server pushes fail SILENTLY: a dead Durable Object or half-open TCP
201
+ * stops delivering with no client-visible signal, so a consumer can show
202
+ * "live" forever while stale. The watchdog checks on an interval and — because
203
+ * those are exactly when sockets die — when a browser tab becomes visible or
204
+ * comes online (in runtimes without those signals, the interval carries it).
205
+ *
206
+ * `dead` → ping answered `false` or REJECTED: the socket works but the
207
+ * server-side connection is gone (DO restart / dropped
208
+ * callback). Recovery opens it again on the same socket.
209
+ * `timed-out` → ping never answered: the shared WebSocket is half-open. The
210
+ * watchdog REPORTS the suspicion to the socket-owned verifier
211
+ * ({@link reportTransportSuspicion}) — it never closes the socket
212
+ * itself — and the holder opens again once the generation
213
+ * reconnects.
214
+ */
215
+ export declare function watchItxConnection(ping: () => boolean | Promise<boolean>, onDead: () => void): () => void;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Groups an authenticated child stub with the parent stubs that keep it alive.
3
+ *
4
+ * Cap'n Web callers often want `using project = connectItx({ projectId })`, but
5
+ * that project stub is reached through a root session and authentication stub.
6
+ * This proxy makes disposal and `dup()` preserve the whole ownership chain, so
7
+ * disposing the child also tells the server it can release the parent stubs.
8
+ */
9
+ type DisposableLike = {
10
+ [Symbol.dispose]?(): void;
11
+ dup?(): DisposableLike;
12
+ };
13
+ export declare function withOwnedRpcSession<T extends object>(stub: T, ...owned: DisposableLike[]): T;
14
+ export {};
@@ -0,0 +1,10 @@
1
+ import { QueryClient } from "@tanstack/react-query";
2
+ /**
3
+ * One Query client policy for every Iterate React renderer.
4
+ *
5
+ * The browser dashboard and the OpenTUI client intentionally share these
6
+ * defaults. Renderer-specific entrypoints own only where the provider mounts;
7
+ * cache lifetime, refetch behavior, and mutation retries must not drift by
8
+ * surface.
9
+ */
10
+ export declare function createIterateQueryClient(): QueryClient;