@swirls/looms 0.0.2 → 0.2.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 (375) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/dist/actor/cell.d.ts +9 -4
  3. package/dist/actor/cell.js +7 -11
  4. package/dist/actor/host.d.ts +7 -3
  5. package/dist/actor/host.js +57 -33
  6. package/dist/actor/index.d.ts +1 -1
  7. package/dist/actor/index.js +1 -1
  8. package/dist/actor/routing.d.ts +12 -1
  9. package/dist/actor/routing.js +57 -51
  10. package/dist/agent/agent-session.d.ts +5 -0
  11. package/dist/agent/agent-session.js +270 -0
  12. package/dist/agent/debugger/agent-chat.d.ts +2 -0
  13. package/dist/agent/debugger/agent-chat.js +176 -0
  14. package/dist/agent/debugger/index.d.ts +5 -0
  15. package/dist/agent/debugger/index.js +19 -0
  16. package/dist/agent/debugger/summarize.d.ts +2 -0
  17. package/dist/agent/debugger/summarize.js +51 -0
  18. package/dist/agent/debugger/token-usage.d.ts +3 -0
  19. package/dist/agent/debugger/token-usage.js +11 -0
  20. package/dist/agent/definitions-store.d.ts +2 -3
  21. package/dist/agent/definitions-store.js +2 -4
  22. package/dist/agent/definitions.d.ts +47 -29
  23. package/dist/agent/definitions.js +39 -12
  24. package/dist/agent/effects.d.ts +359 -2
  25. package/dist/agent/effects.js +60 -34
  26. package/dist/agent/events.d.ts +175 -19
  27. package/dist/agent/events.js +45 -10
  28. package/dist/agent/index.d.ts +1 -0
  29. package/dist/agent/index.js +1 -0
  30. package/dist/agent/llm.d.ts +11 -2
  31. package/dist/agent/llm.js +13 -3
  32. package/dist/agent/module.d.ts +812 -78
  33. package/dist/agent/module.js +16 -4
  34. package/dist/agent/scope.d.ts +174 -18
  35. package/dist/agent/signals.d.ts +26 -1
  36. package/dist/agent/signals.js +79 -0
  37. package/dist/agent/threads.d.ts +3 -33
  38. package/dist/agent/threads.js +20 -25
  39. package/dist/agent/tool-schema.d.ts +1 -17
  40. package/dist/agent/tool-schema.js +3 -43
  41. package/dist/agent/types.d.ts +49 -4
  42. package/dist/agent/types.js +5 -5
  43. package/dist/ai-vercel/index.d.ts +1 -0
  44. package/dist/ai-vercel/index.js +1 -0
  45. package/dist/ai-vercel/messages.js +22 -7
  46. package/dist/ai-vercel/vercel-evaluator.d.ts +36 -0
  47. package/dist/ai-vercel/vercel-evaluator.js +73 -0
  48. package/dist/ai-vercel/vercel-llm.js +64 -44
  49. package/dist/approval/debugger/approvals-view.d.ts +3 -0
  50. package/dist/approval/debugger/approvals-view.js +16 -0
  51. package/dist/approval/debugger/index.d.ts +4 -0
  52. package/dist/approval/debugger/index.js +11 -0
  53. package/dist/approval/debugger/summarize.d.ts +2 -0
  54. package/dist/approval/debugger/summarize.js +17 -0
  55. package/dist/approval/effects.d.ts +89 -0
  56. package/dist/approval/module.d.ts +179 -29
  57. package/dist/approval/projections.d.ts +0 -14
  58. package/dist/approval/projections.js +20 -3
  59. package/dist/approval/signals.d.ts +2 -0
  60. package/dist/approval/signals.js +18 -2
  61. package/dist/cli/cli.js +2 -0
  62. package/dist/cli/commands.js +12 -16
  63. package/dist/client/client.d.ts +1213 -18
  64. package/dist/client/client.js +120 -120
  65. package/dist/client/index.d.ts +1 -1
  66. package/dist/client/index.js +1 -1
  67. package/dist/client/sse.d.ts +13 -0
  68. package/dist/client/sse.js +38 -44
  69. package/dist/cloudflare/alarm-scheduler.d.ts +12 -2
  70. package/dist/cloudflare/alarm-scheduler.js +34 -7
  71. package/dist/cloudflare/durable-object.d.ts +10 -2
  72. package/dist/cloudflare/durable-object.js +59 -28
  73. package/dist/cloudflare/event-store.d.ts +7 -2
  74. package/dist/cloudflare/event-store.js +8 -7
  75. package/dist/cloudflare/index.d.ts +2 -2
  76. package/dist/cloudflare/index.js +2 -2
  77. package/dist/cloudflare/router.d.ts +1 -1
  78. package/dist/cloudflare/router.js +15 -16
  79. package/dist/core/catalog.d.ts +3 -3
  80. package/dist/core/catalog.js +2 -5
  81. package/dist/core/coalesce.js +5 -3
  82. package/dist/core/compose.d.ts +2 -1
  83. package/dist/core/compose.js +19 -2
  84. package/dist/core/debugger-host.d.ts +16 -0
  85. package/dist/core/debugger-host.js +1 -0
  86. package/dist/core/definition-store.d.ts +11 -0
  87. package/dist/core/definition-store.js +6 -0
  88. package/dist/core/effects.d.ts +127 -15
  89. package/dist/core/effects.js +89 -17
  90. package/dist/core/envelope.d.ts +27 -7
  91. package/dist/core/envelope.js +20 -6
  92. package/dist/core/event-codec.d.ts +4 -2
  93. package/dist/core/event-codec.js +10 -5
  94. package/dist/core/event-index.js +1 -3
  95. package/dist/core/fold.d.ts +3 -0
  96. package/dist/core/fold.js +171 -24
  97. package/dist/core/ids.d.ts +5 -0
  98. package/dist/core/ids.js +35 -2
  99. package/dist/core/index.d.ts +7 -1
  100. package/dist/core/index.js +6 -1
  101. package/dist/core/kind.d.ts +22 -0
  102. package/dist/core/kind.js +17 -0
  103. package/dist/core/limits.d.ts +34 -0
  104. package/dist/core/limits.js +46 -0
  105. package/dist/core/match.d.ts +1 -0
  106. package/dist/core/match.js +10 -1
  107. package/dist/core/module-scope.d.ts +22 -20
  108. package/dist/core/module-scope.js +38 -28
  109. package/dist/core/module.d.ts +18 -5
  110. package/dist/core/module.js +4 -0
  111. package/dist/core/projection.d.ts +13 -13
  112. package/dist/core/projection.js +9 -5
  113. package/dist/core/protocol.d.ts +68 -11
  114. package/dist/core/protocol.js +82 -11
  115. package/dist/core/published.d.ts +43 -0
  116. package/dist/core/published.js +44 -0
  117. package/dist/core/replay.d.ts +312 -3
  118. package/dist/core/replay.js +11 -1
  119. package/dist/core/schema.d.ts +11 -20
  120. package/dist/core/schema.js +63 -80
  121. package/dist/core/serialize.js +2 -4
  122. package/dist/core/snapshot-store.d.ts +6 -1
  123. package/dist/core/snapshot-store.js +15 -2
  124. package/dist/core/snapshots.d.ts +5 -1
  125. package/dist/core/snapshots.js +10 -15
  126. package/dist/core/sqlite-store.js +95 -11
  127. package/dist/core/state.d.ts +142 -4
  128. package/dist/core/state.js +78 -0
  129. package/dist/core/store.d.ts +38 -3
  130. package/dist/core/store.js +77 -22
  131. package/dist/core/thread.d.ts +10 -9
  132. package/dist/core/types.d.ts +5 -8
  133. package/dist/core/types.js +28 -24
  134. package/dist/core/validate-event.js +14 -4
  135. package/dist/core/worker-callback.d.ts +17 -0
  136. package/dist/core/worker-callback.js +1 -0
  137. package/dist/debugger/app/assets/index-B3Ah8WAE.css +2 -0
  138. package/dist/debugger/app/assets/index-Qb371t_O.js +22 -0
  139. package/dist/debugger/app/assets/rolldown-runtime-CbXtAM7H.js +1 -0
  140. package/dist/debugger/app/assets/routes-CjtEHgPQ.js +47 -0
  141. package/dist/debugger/app/index.html +14 -0
  142. package/dist/debugger/components/composer.d.ts +17 -0
  143. package/dist/debugger/components/composer.js +25 -0
  144. package/dist/debugger/components/debugger-split.d.ts +2 -1
  145. package/dist/debugger/components/debugger-split.js +2 -2
  146. package/dist/debugger/components/event-inspector.d.ts +2 -1
  147. package/dist/debugger/components/event-inspector.js +25 -16
  148. package/dist/debugger/components/event-stream.d.ts +2 -1
  149. package/dist/debugger/components/event-stream.js +14 -74
  150. package/dist/debugger/components/follow-list.d.ts +19 -0
  151. package/dist/debugger/components/follow-list.js +96 -0
  152. package/dist/debugger/components/json-tree.d.ts +11 -0
  153. package/dist/debugger/components/json-tree.js +120 -0
  154. package/dist/debugger/components/json-view.js +2 -2
  155. package/dist/debugger/components/run-tree.js +53 -20
  156. package/dist/debugger/components/status-dot.d.ts +4 -0
  157. package/dist/debugger/components/status-dot.js +6 -2
  158. package/dist/debugger/context.d.ts +14 -0
  159. package/dist/debugger/context.js +17 -0
  160. package/dist/debugger/contracts.d.ts +7 -4
  161. package/dist/debugger/events/builtin-families.d.ts +3 -0
  162. package/dist/debugger/events/builtin-families.js +9 -0
  163. package/dist/debugger/events/catalog.d.ts +3 -4
  164. package/dist/debugger/events/catalog.js +27 -17
  165. package/dist/debugger/events/protocol-summarize.d.ts +1 -2
  166. package/dist/debugger/events/protocol-summarize.js +42 -24
  167. package/dist/debugger/index.d.ts +20 -6
  168. package/dist/debugger/index.js +19 -5
  169. package/dist/debugger/lib/cn.d.ts +1 -0
  170. package/dist/debugger/lib/cn.js +3 -0
  171. package/dist/debugger/lib/event-meta.d.ts +2 -3
  172. package/dist/debugger/lib/event-meta.js +6 -12
  173. package/dist/debugger/lib/json.d.ts +25 -0
  174. package/dist/debugger/lib/json.js +87 -0
  175. package/dist/debugger/lib/payload.d.ts +7 -0
  176. package/dist/debugger/lib/payload.js +11 -0
  177. package/dist/debugger/lib/time.d.ts +3 -0
  178. package/dist/debugger/lib/time.js +24 -0
  179. package/dist/debugger/plugin.d.ts +36 -0
  180. package/dist/debugger/plugin.js +14 -0
  181. package/dist/debugger/server/index.d.ts +14 -0
  182. package/dist/debugger/server/index.js +114 -0
  183. package/dist/debugger/shell/debugger-shell.d.ts +15 -0
  184. package/dist/debugger/shell/debugger-shell.js +43 -0
  185. package/dist/debugger/shell/definition-key.d.ts +3 -0
  186. package/dist/debugger/shell/definition-key.js +4 -0
  187. package/dist/debugger/shell/events-pane.d.ts +12 -0
  188. package/dist/debugger/shell/events-pane.js +25 -0
  189. package/dist/debugger/shell/projections-panel.d.ts +4 -0
  190. package/dist/debugger/shell/projections-panel.js +59 -0
  191. package/dist/debugger/shell/run-controls.d.ts +4 -0
  192. package/dist/debugger/shell/run-controls.js +72 -0
  193. package/dist/debugger/shell/run-search.d.ts +6 -0
  194. package/dist/debugger/shell/run-search.js +102 -0
  195. package/dist/debugger/shell/run-type-menu.d.ts +6 -0
  196. package/dist/debugger/shell/run-type-menu.js +11 -0
  197. package/dist/debugger/shell/schema-form.d.ts +31 -0
  198. package/dist/debugger/shell/schema-form.js +117 -0
  199. package/dist/debugger/shell/start-form.d.ts +3 -0
  200. package/dist/debugger/shell/start-form.js +75 -0
  201. package/dist/debugger/shell/use-start-run.d.ts +14 -0
  202. package/dist/debugger/shell/use-start-run.js +32 -0
  203. package/dist/debugger/shell/workspace.d.ts +6 -0
  204. package/dist/debugger/shell/workspace.js +15 -0
  205. package/dist/debugger/styles.css +111 -27
  206. package/dist/debugger/ui/button.d.ts +24 -0
  207. package/dist/debugger/ui/button.js +24 -0
  208. package/dist/debugger/ui/command.d.ts +9 -0
  209. package/dist/debugger/ui/command.js +24 -0
  210. package/dist/debugger/ui/dropdown-menu.d.ts +13 -0
  211. package/dist/debugger/ui/dropdown-menu.js +37 -0
  212. package/dist/debugger/ui/panel.d.ts +14 -0
  213. package/dist/debugger/ui/panel.js +16 -0
  214. package/dist/debugger/ui/popover.d.ts +7 -0
  215. package/dist/debugger/ui/popover.js +17 -0
  216. package/dist/debugger/ui/resizable.d.ts +7 -0
  217. package/dist/debugger/ui/resizable.js +14 -0
  218. package/dist/debugger/ui/scroll-area.d.ts +2 -1
  219. package/dist/debugger/ui/scroll-area.js +5 -3
  220. package/dist/debugger/ui/select.d.ts +10 -0
  221. package/dist/debugger/ui/select.js +21 -0
  222. package/dist/debugger/ui/tabs.js +2 -2
  223. package/dist/debugger/ui/textarea.d.ts +3 -0
  224. package/dist/debugger/ui/textarea.js +7 -0
  225. package/dist/evaluator/definitions-store.d.ts +11 -0
  226. package/dist/evaluator/definitions-store.js +8 -0
  227. package/dist/evaluator/definitions.d.ts +18 -0
  228. package/dist/evaluator/definitions.js +4 -0
  229. package/dist/evaluator/effects.d.ts +226 -0
  230. package/dist/evaluator/effects.js +132 -0
  231. package/dist/evaluator/evaluator.d.ts +24 -0
  232. package/dist/evaluator/evaluator.js +67 -0
  233. package/dist/evaluator/events.d.ts +59 -0
  234. package/dist/evaluator/events.js +21 -0
  235. package/dist/evaluator/index.d.ts +11 -0
  236. package/dist/evaluator/index.js +11 -0
  237. package/dist/evaluator/module.d.ts +606 -0
  238. package/dist/evaluator/module.js +25 -0
  239. package/dist/evaluator/projections.d.ts +50 -0
  240. package/dist/evaluator/projections.js +20 -0
  241. package/dist/evaluator/scope.d.ts +59 -0
  242. package/dist/evaluator/scope.js +7 -0
  243. package/dist/evaluator/signals.d.ts +3 -0
  244. package/dist/evaluator/signals.js +17 -0
  245. package/dist/evaluator/threads.d.ts +62 -0
  246. package/dist/evaluator/threads.js +117 -0
  247. package/dist/evaluator/types.d.ts +125 -0
  248. package/dist/evaluator/types.js +51 -0
  249. package/dist/projectors/delivery.d.ts +83 -0
  250. package/dist/projectors/delivery.js +182 -0
  251. package/dist/projectors/index-model.d.ts +1 -1
  252. package/dist/projectors/index-model.js +4 -6
  253. package/dist/projectors/index.d.ts +2 -0
  254. package/dist/projectors/index.js +2 -0
  255. package/dist/projectors/memory.d.ts +1 -1
  256. package/dist/projectors/memory.js +10 -8
  257. package/dist/projectors/postgres.js +44 -58
  258. package/dist/projectors/projector.d.ts +17 -0
  259. package/dist/projectors/projector.js +26 -1
  260. package/dist/projectors/sqlite-delivery.d.ts +4 -0
  261. package/dist/projectors/sqlite-delivery.js +147 -0
  262. package/dist/projectors/sqlite.js +23 -23
  263. package/dist/projectors/with-projectors.d.ts +7 -2
  264. package/dist/projectors/with-projectors.js +27 -28
  265. package/dist/react/notify.js +11 -8
  266. package/dist/react/react.d.ts +2 -3
  267. package/dist/react/react.js +14 -30
  268. package/dist/react/sse.d.ts +1 -1
  269. package/dist/react/sse.js +1 -1
  270. package/dist/react/store.d.ts +3 -3
  271. package/dist/react/store.js +109 -119
  272. package/dist/runtime/cancellation.d.ts +16 -0
  273. package/dist/runtime/cancellation.js +129 -0
  274. package/dist/runtime/deadline-transition-planner.d.ts +31 -0
  275. package/dist/runtime/deadline-transition-planner.js +139 -0
  276. package/dist/runtime/dispatch-effect.d.ts +3 -2
  277. package/dist/runtime/dispatch-effect.js +7 -13
  278. package/dist/runtime/errors.d.ts +51 -0
  279. package/dist/runtime/errors.js +39 -0
  280. package/dist/runtime/{runtime-helpers.d.ts → helpers.d.ts} +24 -4
  281. package/dist/runtime/{runtime-helpers.js → helpers.js} +56 -19
  282. package/dist/runtime/index.d.ts +3 -3
  283. package/dist/runtime/index.js +3 -3
  284. package/dist/runtime/ingress.d.ts +15 -0
  285. package/dist/runtime/ingress.js +42 -0
  286. package/dist/runtime/local-effect-executor.d.ts +21 -0
  287. package/dist/runtime/local-effect-executor.js +137 -0
  288. package/dist/runtime/looms.d.ts +30 -4
  289. package/dist/runtime/looms.js +57 -37
  290. package/dist/runtime/observer.d.ts +45 -0
  291. package/dist/runtime/observer.js +11 -0
  292. package/dist/runtime/repository.d.ts +55 -0
  293. package/dist/runtime/repository.js +293 -0
  294. package/dist/runtime/retry-policy.d.ts +16 -0
  295. package/dist/runtime/retry-policy.js +29 -0
  296. package/dist/runtime/run-execution-context.d.ts +30 -0
  297. package/dist/runtime/run-execution-context.js +56 -0
  298. package/dist/runtime/run-start.d.ts +4 -0
  299. package/dist/runtime/run-start.js +44 -0
  300. package/dist/runtime/runtime.d.ts +6 -87
  301. package/dist/runtime/runtime.js +248 -470
  302. package/dist/runtime/server/auth.d.ts +41 -0
  303. package/dist/runtime/server/auth.js +45 -0
  304. package/dist/runtime/server/debugger-host.d.ts +11 -0
  305. package/dist/runtime/server/debugger-host.js +32 -0
  306. package/dist/runtime/server/fetch-handler.d.ts +18 -0
  307. package/dist/runtime/server/fetch-handler.js +62 -0
  308. package/dist/runtime/server/http.d.ts +38 -0
  309. package/dist/runtime/server/http.js +66 -0
  310. package/dist/runtime/server/route-table.d.ts +55 -0
  311. package/dist/runtime/server/route-table.js +74 -0
  312. package/dist/runtime/server/routes/definitions.d.ts +3 -0
  313. package/dist/runtime/server/routes/definitions.js +20 -0
  314. package/dist/runtime/server/routes/effects.d.ts +3 -0
  315. package/dist/runtime/server/routes/effects.js +59 -0
  316. package/dist/runtime/server/routes/operations.d.ts +3 -0
  317. package/dist/runtime/server/routes/operations.js +14 -0
  318. package/dist/runtime/server/routes/runs.d.ts +3 -0
  319. package/dist/runtime/server/routes/runs.js +199 -0
  320. package/dist/runtime/server.d.ts +4 -8
  321. package/dist/runtime/server.js +6 -211
  322. package/dist/runtime/sse.d.ts +2 -1
  323. package/dist/runtime/sse.js +24 -17
  324. package/dist/runtime/types.d.ts +117 -0
  325. package/dist/runtime/types.js +1 -0
  326. package/dist/runtime/wake-engine.d.ts +20 -0
  327. package/dist/runtime/wake-engine.js +311 -0
  328. package/dist/runtime/wake-scheduler.d.ts +3 -4
  329. package/dist/runtime/wake-scheduler.js +25 -31
  330. package/dist/runtime/worker-callbacks.d.ts +16 -0
  331. package/dist/runtime/worker-callbacks.js +167 -0
  332. package/dist/runtime/worker-effect-dispatch.d.ts +36 -0
  333. package/dist/runtime/worker-effect-dispatch.js +126 -0
  334. package/dist/s2/config.d.ts +1 -1
  335. package/dist/s2/config.js +1 -1
  336. package/dist/s2/lite.js +70 -82
  337. package/dist/s2/pagination.d.ts +15 -0
  338. package/dist/s2/pagination.js +36 -0
  339. package/dist/s2/projector.js +6 -5
  340. package/dist/s2/snapshot-store.js +188 -161
  341. package/dist/s2/store.d.ts +3 -21
  342. package/dist/s2/store.js +71 -165
  343. package/dist/testing/index.d.ts +15 -0
  344. package/dist/testing/index.js +53 -16
  345. package/dist/workflow/debugger/index.d.ts +4 -0
  346. package/dist/workflow/debugger/index.js +11 -0
  347. package/dist/workflow/debugger/nodes-view.d.ts +3 -0
  348. package/dist/workflow/debugger/nodes-view.js +13 -0
  349. package/dist/workflow/debugger/summarize.d.ts +2 -0
  350. package/dist/workflow/debugger/summarize.js +30 -0
  351. package/dist/workflow/definitions-store.d.ts +2 -3
  352. package/dist/workflow/definitions-store.js +2 -2
  353. package/dist/workflow/definitions.d.ts +78 -22
  354. package/dist/workflow/definitions.js +80 -14
  355. package/dist/workflow/effects.d.ts +453 -107
  356. package/dist/workflow/effects.js +214 -115
  357. package/dist/workflow/events.d.ts +149 -5
  358. package/dist/workflow/events.js +27 -5
  359. package/dist/workflow/graph.d.ts +56 -0
  360. package/dist/workflow/graph.js +207 -0
  361. package/dist/workflow/index.d.ts +1 -0
  362. package/dist/workflow/index.js +1 -0
  363. package/dist/workflow/module.d.ts +1047 -266
  364. package/dist/workflow/projections.d.ts +14 -12
  365. package/dist/workflow/projections.js +30 -8
  366. package/dist/workflow/scope.d.ts +146 -4
  367. package/dist/workflow/threads.d.ts +383 -155
  368. package/dist/workflow/threads.js +308 -110
  369. package/package.json +26 -2
  370. package/dist/debugger/events/domain-summarize.d.ts +0 -3
  371. package/dist/debugger/events/domain-summarize.js +0 -107
  372. package/dist/debugger/events/family.d.ts +0 -8
  373. package/dist/debugger/events/family.js +0 -44
  374. package/dist/runtime/events-api.d.ts +0 -4
  375. package/dist/runtime/events-api.js +0 -84
@@ -1,19 +1,26 @@
1
1
  import { AppendInput, AppendRecord, S2, S2Endpoints, S2Error } from '@s2-dev/streamstore';
2
- import { Effect, Layer, Option, Schema } from 'effect';
3
- import { createKeyedSerializer, SnapshotStoreError, SnapshotStoreTag, } from '@swirls/looms/core';
2
+ import { Effect, Layer, Option, Schema, Stream } from 'effect';
3
+ import { createKeyedSerializer, assertSnapshotByteLimit, RunStateSchema, RunSummarySchema, SnapshotStoreError, SnapshotPayloadTooLargeError, SnapshotStoreTag, } from '@swirls/looms/core';
4
4
  import { S2ConfigSchema } from './config.js';
5
+ import { paginateS2Stream } from './pagination.js';
5
6
  const DEFAULT_PREFIX = 'snapshots';
6
7
  const DEFAULT_MAX_CHUNK_BYTES = 900 * 1024;
7
8
  const SnapshotFrameSchema = Schema.Struct({
8
9
  v: Schema.Literal(1),
9
10
  runId: Schema.String,
10
- cursor: Schema.Number,
11
+ cursor: Schema.Finite,
11
12
  stateHash: Schema.String,
12
- takenAt: Schema.Number,
13
- chunk: Schema.Number,
14
- chunks: Schema.Number,
13
+ takenAt: Schema.Finite,
14
+ chunk: Schema.Finite,
15
+ chunks: Schema.Finite,
15
16
  data: Schema.String,
16
17
  });
18
+ const SnapshotFrameJson = Schema.fromJsonString(SnapshotFrameSchema);
19
+ const decodeFrame = Schema.decodeSync(SnapshotFrameJson);
20
+ const encodeFrame = Schema.encodeSync(SnapshotFrameJson);
21
+ const HeaderJson = Schema.fromJsonString(RunSummarySchema);
22
+ const decodeHeader = Schema.decodeUnknownOption(HeaderJson);
23
+ const encodeHeader = Schema.encodeSync(HeaderJson);
17
24
  export function frameSnapshot(snapshot, maxChunkBytes = DEFAULT_MAX_CHUNK_BYTES) {
18
25
  const json = JSON.stringify(snapshot.state);
19
26
  const chunks = Math.max(1, Math.ceil(json.length / maxChunkBytes));
@@ -37,6 +44,8 @@ export function assembleSnapshot(frames) {
37
44
  throw new SnapshotStoreError('Cannot assemble snapshot from empty frames');
38
45
  }
39
46
  const first = frames[0];
47
+ // Oxlint's immutable alternative requires ES2023; sorting a fresh copy is safe on ES2022.
48
+ // oxlint-disable-next-line unicorn/no-array-sort
40
49
  const ordered = [...frames].sort((a, b) => a.chunk - b.chunk);
41
50
  for (const frame of ordered) {
42
51
  if (frame.cursor !== first.cursor ||
@@ -54,8 +63,7 @@ export function assembleSnapshot(frames) {
54
63
  }
55
64
  }
56
65
  const json = ordered.map((frame) => frame.data).join('');
57
- // SAFETY: frames are produced by `frameSnapshot` from a serialized RunState.
58
- const state = JSON.parse(json);
66
+ const state = Schema.decodeSync(Schema.fromJsonString(RunStateSchema))(json);
59
67
  return {
60
68
  runId: first.runId,
61
69
  cursor: first.cursor,
@@ -84,74 +92,56 @@ function createClient(config) {
84
92
  }
85
93
  function parseFrame(body) {
86
94
  try {
87
- return Schema.decodeUnknownSync(SnapshotFrameSchema)(JSON.parse(body));
95
+ return decodeFrame(body);
88
96
  }
89
97
  catch {
90
98
  return undefined;
91
99
  }
92
100
  }
101
+ function toSnapshotError(cause, message) {
102
+ return cause instanceof SnapshotStoreError
103
+ ? cause
104
+ : new SnapshotStoreError(`${message}: ${cause instanceof Error ? cause.message : String(cause)}`, cause);
105
+ }
93
106
  /**
94
107
  * Trailing command records (the trim written by `prune`) are skipped by
95
108
  * `ignoreCommandRecords`, so a `tailOffset: 1` read can come back empty.
96
109
  * Probe a few records from the tail instead.
97
110
  */
98
111
  const TAIL_PROBE = 4;
99
- async function readFromTail(stream, tailOffset) {
100
- try {
101
- const page = await stream.read({
112
+ function readFromTail(stream, tailOffset) {
113
+ return Effect.tryPromise({
114
+ try: () => stream.read({
102
115
  start: { from: { tailOffset } },
103
116
  ignoreCommandRecords: true,
104
- });
105
- return [...page.records];
106
- }
107
- catch (cause) {
108
- if (cause instanceof S2Error && (cause.status === 404 || cause.status === 416)) {
109
- return null;
110
- }
111
- throw cause;
112
- }
117
+ }),
118
+ catch: (cause) => toSnapshotError(cause, 'Failed to read snapshot stream tail'),
119
+ }).pipe(Effect.map((page) => [...page.records]), Effect.catchIf((error) => error.cause instanceof S2Error &&
120
+ (error.cause.status === 404 || error.cause.status === 416), () => Effect.succeed([])));
113
121
  }
114
122
  /** Latest complete data frame near the tail, with its record, or undefined. */
115
- async function readLatestFrame(stream) {
116
- const records = (await readFromTail(stream, TAIL_PROBE)) ?? [];
117
- for (let i = records.length - 1; i >= 0; i--) {
118
- const record = records[i];
119
- const frame = parseFrame(record.body);
120
- if (frame) {
121
- return { record, frame };
123
+ function readLatestFrame(stream) {
124
+ return readFromTail(stream, TAIL_PROBE).pipe(Effect.map((records) => {
125
+ for (let i = records.length - 1; i >= 0; i--) {
126
+ const record = records[i];
127
+ const frame = parseFrame(record.body);
128
+ if (frame) {
129
+ return { record, frame };
130
+ }
122
131
  }
123
- }
124
- return undefined;
132
+ return undefined;
133
+ }));
125
134
  }
126
- async function readAllSnapshotRecords(stream) {
127
- const records = [];
128
- let cursor = 0;
129
- while (true) {
130
- const page = await stream
131
- .read({
135
+ function readAllSnapshotRecords(stream) {
136
+ return paginateS2Stream((cursor, count) => Effect.tryPromise({
137
+ try: () => stream.read({
132
138
  start: { from: { seqNum: cursor }, clamp: true },
133
- stop: { limits: { count: 1000 } },
139
+ stop: { limits: { count } },
134
140
  ignoreCommandRecords: true,
135
- })
136
- .catch((cause) => {
137
- // 404: no stream yet. 416: nothing at or past `cursor` (e.g. only a trim record).
138
- if (cause instanceof S2Error && (cause.status === 404 || cause.status === 416)) {
139
- return null;
140
- }
141
- throw cause;
142
- });
143
- if (!page || page.records.length === 0) {
144
- break;
145
- }
146
- records.push(...page.records);
147
- const last = page.records[page.records.length - 1];
148
- const next = last.seqNum + 1;
149
- if (page.tail?.seqNum !== undefined && next >= page.tail.seqNum) {
150
- break;
151
- }
152
- cursor = next;
153
- }
154
- return records;
141
+ }),
142
+ catch: (cause) => toSnapshotError(cause, 'Failed to scan snapshot stream'),
143
+ }).pipe(Effect.catchIf((error) => error.cause instanceof S2Error &&
144
+ (error.cause.status === 404 || error.cause.status === 416), () => Effect.succeed(null)))).pipe(Stream.runCollect);
155
145
  }
156
146
  function groupCompleteSnapshots(records) {
157
147
  const byCursor = new Map();
@@ -173,144 +163,181 @@ function groupCompleteSnapshots(records) {
173
163
  continue;
174
164
  }
175
165
  }
166
+ // `assembled` is function-local, so in-place sorting cannot mutate caller-owned data.
167
+ // oxlint-disable-next-line unicorn/no-array-sort
176
168
  return assembled.sort((a, b) => a.cursor - b.cursor);
177
169
  }
178
170
  export function s2SnapshotStore(config) {
179
- const parsed = Schema.decodeUnknownSync(S2ConfigSchema)(config);
171
+ const parsed = Schema.decodeSync(S2ConfigSchema)(config);
180
172
  const client = createClient(parsed);
181
173
  const basin = client.basin(parsed.basin);
182
174
  const prefix = parsed.snapshotPrefix ?? DEFAULT_PREFIX;
183
175
  const appends = createKeyedSerializer();
184
176
  const ensured = new Set();
177
+ const headers = new Map();
185
178
  let basinEnsured = false;
179
+ let headersLoaded = false;
186
180
  const streamName = (runId) => `${prefix}/${runId}`;
187
- const ensureBasin = async () => {
181
+ const ensureBasin = Effect.suspend(() => {
188
182
  if (basinEnsured) {
189
- return;
183
+ return Effect.void;
190
184
  }
191
- try {
192
- await client.basins.create({ basin: parsed.basin });
193
- }
194
- catch (err) {
195
- if (!(err instanceof S2Error && err.status === 409)) {
196
- throw err;
197
- }
198
- }
199
- basinEnsured = true;
200
- };
201
- const ensureStream = async (runId) => {
202
- await ensureBasin();
185
+ return Effect.tryPromise({
186
+ try: () => client.basins.create({ basin: parsed.basin }),
187
+ catch: (cause) => toSnapshotError(cause, `Failed to ensure basin ${parsed.basin}`),
188
+ }).pipe(Effect.catchIf((error) => error.cause instanceof S2Error && error.cause.status === 409, () => Effect.void), Effect.tap(() => Effect.sync(() => {
189
+ basinEnsured = true;
190
+ })), Effect.asVoid);
191
+ });
192
+ const ensureStream = (runId) => Effect.gen(function* () {
193
+ yield* ensureBasin;
203
194
  const name = streamName(runId);
204
195
  if (!ensured.has(name)) {
205
- try {
206
- await basin.streams.create({
196
+ yield* Effect.tryPromise({
197
+ try: () => basin.streams.create({
207
198
  stream: name,
208
199
  config: { retentionPolicy: { infinite: {} } },
209
- });
210
- }
211
- catch (err) {
212
- if (!(err instanceof S2Error &&
213
- (err.status === 409 || err.status === 404 || err.status === 405))) {
214
- throw err;
215
- }
216
- }
200
+ }),
201
+ catch: (cause) => toSnapshotError(cause, `Failed to ensure stream ${name}`),
202
+ }).pipe(Effect.catchIf((error) => error.cause instanceof S2Error &&
203
+ (error.cause.status === 409 ||
204
+ error.cause.status === 404 ||
205
+ error.cause.status === 405), () => Effect.void));
217
206
  ensured.add(name);
218
207
  }
219
208
  return basin.stream(name);
220
- };
221
- const toError = (cause, message) => cause instanceof SnapshotStoreError
222
- ? cause
223
- : new SnapshotStoreError(`${message}: ${cause instanceof Error ? cause.message : String(cause)}`, cause);
209
+ });
224
210
  const service = {
225
- save: (snapshot) => Effect.tryPromise({
226
- try: async () => {
227
- const stream = await ensureStream(snapshot.runId);
228
- const frames = frameSnapshot(snapshot);
229
- const records = frames.map((frame) => AppendRecord.string({
230
- body: JSON.stringify(frame),
231
- headers: [
232
- ['looms-cursor', String(frame.cursor)],
233
- ['looms-chunk', `${frame.chunk}/${frame.chunks}`],
234
- ],
235
- }));
236
- await appends.run(snapshot.runId, async () => {
211
+ save: (snapshot) => Effect.gen(function* () {
212
+ yield* Effect.try({
213
+ try: () => assertSnapshotByteLimit(snapshot),
214
+ catch: (cause) => cause instanceof SnapshotPayloadTooLargeError
215
+ ? cause
216
+ : new SnapshotStoreError('snapshot size validation failed', cause),
217
+ });
218
+ const stream = yield* ensureStream(snapshot.runId);
219
+ const frames = frameSnapshot(snapshot);
220
+ const records = frames.map((frame) => AppendRecord.string({
221
+ body: encodeFrame(frame),
222
+ headers: [
223
+ ['looms-cursor', String(frame.cursor)],
224
+ ['looms-chunk', `${frame.chunk}/${frame.chunks}`],
225
+ ],
226
+ }));
227
+ yield* Effect.tryPromise({
228
+ try: () => appends.run(snapshot.runId, () => {
237
229
  if (records.length === 1) {
238
- await stream.append(AppendInput.create(records));
239
- return;
230
+ return stream.append(AppendInput.create(records)).then(() => undefined);
240
231
  }
232
+ let pending = Promise.resolve();
241
233
  for (const record of records) {
242
- await stream.append(AppendInput.create([record]));
234
+ pending = pending.then(() => stream.append(AppendInput.create([record])).then(() => undefined));
243
235
  }
244
- });
245
- },
246
- catch: (cause) => toError(cause, `Failed to save snapshot for ${snapshot.runId}`),
236
+ return pending;
237
+ }),
238
+ catch: (cause) => toSnapshotError(cause, `Failed to save snapshot for ${snapshot.runId}`),
239
+ });
247
240
  }),
248
- loadLatest: (runId) => Effect.tryPromise({
249
- try: async () => {
250
- const stream = await ensureStream(runId);
251
- const tailFrame = await readLatestFrame(stream);
252
- if (!tailFrame) {
253
- return Option.none();
254
- }
255
- if (tailFrame.frame.chunk === tailFrame.frame.chunks - 1) {
256
- const lastFrame = tailFrame.frame;
257
- const records = (await readFromTail(stream, lastFrame.chunks + TAIL_PROBE)) ?? [];
258
- const frames = records
259
- .map((record) => parseFrame(record.body))
260
- .filter((frame) => frame !== undefined)
261
- .filter((frame) => frame.cursor === lastFrame.cursor);
262
- if (frames.length === lastFrame.chunks) {
263
- return Option.some(assembleSnapshot(frames));
264
- }
241
+ loadLatest: (runId) => Effect.gen(function* () {
242
+ const stream = yield* ensureStream(runId);
243
+ const tailFrame = yield* readLatestFrame(stream);
244
+ if (!tailFrame) {
245
+ return Option.none();
246
+ }
247
+ if (tailFrame.frame.chunk === tailFrame.frame.chunks - 1) {
248
+ const lastFrame = tailFrame.frame;
249
+ const records = (yield* readFromTail(stream, lastFrame.chunks + TAIL_PROBE)) ?? [];
250
+ const frames = records
251
+ .map((record) => parseFrame(record.body))
252
+ .filter((frame) => frame !== undefined)
253
+ .filter((frame) => frame.cursor === lastFrame.cursor);
254
+ if (frames.length === lastFrame.chunks) {
255
+ return Option.some(assembleSnapshot(frames));
265
256
  }
266
- // Incomplete or interleaved tail: fall back to scanning the retained stream.
267
- const records = await readAllSnapshotRecords(stream);
268
- const complete = groupCompleteSnapshots(records);
269
- const newest = complete[complete.length - 1];
270
- return newest ? Option.some(newest) : Option.none();
271
- },
272
- catch: (cause) => toError(cause, `Failed to load snapshot for ${runId}`),
257
+ }
258
+ // Incomplete or interleaved tail: fall back to scanning the retained stream.
259
+ const records = yield* readAllSnapshotRecords(stream);
260
+ const complete = groupCompleteSnapshots(records);
261
+ const newest = complete[complete.length - 1];
262
+ return newest ? Option.some(newest) : Option.none();
273
263
  }),
274
- listCursors: (runId) => Effect.tryPromise({
275
- try: async () => {
276
- const stream = await ensureStream(runId);
277
- const records = await readAllSnapshotRecords(stream);
278
- return groupCompleteSnapshots(records).map((snapshot) => snapshot.cursor);
279
- },
280
- catch: (cause) => toError(cause, `Failed to list snapshot cursors for ${runId}`),
264
+ listCursors: (runId) => Effect.gen(function* () {
265
+ const stream = yield* ensureStream(runId);
266
+ const records = yield* readAllSnapshotRecords(stream);
267
+ return groupCompleteSnapshots(records).map((snapshot) => snapshot.cursor);
281
268
  }),
282
- prune: (runId, keepLatest) => Effect.tryPromise({
283
- try: async () => {
284
- const stream = await ensureStream(runId);
285
- if (keepLatest <= 1) {
286
- // Fast path: the latest complete frame tells us where the latest snapshot starts.
287
- const latest = await readLatestFrame(stream);
288
- if (!latest || latest.frame.chunk !== latest.frame.chunks - 1) {
289
- return;
290
- }
291
- const start = latest.record.seqNum - latest.frame.chunk;
292
- if (start <= 0) {
293
- return;
294
- }
295
- await appends.run(runId, () => stream.append(AppendInput.create([AppendRecord.trim(start)])));
269
+ prune: (runId, keepLatest) => Effect.gen(function* () {
270
+ const stream = yield* ensureStream(runId);
271
+ if (keepLatest <= 1) {
272
+ // Fast path: the latest complete frame tells us where the latest snapshot starts.
273
+ const latest = yield* readLatestFrame(stream);
274
+ if (!latest || latest.frame.chunk !== latest.frame.chunks - 1) {
296
275
  return;
297
276
  }
298
- const records = await readAllSnapshotRecords(stream);
299
- const complete = groupCompleteSnapshots(records);
300
- if (complete.length <= keepLatest) {
277
+ const start = latest.record.seqNum - latest.frame.chunk;
278
+ if (start <= 0) {
301
279
  return;
302
280
  }
303
- const keep = complete[complete.length - keepLatest];
304
- if (!keep) {
305
- return;
281
+ yield* Effect.tryPromise({
282
+ try: () => appends.run(runId, () => stream.append(AppendInput.create([AppendRecord.trim(start)]))),
283
+ catch: (cause) => toSnapshotError(cause, `Failed to prune snapshots for ${runId}`),
284
+ });
285
+ return;
286
+ }
287
+ const records = yield* readAllSnapshotRecords(stream);
288
+ const complete = groupCompleteSnapshots(records);
289
+ if (complete.length <= keepLatest) {
290
+ return;
291
+ }
292
+ const keep = complete[complete.length - keepLatest];
293
+ if (!keep) {
294
+ return;
295
+ }
296
+ const firstKept = records.find((record) => parseFrame(record.body)?.cursor === keep.cursor);
297
+ if (!firstKept) {
298
+ return;
299
+ }
300
+ yield* Effect.tryPromise({
301
+ try: () => appends.run(runId, () => stream.append(AppendInput.create([AppendRecord.trim(firstKept.seqNum)]))),
302
+ catch: (cause) => toSnapshotError(cause, `Failed to prune snapshots for ${runId}`),
303
+ });
304
+ }),
305
+ saveHeader: (header) => Effect.gen(function* () {
306
+ const stream = yield* ensureStream('__headers');
307
+ yield* Effect.tryPromise({
308
+ try: () => appends.run('__headers', () => stream.append(AppendInput.create([AppendRecord.string({ body: encodeHeader(header) })]))),
309
+ catch: (cause) => toSnapshotError(cause, `Failed to save run header for ${header.runId}`),
310
+ });
311
+ headers.set(header.runId, header);
312
+ }),
313
+ listHeaders: Effect.gen(function* () {
314
+ if (headersLoaded) {
315
+ return [...headers.values()];
316
+ }
317
+ const stream = yield* ensureStream('__headers');
318
+ const records = yield* paginateS2Stream((cursor, count) => Effect.tryPromise({
319
+ try: () => stream.read({
320
+ start: { from: { seqNum: cursor }, clamp: true },
321
+ stop: { limits: { count } },
322
+ ignoreCommandRecords: true,
323
+ }),
324
+ catch: (cause) => toSnapshotError(cause, 'Failed to read run headers'),
325
+ }).pipe(Effect.catchIf((error) => error.cause instanceof S2Error &&
326
+ (error.cause.status === 404 || error.cause.status === 416), () => Effect.succeed(null)))).pipe(Stream.runCollect);
327
+ const loaded = new Map();
328
+ for (const record of records) {
329
+ const decoded = decodeHeader(record.body);
330
+ if (Option.isSome(decoded)) {
331
+ loaded.set(decoded.value.runId, decoded.value);
306
332
  }
307
- const firstKept = records.find((record) => parseFrame(record.body)?.cursor === keep.cursor);
308
- if (!firstKept) {
309
- return;
333
+ }
334
+ for (const [runId, header] of loaded) {
335
+ if (!headers.has(runId)) {
336
+ headers.set(runId, header);
310
337
  }
311
- await appends.run(runId, () => stream.append(AppendInput.create([AppendRecord.trim(firstKept.seqNum)])));
312
- },
313
- catch: (cause) => toError(cause, `Failed to prune snapshots for ${runId}`),
338
+ }
339
+ headersLoaded = true;
340
+ return [...headers.values()];
314
341
  }),
315
342
  };
316
343
  return service;
@@ -1,26 +1,8 @@
1
- import { Effect, Layer, Stream } from 'effect';
1
+ import { Effect, Layer } from 'effect';
2
2
  import { EventStoreError, EventStoreTag, type EventStore } from '@swirls/looms/core';
3
3
  import { type S2Config } from './config';
4
- export interface S2ReadBatchLike<T> {
5
- readonly records: ReadonlyArray<T>;
6
- readonly tail?: {
7
- readonly seqNum: number;
8
- } | null;
9
- }
10
- export interface ReadAllPagesOptions {
11
- readonly fromSeq?: number;
12
- readonly limit?: number;
13
- readonly maxPageSize?: number;
14
- }
15
- /**
16
- * Paginate an S2 stream lazily using Effect Streams.
17
- *
18
- * Pulls pages on demand until the stream tail is reached, an empty page is returned,
19
- * or the caller's optional limit is satisfied.
20
- */
21
- export declare function paginateS2Stream<T extends {
22
- readonly seqNum: number;
23
- }>(fetchPage: (cursor: number, count: number) => Effect.Effect<S2ReadBatchLike<T> | null, EventStoreError>, options?: ReadAllPagesOptions): Stream.Stream<T, EventStoreError>;
4
+ import { paginateS2Stream, type ReadAllPagesOptions, type S2ReadBatchLike } from './pagination';
5
+ export { paginateS2Stream, type ReadAllPagesOptions, type S2ReadBatchLike };
24
6
  export declare function readAllPages<T extends {
25
7
  readonly seqNum: number;
26
8
  }>(fetchPage: (cursor: number, count: number) => Promise<S2ReadBatchLike<T> | null>, options?: ReadAllPagesOptions): Promise<T[]>;