@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,213 +1,153 @@
1
- import { Data, Effect, Option, Predicate } from 'effect';
2
- import { advanceCursor, assignSequences, composeModules, createCoalescingAppender, createEvent, createThreadId, createRunId, DEFAULT_SNAPSHOT_EVERY, emptyCursor, EventStoreTag, EventStoreTrimmedError, foldRun, hashRunState, isRunTerminal, isWaitOnTimer, project, replayTo, validateEventInput, withdrawnError, } from '@swirls/looms/core';
3
- import { dispatchEffect } from './dispatch-effect.js';
4
- import { createEffectFailedEvent, createLiveEvent, groupOutstanding, materializeEffectOutcome, moduleServices, resolveSnapshotStore, RunCursorCache, stripSeq, synthesizedThreadFailed, threadStartedEvents, validateAndCreateEvents, waitSatisfiedEvents, } from './runtime-helpers.js';
5
- import { createTimeoutScheduler, toEffectVoid } from './wake-scheduler.js';
1
+ import { Clock, Effect, ManagedRuntime, Predicate } from 'effect';
2
+ import { composeModules, createEvent, createThreadId, createRunId, DEFAULT_DEFINITION_VERSION, DEFAULT_SNAPSHOT_EVERY, definitionKey, EventStoreTag, foldRun, project, replayTo, summarizeRun, } from '@swirls/looms/core';
3
+ import { createCancellation } from './cancellation.js';
4
+ import { nextRuntimeDeadline } from './deadline-transition-planner.js';
5
+ import { RuntimeExecutionError } from './errors.js';
6
+ import { admitExternalEvents, moduleServices, resolveSnapshotStore, RunCursorCache, threadStartedEvents, UnknownDefinitionError, validateAndCreateEvents, waitSatisfiedEvents, } from './helpers.js';
7
+ import { createRunIngress } from './ingress.js';
8
+ import { notifyObserver } from './observer.js';
9
+ import { createRuntimeRepository } from './repository.js';
10
+ import { RunExecutionContexts } from './run-execution-context.js';
11
+ import { assertSameDurableStart } from './run-start.js';
12
+ import { createWakeEngine } from './wake-engine.js';
13
+ import { createTimeoutScheduler } from './wake-scheduler.js';
14
+ import { createWorkerCallbacks } from './worker-callbacks.js';
6
15
  export { createTimeoutScheduler } from './wake-scheduler.js';
7
- export class DuplicateEffectDispatchError extends Data.TaggedError('DuplicateEffectDispatchError') {
8
- constructor(runId, effectId, threadId) {
9
- super({
10
- runId,
11
- effectId,
12
- threadId,
13
- message: `Duplicate effect dispatch detected for effectId "${effectId}" on thread "${threadId}"; refusing to re-dispatch already executed effect`,
14
- });
15
- this.name = 'DuplicateEffectDispatchError';
16
- }
16
+ export { DuplicateEffectDispatchError, MaxWakeIterationsError, StartRunConflictError, } from './errors.js';
17
+ export { RunOverloadedError } from './ingress.js';
18
+ function currentTimeMillis() {
19
+ return Math.floor(performance.timeOrigin + performance.now());
17
20
  }
18
- export class MaxWakeIterationsError extends Data.TaggedError('MaxWakeIterationsError') {
19
- constructor(runId, maxIterations) {
20
- super({
21
- runId,
22
- maxIterations,
23
- message: `Max wake iterations exceeded (${maxIterations}) for run "${runId}"; possible infinite loop`,
24
- });
25
- this.name = 'MaxWakeIterationsError';
21
+ /** Threads whose in-process effects an admitted signal interrupts. */
22
+ function interruptedThreadIds(events, rules) {
23
+ const threadIds = new Set();
24
+ for (const event of events) {
25
+ const payload = Predicate.isObject(event.payload) ? event.payload : {};
26
+ if (event.type === 'runtime.thread.cancel.requested' && Predicate.isString(payload.threadId)) {
27
+ threadIds.add(payload.threadId);
28
+ }
29
+ for (const rule of rules) {
30
+ for (const threadId of rule(event)) {
31
+ threadIds.add(threadId);
32
+ }
33
+ }
26
34
  }
35
+ return threadIds;
27
36
  }
28
37
  const DEFAULT_RUN_CACHE_SIZE = 0;
38
+ const RESCAN_CONCURRENCY = 16;
29
39
  const DEFAULT_KEEP_SNAPSHOTS = 1;
40
+ const DEFAULT_MAX_PENDING_RUN_OPERATIONS = 256;
30
41
  export function createRuntime(options) {
31
42
  const registry = composeModules(options.modules);
32
43
  const registeredDefinitions = registry.definitions;
33
- const definitions = new Map(registeredDefinitions.map((def) => [`${def.kind}:${def.name}`, def]));
34
- const services = moduleServices(options.modules);
35
- const waking = new Set();
36
- /** Coalesce wake requests that arrive while a wake is in flight for the same run. */
37
- const wakeAgain = new Set();
44
+ const definitions = new Map(registeredDefinitions.map((def) => [definitionKey(def.kind, def.name, def.version), def]));
45
+ // Module composition erases the heterogeneous service identifier union at this host boundary.
46
+ // oxlint-disable-next-line effecttsgo/any-unknown-in-error-context
47
+ const services = ManagedRuntime.make(moduleServices(options.modules));
48
+ const executionContexts = new RunExecutionContexts();
49
+ const maxPendingRunOperations = options.maxPendingRunOperations ?? DEFAULT_MAX_PENDING_RUN_OPERATIONS;
38
50
  const snapshotEvery = options.snapshotEvery ?? DEFAULT_SNAPSHOT_EVERY;
39
51
  const maxWakeIterations = options.maxWakeIterations ?? 100;
40
52
  const snapshotStore = resolveSnapshotStore(options.snapshotStore, options.store);
41
53
  const trimAfterSnapshot = options.trimAfterSnapshot;
42
54
  const keepSnapshots = trimAfterSnapshot?.keepSnapshots ?? DEFAULT_KEEP_SNAPSHOTS;
43
55
  const runCache = new RunCursorCache(options.runCacheSize ?? DEFAULT_RUN_CACHE_SIZE);
44
- let liveStore = options.store;
45
- const liveCount = new Map();
46
- const liveAppends = createCoalescingAppender((runId, batch) => {
47
- const store = liveStore;
48
- if (!store) {
49
- return Effect.fail(new Error('Cannot append live events without a store'));
50
- }
51
- return store.append(runId, batch).pipe(Effect.tap((result) => Effect.sync(() => {
52
- liveCount.set(runId, (liveCount.get(runId) ?? 0) + result.sequences.length);
53
- })), Effect.asVoid);
54
- });
55
- const readRemaining = (store, runId, events, expectedTail) => Effect.gen(function* () {
56
- let collected = events;
57
- while (collected.length > 0) {
58
- const lastSeq = collected[collected.length - 1].seq;
59
- const target = expectedTail ?? (yield* store.tail(runId));
60
- if (lastSeq >= target) {
61
- break;
62
- }
63
- const more = yield* store.read(runId, {
64
- fromSeq: lastSeq + 1,
65
- limit: expectedTail !== undefined ? Math.max(0, target - lastSeq) : undefined,
66
- });
67
- if (more.length === 0 || more[0].seq <= lastSeq) {
68
- break;
69
- }
70
- collected = [...collected, ...more];
71
- }
72
- return collected;
56
+ const observe = (event) => notifyObserver(options.observer, event);
57
+ const withRunIngress = createRunIngress(executionContexts, maxPendingRunOperations);
58
+ const repository = createRuntimeRepository({
59
+ registry,
60
+ snapshotStore,
61
+ trimAfterSnapshot,
62
+ keepSnapshots,
63
+ runCache,
64
+ executionContexts,
65
+ observer: options.observer,
73
66
  });
74
- const catchUp = (store, runId, cursor, expectedTail) => Effect.gen(function* () {
75
- if (expectedTail !== undefined && cursor.seq === expectedTail) {
76
- return cursor;
77
- }
78
- const fromSeq = cursor.seq + 1;
79
- const limit = expectedTail !== undefined ? Math.max(0, expectedTail - cursor.seq) : undefined;
80
- let delta = yield* store.read(runId, { fromSeq, limit });
81
- if (delta.length === 0) {
82
- return cursor;
83
- }
84
- const firstSeq = delta[0].seq;
85
- if (firstSeq > fromSeq) {
86
- if (store.bounds) {
87
- const bounds = yield* store.bounds(runId);
88
- if (bounds.head > fromSeq) {
89
- return yield* Effect.fail(new EventStoreTrimmedError(runId, bounds.head, fromSeq));
90
- }
91
- }
92
- else {
93
- return yield* Effect.fail(new EventStoreTrimmedError(runId, firstSeq, fromSeq));
67
+ const { commit, loadCursor } = repository;
68
+ const signalAdmitted = (runId, events, signalOpts) => Effect.gen(function* () {
69
+ const store = yield* EventStoreTag;
70
+ const signalKey = signalOpts?.idempotencyKey;
71
+ const committed = yield* commit(store, runId, (current) => Effect.gen(function* () {
72
+ const processed = current.state.processedIdempotencyKeys;
73
+ const filteredEvents = signalKey && processed.includes(signalKey)
74
+ ? []
75
+ : events.filter((input) => {
76
+ const key = input.idempotencyKey ?? signalKey;
77
+ return !key || !processed.includes(key);
78
+ });
79
+ if (filteredEvents.length === 0) {
80
+ return { events: [], value: current.state };
94
81
  }
82
+ const batch = yield* validateAndCreateEvents(registry.catalogs, runId, filteredEvents.map((input) => ({
83
+ ...input,
84
+ idempotencyKey: input.idempotencyKey ?? signalKey,
85
+ })), { origin: { type: 'external' } });
86
+ const folded = foldRun(batch, registry, { runId, initial: current.state });
87
+ const satisfiedEvents = waitSatisfiedEvents(folded, batch).map((input) => createEvent(runId, input));
88
+ return {
89
+ events: [...batch, ...satisfiedEvents],
90
+ value: current.state,
91
+ idempotencyKey: signalKey ? `signal:${signalKey}` : undefined,
92
+ };
93
+ }));
94
+ if (committed.append) {
95
+ executionContexts.abort(runId, interruptedThreadIds(events, registry.interruptRules), 'Effect interrupted');
95
96
  }
96
- delta = yield* readRemaining(store, runId, delta, expectedTail);
97
- return advanceCursor(cursor, delta, registry);
97
+ return committed.value;
98
98
  });
99
- const loadCursorCold = (store, runId) => Effect.gen(function* () {
100
- let cursor = emptyCursor(runId);
101
- if (snapshotStore) {
102
- const snap = yield* snapshotStore
103
- .loadLatest(runId)
104
- .pipe(Effect.catch(() => Effect.succeed(Option.none())));
105
- if (Option.isSome(snap)) {
106
- cursor = {
107
- state: snap.value.state,
108
- seq: snap.value.cursor,
109
- durableSinceSnapshot: 0,
110
- };
111
- }
112
- }
113
- const fromSeq = cursor.seq + 1;
114
- let delta = yield* store.read(runId, { fromSeq });
115
- if (cursor.seq === 0 && delta[0] && delta[0].seq > 1) {
116
- if (store.bounds) {
117
- const bounds = yield* store.bounds(runId);
118
- if (bounds.head > 1) {
119
- return yield* Effect.fail(new EventStoreTrimmedError(runId, bounds.head, 1));
120
- }
121
- }
122
- else {
123
- return yield* Effect.fail(new EventStoreTrimmedError(runId, delta[0].seq, 1));
124
- }
125
- }
126
- if (delta.length > 0) {
127
- delta = yield* readRemaining(store, runId, delta);
128
- }
129
- return advanceCursor(cursor, delta, registry);
99
+ let scheduler;
100
+ let runtime;
101
+ const wakeEngine = createWakeEngine({
102
+ registry,
103
+ services,
104
+ definitions,
105
+ executionContexts,
106
+ repository,
107
+ snapshotEvery,
108
+ maxWakeIterations,
109
+ worker: options.worker,
110
+ observer: options.observer,
111
+ scheduler: () => scheduler,
130
112
  });
131
- const loadCursor = (store, runId) => Effect.gen(function* () {
132
- const cached = runCache.get(runId);
133
- if (cached) {
134
- const tail = yield* store.tail(runId);
135
- // A tail behind the cursor means the stream was recreated; the cache is poison.
136
- if (tail < cached.seq) {
137
- runCache.delete(runId);
138
- return yield* loadCursorCold(store, runId);
139
- }
140
- if (tail === cached.seq) {
141
- return cached;
142
- }
143
- const next = yield* catchUp(store, runId, cached, tail).pipe(Effect.catchTag('EventStoreTrimmedError', () => {
144
- runCache.delete(runId);
145
- return loadCursorCold(store, runId);
146
- }));
147
- runCache.set(runId, next);
148
- return next;
149
- }
150
- const next = yield* loadCursorCold(store, runId);
151
- runCache.set(runId, next);
152
- return next;
113
+ const workerCallbacks = createWorkerCallbacks({
114
+ registry,
115
+ repository,
116
+ withRunIngress,
117
+ observer: options.observer,
118
+ wake: wakeEngine,
153
119
  });
154
- /**
155
- * Save the current state externally, keep only `keepSnapshots`, and optionally
156
- * trim the event log behind the oldest kept snapshot. Failures are swallowed:
157
- * a snapshot is an optimization, the log stays authoritative.
158
- */
159
- const persistSnapshot = (store, runId, cursor) => Effect.gen(function* () {
160
- if (cursor.durableSinceSnapshot === 0) {
161
- return cursor;
162
- }
163
- yield* snapshotStore.save({
164
- runId,
165
- cursor: cursor.seq,
166
- stateHash: hashRunState(cursor.state),
167
- takenAt: Date.now(),
168
- state: cursor.state,
169
- });
170
- if (snapshotStore.prune) {
171
- yield* snapshotStore.prune(runId, keepSnapshots);
172
- }
173
- if (trimAfterSnapshot && store.trim && snapshotStore.listCursors) {
174
- const cursors = yield* snapshotStore.listCursors(runId);
175
- const oldestKept = cursors[Math.max(0, cursors.length - keepSnapshots)];
176
- if (oldestKept !== undefined) {
177
- yield* store.trim(runId, oldestKept + 1);
178
- }
179
- }
180
- return { ...cursor, durableSinceSnapshot: 0 };
181
- }).pipe(Effect.catch(() => Effect.succeed(cursor)));
182
- const applyAppend = (store, runId, cursor, batch, appendResult, commandRecords = 0) => Effect.gen(function* () {
183
- const echo = assignSequences(batch, appendResult);
184
- const first = echo[0];
185
- let next = cursor;
186
- if (first && first.seq !== cursor.seq + 1 + commandRecords) {
187
- const gap = first.seq - 1 - cursor.seq;
188
- const live = liveCount.get(runId) ?? 0;
189
- if (live === gap) {
190
- next = { ...cursor, seq: first.seq - 1 };
191
- }
192
- else {
193
- next = yield* catchUp(store, runId, cursor, first.seq - 1);
194
- }
195
- }
196
- next = advanceCursor(next, echo, registry);
197
- liveCount.set(runId, 0);
198
- if (next.seq !== appendResult.tail) {
199
- next = yield* catchUp(store, runId, next, appendResult.tail);
200
- }
201
- runCache.set(runId, next);
202
- return next;
120
+ const cancellation = createCancellation({
121
+ registry,
122
+ repository,
123
+ executionContexts,
124
+ worker: options.worker,
125
+ observer: options.observer,
126
+ wake: wakeEngine,
127
+ withRunIngress,
203
128
  });
204
- let scheduler;
205
- const runtime = {
129
+ runtime = {
206
130
  modules: options.modules,
207
131
  registry,
208
- listRuns: () => Effect.gen(function* () {
132
+ ...workerCallbacks,
133
+ listRuns: Effect.gen(function* () {
209
134
  const store = yield* EventStoreTag;
210
- return yield* store.listRuns();
135
+ return yield* store.listRuns;
136
+ }),
137
+ listRunSummaries: Effect.gen(function* () {
138
+ const store = yield* EventStoreTag;
139
+ const runIds = yield* store.listRuns;
140
+ const indexed = yield* snapshotStore.listHeaders;
141
+ const byId = new Map(indexed.map((header) => [header.runId, header]));
142
+ const missing = runIds.filter((runId) => !byId.has(runId));
143
+ const filled = yield* Effect.forEach(missing, (runId) => runtime.getRun(runId).pipe(Effect.map(summarizeRun)), { concurrency: 8 });
144
+ for (const header of filled) {
145
+ byId.set(header.runId, header);
146
+ }
147
+ return runIds.flatMap((runId) => {
148
+ const header = byId.get(runId);
149
+ return header ? [header] : [];
150
+ });
211
151
  }),
212
152
  getEvents: (runId, readOptions) => Effect.gen(function* () {
213
153
  const store = yield* EventStoreTag;
@@ -226,306 +166,144 @@ export function createRuntime(options) {
226
166
  const events = yield* runtime.getEvents(runId);
227
167
  return replayTo(events, registry, seq);
228
168
  }),
229
- rescanTimers: () => Effect.gen(function* () {
169
+ inspectRun: (runId) => Effect.gen(function* () {
230
170
  const store = yield* EventStoreTag;
231
- const runIds = yield* store.listRuns();
232
- let count = 0;
233
- for (const runId of runIds) {
234
- const runState = yield* runtime.getRun(runId);
235
- if (isRunTerminal(runState)) {
236
- continue;
237
- }
238
- let earliestTimerAt = null;
239
- for (const waitRecord of Object.values(runState.waits)) {
240
- if (isWaitOnTimer(waitRecord.on)) {
241
- if (earliestTimerAt === null || waitRecord.on.timerAt < earliestTimerAt) {
242
- earliestTimerAt = waitRecord.on.timerAt;
243
- }
244
- }
245
- }
246
- if (earliestTimerAt !== null) {
247
- yield* toEffectVoid(scheduler.schedule(runId, earliestTimerAt));
248
- count += 1;
249
- }
250
- }
251
- return count;
171
+ const state = yield* runtime.getRun(runId);
172
+ const tail = yield* store.tail(runId);
173
+ const bounds = store.bounds ? yield* store.bounds(runId) : { head: 1, tail };
174
+ const effects = state.outstandingEffects.map((item) => {
175
+ const execution = state.effectExecutions[item.effectId];
176
+ return {
177
+ effectId: item.effectId,
178
+ threadId: item.threadId,
179
+ type: item.effect.type,
180
+ attempt: execution?.attempt ?? 0,
181
+ status: execution?.status ?? 'pending',
182
+ deadlineAt: execution?.deadlineAt ?? null,
183
+ lastError: execution?.lastError ?? null,
184
+ };
185
+ });
186
+ return { runId, state, head: bounds.head, tail, effects };
252
187
  }),
253
- startRun: (args) => Effect.gen(function* () {
254
- if (!definitions.get(`${args.kind}:${args.definitionName}`)) {
255
- return yield* Effect.fail(new Error(`Unknown definition ${args.kind}:${args.definitionName}`));
256
- }
188
+ retryEffect: (runId, effectId) => withRunIngress(runId, Effect.gen(function* () {
257
189
  const store = yield* EventStoreTag;
258
- const runId = args.runId ?? createRunId();
259
- const existing = yield* loadCursor(store, runId);
260
- if (existing.seq > 0) {
261
- if ((args.idempotencyKey &&
262
- existing.state.processedIdempotencyKeys?.includes(args.idempotencyKey)) ||
263
- existing.state.rootThreadId) {
264
- return {
265
- runId,
266
- threadId: existing.state.rootThreadId ?? '',
267
- state: existing.state,
268
- };
190
+ yield* commit(store, runId, (cursor) => Effect.gen(function* () {
191
+ const execution = cursor.state.effectExecutions[effectId];
192
+ const item = cursor.state.outstandingEffects.find((candidate) => candidate.effectId === effectId);
193
+ if (!execution || !item) {
194
+ return yield* new RuntimeExecutionError(`Unknown outstanding effect "${effectId}"`);
269
195
  }
270
- }
271
- const threadId = args.threadId ?? createThreadId();
272
- const startedEvents = yield* threadStartedEvents(definitions, {
273
- kind: args.kind,
274
- definitionName: args.definitionName,
275
- input: args.input ?? null,
276
- threadId,
277
- parentThreadId: null,
278
- });
279
- const started = startedEvents[0];
280
- const startedInput = started && Predicate.isObject(started.payload)
281
- ? (started.payload.input ?? null)
282
- : (args.input ?? null);
283
- const batch = yield* validateAndCreateEvents(registry.catalogs, runId, [
284
- {
285
- type: 'runtime.run.started',
196
+ if (execution.status !== 'ambiguous' &&
197
+ execution.status !== 'timed_out' &&
198
+ execution.status !== 'cancel_requested') {
199
+ return yield* new RuntimeExecutionError(`Effect "${effectId}" cannot be retried from status "${execution.status}"`);
200
+ }
201
+ const now = yield* Clock.currentTimeMillis;
202
+ const retryEvent = createEvent(runId, {
203
+ type: 'runtime.effect.retry.scheduled',
286
204
  payload: {
287
- rootThreadId: threadId,
288
- kind: args.kind,
289
- definitionName: args.definitionName,
290
- input: startedInput,
205
+ effectId,
206
+ attempt: execution.attempt,
207
+ nextAttemptAt: now,
208
+ error: `operator retry from ${execution.status}`,
291
209
  },
292
- threadId: null,
293
- idempotencyKey: args.idempotencyKey,
294
- },
295
- ...startedEvents,
296
- ], { origin: { type: 'system' } });
297
- yield* store.append(runId, stripSeq(batch));
298
- const state = yield* runtime.wake(runId);
299
- return { runId, threadId, state };
300
- }),
301
- signal: (runId, events, signalOpts) => Effect.gen(function* () {
302
- const store = yield* EventStoreTag;
303
- const current = yield* loadCursor(store, runId);
304
- const currentState = current.state;
305
- if (signalOpts?.idempotencyKey &&
306
- currentState.processedIdempotencyKeys?.includes(signalOpts.idempotencyKey)) {
307
- return currentState;
308
- }
309
- const filteredEvents = events.filter((input) => {
310
- const key = input.idempotencyKey ?? signalOpts?.idempotencyKey;
311
- return !key || !currentState.processedIdempotencyKeys?.includes(key);
312
- });
313
- if (filteredEvents.length === 0) {
314
- return currentState;
315
- }
316
- const batch = yield* validateAndCreateEvents(registry.catalogs, runId, filteredEvents.map((input) => ({
317
- ...input,
318
- idempotencyKey: input.idempotencyKey ?? signalOpts?.idempotencyKey,
319
- })), { origin: { type: 'external' } });
320
- const folded = foldRun(batch, registry, { runId, initial: currentState });
321
- const satisfied = waitSatisfiedEvents(folded, batch);
322
- const satisfiedEvents = satisfied.map((input) => createEvent(runId, input));
323
- yield* store.append(runId, stripSeq([...batch, ...satisfiedEvents]));
324
- return yield* runtime.wake(runId);
325
- }),
326
- cancel: (runId, threadId) => Effect.gen(function* () {
210
+ threadId: item.threadId,
211
+ causationId: item.causingEventId,
212
+ origin: { type: 'external', actorId: 'operator' },
213
+ });
214
+ return { events: [retryEvent], value: undefined };
215
+ }));
216
+ })).pipe(Effect.andThen(runtime.wake(runId))),
217
+ rescanTimers: Effect.gen(function* () {
327
218
  const store = yield* EventStoreTag;
328
- const targetThreadId = threadId ?? (yield* loadCursor(store, runId)).state.rootThreadId ?? '';
329
- return yield* runtime.signal(runId, [
330
- {
331
- type: 'runtime.thread.cancelled',
332
- payload: { threadId: targetThreadId, reason: 'cancelled' },
333
- threadId: targetThreadId || null,
334
- origin: { type: 'external' },
335
- },
336
- ]);
219
+ const runIds = yield* store.listRuns;
220
+ const scheduled = yield* Effect.forEach(runIds, (runId) => Effect.gen(function* () {
221
+ const deadline = nextRuntimeDeadline(yield* runtime.getRun(runId));
222
+ if (deadline === undefined) {
223
+ return false;
224
+ }
225
+ yield* scheduler.schedule(runId, deadline);
226
+ return true;
227
+ }), { concurrency: RESCAN_CONCURRENCY });
228
+ return scheduled.filter(Boolean).length;
337
229
  }),
338
- wake: (runId) => Effect.gen(function* () {
339
- // Claim synchronously before any yield so concurrent fibers cannot both pass.
340
- if (waking.has(runId)) {
341
- wakeAgain.add(runId);
342
- return yield* runtime.getRun(runId);
343
- }
344
- waking.add(runId);
345
- const store = yield* EventStoreTag;
346
- liveStore = store;
347
- try {
348
- let finalState;
349
- do {
350
- let cursor = yield* loadCursor(store, runId);
351
- if (isRunTerminal(cursor.state)) {
352
- finalState = cursor.state;
353
- break;
354
- }
355
- liveCount.set(runId, 0);
356
- const dispatchedEffectIds = new Set();
357
- let guard = 0;
358
- while (!isRunTerminal(cursor.state)) {
359
- if (guard >= maxWakeIterations) {
360
- return yield* Effect.fail(new MaxWakeIterationsError(runId, maxWakeIterations));
361
- }
362
- guard += 1;
363
- const state = cursor.state;
364
- const now = Date.now();
365
- const due = Object.values(state.waits).filter((record) => isWaitOnTimer(record.on) && record.on.timerAt <= now + 5);
366
- if (due.length > 0) {
367
- const batch = [];
368
- for (const record of due) {
369
- if (!isWaitOnTimer(record.on)) {
370
- continue;
371
- }
372
- batch.push(createEvent(runId, {
373
- type: 'runtime.timer.fired',
374
- payload: { timerId: record.waitId, waitId: record.waitId },
375
- threadId: record.threadId,
376
- origin: { type: 'system' },
377
- }));
378
- }
379
- const stateAfterFired = foldRun(batch, registry, { runId, initial: state });
380
- const satisfied = waitSatisfiedEvents(stateAfterFired, batch);
381
- const satisfiedEvents = satisfied.map((input) => createEvent(runId, input));
382
- const fullTimerBatch = [...batch, ...satisfiedEvents];
383
- const appendResult = yield* store.append(runId, stripSeq(fullTimerBatch));
384
- cursor = yield* applyAppend(store, runId, cursor, stripSeq(fullTimerBatch), appendResult);
385
- continue;
386
- }
387
- const outstanding = state.outstandingEffects;
388
- if (outstanding.length === 0) {
389
- break;
390
- }
391
- for (const item of outstanding) {
392
- if (dispatchedEffectIds.has(item.effectId)) {
393
- return yield* Effect.fail(new DuplicateEffectDispatchError(runId, item.effectId, item.threadId));
394
- }
395
- }
396
- for (const item of outstanding) {
397
- dispatchedEffectIds.add(item.effectId);
398
- }
399
- const groupKeys = groupOutstanding(outstanding);
400
- const groupResults = yield* Effect.forEach(groupKeys, (groupKey) => Effect.gen(function* () {
401
- const group = outstanding.filter((item) => `${item.threadId}:${item.causingSeq}` === groupKey);
402
- const groupProduced = [];
403
- let failedEffectId;
404
- for (const item of group) {
405
- if (failedEffectId && item.effect.type === 'runtime.wait') {
406
- groupProduced.push(createEffectFailedEvent(runId, item, withdrawnError(failedEffectId)));
407
- continue;
408
- }
409
- const appendLive = async (input) => {
410
- const validated = await Effect.runPromise(validateEventInput(registry.catalogs, input));
411
- const liveEvent = createLiveEvent(runId, item, validated);
412
- if (liveEvent) {
413
- await Effect.runPromise(liveAppends.push(runId, liveEvent));
414
- }
415
- };
416
- const outcomes = yield* dispatchEffect(registry, services, definitions, item.effect, {
417
- effectId: item.effectId,
418
- runId,
419
- threadId: item.threadId,
420
- causingEventId: item.causingEventId,
421
- emit: appendLive,
422
- });
423
- const before = groupProduced.length;
424
- if (outcomes.length === 0) {
425
- groupProduced.push(createEffectFailedEvent(runId, item, 'empty-outcome'));
426
- }
427
- for (const input of outcomes) {
428
- groupProduced.push(yield* materializeEffectOutcome(registry.catalogs, runId, item, input));
429
- }
430
- const groupOutcomes = groupProduced.slice(before);
431
- if (item.effect.type !== 'runtime.wait' &&
432
- groupOutcomes.some((event) => event.type === 'runtime.effect.failed')) {
433
- failedEffectId = item.effectId;
434
- }
435
- }
436
- return groupProduced;
437
- }), { concurrency: 'unbounded' });
438
- const produced = groupResults.flat();
439
- if (produced.length === 0) {
440
- break;
441
- }
442
- yield* liveAppends.drain(runId);
443
- const beforeFold = cursor.state;
444
- const intermediateState = foldRun(produced, registry, { runId, initial: beforeFold });
445
- const synthesized = synthesizedThreadFailed(beforeFold, intermediateState, produced);
446
- const synthesizedEvents = synthesized.map((input) => createEvent(runId, input));
447
- const stateAfterSynth = synthesizedEvents.length > 0
448
- ? foldRun(synthesizedEvents, registry, { runId, initial: intermediateState })
449
- : intermediateState;
450
- const allNewEvents = [...produced, ...synthesizedEvents];
451
- const satisfied = waitSatisfiedEvents(stateAfterSynth, allNewEvents);
452
- const satisfiedEvents = satisfied.map((input) => createEvent(runId, input));
453
- const fullBatch = [...allNewEvents, ...satisfiedEvents];
454
- const appendResult = yield* store.append(runId, stripSeq(fullBatch));
455
- cursor = yield* applyAppend(store, runId, cursor, stripSeq(fullBatch), appendResult);
456
- // Bound replay after a crash inside a long wake.
457
- if (snapshotEvery > 0 && cursor.durableSinceSnapshot >= snapshotEvery) {
458
- cursor = yield* persistSnapshot(store, runId, cursor);
459
- }
460
- }
461
- const root = cursor.state.rootThreadId
462
- ? cursor.state.threads[cursor.state.rootThreadId]
463
- : undefined;
464
- if (root &&
465
- (root.status === 'completed' ||
466
- root.status === 'failed' ||
467
- root.status === 'cancelled') &&
468
- !isRunTerminal(cursor.state)) {
469
- const completed = createEvent(runId, {
470
- type: 'runtime.run.completed',
230
+ recoverDeadlines: Effect.suspend(() => runtime.rescanTimers),
231
+ startRun: (args) => {
232
+ const runId = args.runId ?? createRunId();
233
+ return withRunIngress(runId, Effect.gen(function* () {
234
+ const definitionVersion = args.definitionVersion ?? DEFAULT_DEFINITION_VERSION;
235
+ if (!definitions.get(definitionKey(args.kind, args.definitionName, definitionVersion))) {
236
+ return yield* new UnknownDefinitionError(args.kind, args.definitionName, definitionVersion);
237
+ }
238
+ const store = yield* EventStoreTag;
239
+ observe({ type: 'run.start', runId, at: currentTimeMillis() });
240
+ const threadId = args.threadId ?? createThreadId();
241
+ const startedEvents = yield* threadStartedEvents(definitions, {
242
+ kind: args.kind,
243
+ definitionName: args.definitionName,
244
+ definitionVersion,
245
+ input: args.input ?? null,
246
+ threadId,
247
+ parentThreadId: null,
248
+ });
249
+ const started = startedEvents[0];
250
+ const startedInput = started && Predicate.isObject(started.payload)
251
+ ? (started.payload.input ?? null)
252
+ : (args.input ?? null);
253
+ const requestedIdentity = {
254
+ kind: args.kind,
255
+ definitionName: args.definitionName,
256
+ definitionVersion,
257
+ input: startedInput,
258
+ requestedThreadId: args.threadId ?? null,
259
+ idempotencyKey: args.idempotencyKey ?? null,
260
+ };
261
+ const committed = yield* commit(store, runId, (existing) => existing.seq > 0
262
+ ? assertSameDurableStart(runId, existing.state, requestedIdentity).pipe(Effect.map((rootThreadId) => ({ events: [], value: rootThreadId })))
263
+ : validateAndCreateEvents(registry.catalogs, runId, [
264
+ {
265
+ type: 'runtime.run.started',
471
266
  payload: {
472
- output: root.output,
473
- error: root.error,
267
+ rootThreadId: threadId,
268
+ kind: args.kind,
269
+ definitionName: args.definitionName,
270
+ definitionVersion,
271
+ input: startedInput,
272
+ requestedThreadId: args.threadId ?? null,
474
273
  },
475
274
  threadId: null,
476
- origin: { type: 'system' },
477
- });
478
- const appendResult = yield* store.append(runId, stripSeq([completed]));
479
- cursor = yield* applyAppend(store, runId, cursor, stripSeq([completed]), appendResult);
480
- }
481
- // Snapshot on park: the next wake (on any instance) starts from here.
482
- cursor = yield* persistSnapshot(store, runId, cursor);
483
- const pendingTimers = Object.values(cursor.state.waits).filter((record) => isWaitOnTimer(record.on) && record.on.timerAt > Date.now());
484
- if (pendingTimers.length > 0 && !isRunTerminal(cursor.state)) {
485
- // SAFETY: pendingTimers is filtered by isWaitOnTimer above.
486
- const earliestTimerAt = Math.min(...pendingTimers.map((r) => r.on.timerAt));
487
- yield* toEffectVoid(scheduler.schedule(runId, earliestTimerAt));
488
- }
489
- else {
490
- yield* toEffectVoid(scheduler.cancel(runId));
491
- }
492
- if (isRunTerminal(cursor.state)) {
493
- runCache.delete(runId);
494
- }
495
- else {
496
- runCache.set(runId, cursor);
497
- }
498
- finalState = cursor.state;
499
- } while (wakeAgain.delete(runId));
500
- // Lost race: a waiter queued after the drain loop but before unlock.
501
- if (wakeAgain.has(runId)) {
502
- waking.delete(runId);
503
- liveAppends.clear(runId);
504
- liveCount.delete(runId);
505
- return yield* runtime.wake(runId);
506
- }
507
- return finalState;
508
- }
509
- finally {
510
- if (waking.has(runId)) {
511
- waking.delete(runId);
275
+ idempotencyKey: args.idempotencyKey,
276
+ },
277
+ ...startedEvents,
278
+ ], { origin: { type: 'system' } }).pipe(Effect.map((batch) => ({
279
+ events: batch,
280
+ value: threadId,
281
+ idempotencyKey: args.idempotencyKey
282
+ ? `start:${args.idempotencyKey}`
283
+ : undefined,
284
+ }))));
285
+ if (!committed.append?.deduplicated) {
286
+ return committed.value;
512
287
  }
513
- liveAppends.clear(runId);
514
- liveCount.delete(runId);
515
- }
516
- }),
517
- dispose: () => {
518
- scheduler.dispose?.();
288
+ const durable = yield* loadCursor(store, runId);
289
+ return yield* assertSameDurableStart(runId, durable.state, requestedIdentity);
290
+ })).pipe(Effect.flatMap((threadId) => runtime.wake(runId).pipe(Effect.map((state) => ({
291
+ runId,
292
+ threadId: state.rootThreadId ?? threadId,
293
+ state,
294
+ })))));
519
295
  },
296
+ signal: (runId, events, signalOpts) => admitExternalEvents(events).pipe(Effect.flatMap((admitted) => withRunIngress(runId, signalAdmitted(runId, admitted, signalOpts))), Effect.andThen(runtime.wake(runId))),
297
+ cancel: cancellation,
298
+ wake: wakeEngine,
299
+ dispose: Effect.sync(() => scheduler.dispose?.()).pipe(Effect.andThen(services.disposeEffect)),
520
300
  };
521
301
  scheduler =
522
302
  options.scheduler ??
523
- createTimeoutScheduler((runId) => {
524
- if (options.store) {
525
- Effect.runPromise(Effect.provideService(runtime.wake(runId), EventStoreTag, options.store)).catch((err) => {
526
- console.error('[timer wake error]', err);
527
- });
528
- }
529
- });
303
+ createTimeoutScheduler((runId) => options.store
304
+ ? runtime
305
+ .wake(runId)
306
+ .pipe(Effect.provideService(EventStoreTag, options.store), Effect.tapError(Effect.logError), Effect.ignore)
307
+ : Effect.void);
530
308
  return runtime;
531
309
  }