@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
@@ -0,0 +1,182 @@
1
+ import { Data, Effect, Semaphore } from 'effect';
2
+ import { runProjectorEffect } from './projector.js';
3
+ export class ProjectorCursorError extends Data.TaggedError('ProjectorCursorError') {
4
+ }
5
+ export class UnknownProjectorError extends Data.TaggedError('UnknownProjectorError') {
6
+ }
7
+ function projectorIdentity(projector) {
8
+ return `${projector.name}@${projector.version}`;
9
+ }
10
+ class ProjectorDeliveryError extends Data.TaggedError('ProjectorDeliveryError') {
11
+ }
12
+ function normalizeError(cause) {
13
+ return new ProjectorDeliveryError({
14
+ message: cause instanceof Error ? cause.message : String(cause),
15
+ });
16
+ }
17
+ export function createProjectorDelivery(source, projectors, cursors, options = {}) {
18
+ const batchSize = options.batchSize ?? 100;
19
+ const initialRetryMs = options.initialRetryMs ?? 1_000;
20
+ const maxRetryMs = options.maxRetryMs ?? 60_000;
21
+ const maxAttempts = options.maxAttempts ?? 8;
22
+ const now = options.now ?? Date.now;
23
+ const initialized = new Map();
24
+ const exclusive = new Map();
25
+ const observe = (event) => {
26
+ try {
27
+ Promise.resolve(options.observer?.observe(event)).catch(() => undefined);
28
+ }
29
+ catch {
30
+ // Delivery correctness must not depend on telemetry.
31
+ }
32
+ };
33
+ const initialize = (projector) => {
34
+ const key = projectorIdentity(projector);
35
+ let pending = initialized.get(key);
36
+ if (!pending) {
37
+ pending = Promise.resolve(projector.init?.()).catch((cause) => {
38
+ initialized.delete(key);
39
+ throw cause;
40
+ });
41
+ initialized.set(key, pending);
42
+ }
43
+ return pending;
44
+ };
45
+ const scheduleRetry = (at) => options.scheduleRetry?.(at) ?? Effect.void;
46
+ const recordFailure = (runId, projector, status, failedSeq, error) => Effect.gen(function* () {
47
+ const attempts = status.attempts + 1;
48
+ const deadLettered = attempts >= maxAttempts;
49
+ const nextRetryAt = deadLettered
50
+ ? null
51
+ : now() + Math.min(maxRetryMs, initialRetryMs * 2 ** Math.max(0, attempts - 1));
52
+ yield* cursors.fail(runId, projector, {
53
+ attempts,
54
+ failedSeq,
55
+ nextRetryAt,
56
+ error: error.message,
57
+ now: now(),
58
+ });
59
+ const sourceTail = yield* source.tail(runId).pipe(Effect.orElseSucceed(() => status.cursor));
60
+ observe({
61
+ type: deadLettered ? 'projector.dead-letter' : 'projector.retry',
62
+ runId,
63
+ projector: projectorIdentity(projector),
64
+ at: now(),
65
+ cursor: status.cursor,
66
+ lag: Math.max(0, sourceTail - status.cursor),
67
+ attempts,
68
+ error: error.message,
69
+ });
70
+ if (nextRetryAt !== null) {
71
+ yield* scheduleRetry(nextRetryAt);
72
+ }
73
+ });
74
+ const deliverProjector = (runId, projector, force) => Effect.gen(function* () {
75
+ let status = yield* cursors.get(runId, projector);
76
+ const currentTime = now();
77
+ if (status.state === 'dead-letter' ||
78
+ (!force && status.nextRetryAt !== null && status.nextRetryAt > currentTime)) {
79
+ return;
80
+ }
81
+ const initialization = yield* Effect.tryPromise({
82
+ try: () => initialize(projector),
83
+ catch: normalizeError,
84
+ }).pipe(Effect.exit);
85
+ if (initialization._tag === 'Failure') {
86
+ yield* recordFailure(runId, projector, status, status.cursor + 1, normalizeError(initialization.cause));
87
+ return;
88
+ }
89
+ while (true) {
90
+ const events = yield* source.read(runId, {
91
+ fromSeq: status.cursor + 1,
92
+ limit: batchSize,
93
+ });
94
+ const first = events[0];
95
+ const last = events.at(-1);
96
+ if (first === undefined || last === undefined) {
97
+ return;
98
+ }
99
+ const projected = yield* runProjectorEffect(projectorIdentity(projector), () => projector.project(events)).pipe(Effect.exit);
100
+ if (projected._tag === 'Failure') {
101
+ yield* recordFailure(runId, projector, status, first.seq, normalizeError(projected.cause));
102
+ return;
103
+ }
104
+ const advancedAt = now();
105
+ yield* cursors.advance(runId, projector, last.seq, advancedAt);
106
+ status = {
107
+ ...status,
108
+ cursor: last.seq,
109
+ state: 'idle',
110
+ attempts: 0,
111
+ failedSeq: null,
112
+ nextRetryAt: null,
113
+ lastError: null,
114
+ };
115
+ const sourceTail = yield* source.tail(runId).pipe(Effect.orElseSucceed(() => last.seq));
116
+ observe({
117
+ type: 'projector.lag',
118
+ runId,
119
+ projector: projectorIdentity(projector),
120
+ at: advancedAt,
121
+ cursor: last.seq,
122
+ lag: Math.max(0, sourceTail - last.seq),
123
+ attempts: 0,
124
+ });
125
+ }
126
+ });
127
+ /** One delivery at a time per run and projector; independent projectors still run in parallel. */
128
+ const deliverExclusive = (runId, projector, force) => {
129
+ const key = `${runId}:${projectorIdentity(projector)}`;
130
+ let semaphore = exclusive.get(key);
131
+ if (!semaphore) {
132
+ semaphore = Semaphore.makeUnsafe(1);
133
+ exclusive.set(key, semaphore);
134
+ }
135
+ return Semaphore.withPermits(semaphore, 1, deliverProjector(runId, projector, force));
136
+ };
137
+ const findProjector = (name, version) => projectors.find((candidate) => candidate.name === name && (version === undefined || candidate.version === version));
138
+ return {
139
+ deliver: (runId, runOptions) => Effect.forEach(projectors, (projector) => deliverExclusive(runId, projector, runOptions?.force ?? false), { concurrency: 'unbounded', discard: true }),
140
+ status: (runId) => cursors.list(runId),
141
+ requeue: (runId, projectorName, version) => {
142
+ const projector = findProjector(projectorName, version);
143
+ if (!projector) {
144
+ return Effect.fail(new UnknownProjectorError({
145
+ message: `Unknown projector ${projectorName}${version === undefined ? '' : `@${version}`}`,
146
+ }));
147
+ }
148
+ return cursors
149
+ .requeue(runId, projector, now())
150
+ .pipe(Effect.andThen(deliverExclusive(runId, projector, true)));
151
+ },
152
+ nextRetryAt: (runId) => cursors.list(runId).pipe(Effect.map((statuses) => {
153
+ const deadlines = statuses
154
+ .map((status) => status.nextRetryAt)
155
+ .filter((deadline) => deadline !== null);
156
+ return deadlines.length === 0 ? null : Math.min(...deadlines);
157
+ })),
158
+ inspect: (runId) => cursors.list(runId).pipe(Effect.flatMap((statuses) => Effect.forEach(statuses, (status) => source.tail(status.runId).pipe(Effect.map((sourceTail) => ({
159
+ ...status,
160
+ sourceTail,
161
+ lag: Math.max(0, sourceTail - status.cursor),
162
+ }))), { concurrency: 'unbounded' }))),
163
+ recover: (runId) => Effect.gen(function* () {
164
+ const statuses = yield* cursors.list(runId);
165
+ let recovered = 0;
166
+ for (const status of statuses) {
167
+ if (status.state === 'dead-letter' || status.nextRetryAt === null) {
168
+ continue;
169
+ }
170
+ yield* scheduleRetry(status.nextRetryAt);
171
+ if (status.nextRetryAt <= now()) {
172
+ const projector = findProjector(status.projector, status.version);
173
+ if (projector) {
174
+ yield* deliverExclusive(status.runId, projector, true);
175
+ }
176
+ }
177
+ recovered += 1;
178
+ }
179
+ return recovered;
180
+ }),
181
+ };
182
+ }
@@ -53,4 +53,4 @@ export interface IndexProjector extends Projector {
53
53
  listReviews: (actorId?: string) => Promise<ReviewIndexRow[]>;
54
54
  }
55
55
  export declare function indexOpsFor(event: EventEnvelope): IndexOp[];
56
- export declare function createIndexProjector(name: string, backend: IndexBackend): IndexProjector;
56
+ export declare function createIndexProjector(name: string, backend: IndexBackend, version?: string): IndexProjector;
@@ -99,17 +99,15 @@ export function indexOpsFor(event) {
99
99
  return [];
100
100
  }
101
101
  }
102
- export function createIndexProjector(name, backend) {
102
+ export function createIndexProjector(name, backend, version = '1') {
103
103
  return {
104
104
  name,
105
+ version,
105
106
  init: backend.init,
106
107
  dispose: backend.dispose,
107
- project: async (events) => {
108
+ project: (events) => {
108
109
  const ops = events.flatMap(indexOpsFor);
109
- if (ops.length === 0) {
110
- return;
111
- }
112
- await backend.applyOps(ops);
110
+ return ops.length === 0 ? Promise.resolve() : backend.applyOps(ops);
113
111
  },
114
112
  getActor: (actorId) => backend.getActor(actorId),
115
113
  listReviews: (actorId) => backend.listReviews(actorId),
@@ -1,4 +1,6 @@
1
1
  export type { Projector, ProjectorErrorHandler } from './projector';
2
+ export { createProjectorDelivery, ProjectorCursorError, UnknownProjectorError, type ProjectorCursorStore, type ProjectorDeliveryFailure, type ProjectorDelivery, type ProjectorDeliveryOptions, type ProjectorDeliveryState, type ProjectorDeliveryStatus, type ProjectorObservation, type ProjectorObserver, type ProjectorOperationalStatus, } from './delivery';
3
+ export { sqliteProjectorCursorStore, SQLITE_PROJECTOR_DELIVERY_SCHEMA } from './sqlite-delivery';
2
4
  export { withProjectors, projectEvents, ProjectorsStoreLive, type WithProjectorsOptions, } from './with-projectors';
3
5
  export { indexOpsFor, createIndexProjector, type IndexOp, type IndexBackend, type IndexProjector, type ActorIndexRow, type ReviewIndexRow, } from './index-model';
4
6
  export { memory, noop } from './memory';
@@ -1,3 +1,5 @@
1
+ export { createProjectorDelivery, ProjectorCursorError, UnknownProjectorError, } from './delivery.js';
2
+ export { sqliteProjectorCursorStore, SQLITE_PROJECTOR_DELIVERY_SCHEMA } from './sqlite-delivery.js';
1
3
  export { withProjectors, projectEvents, ProjectorsStoreLive, } from './with-projectors.js';
2
4
  export { indexOpsFor, createIndexProjector, } from './index-model.js';
3
5
  export { memory, noop } from './memory.js';
@@ -1,5 +1,5 @@
1
1
  import { type IndexProjector } from './index-model';
2
- import type { Projector } from './projector';
2
+ import { type Projector } from './projector';
3
3
  export declare function memory(): IndexProjector;
4
4
  /** No-op projector — safe default when indexing is disabled. */
5
5
  export declare function noop(): Projector;
@@ -1,4 +1,5 @@
1
1
  import { createIndexProjector, } from './index-model.js';
2
+ import { runProjectorSync } from './projector.js';
2
3
  function applyOp(actors, reviews, op) {
3
4
  switch (op.type) {
4
5
  case 'upsertActor':
@@ -51,20 +52,20 @@ export function memory() {
51
52
  const actors = new Map();
52
53
  const reviews = new Map();
53
54
  const backend = {
54
- applyOps: async (ops) => {
55
+ applyOps: (ops) => runProjectorSync('memory', () => {
55
56
  for (const op of ops) {
56
57
  applyOp(actors, reviews, op);
57
58
  }
58
- },
59
- getActor: async (actorId) => actors.get(actorId) ?? null,
60
- listReviews: async (actorId) => {
59
+ }),
60
+ getActor: (actorId) => runProjectorSync('memory', () => actors.get(actorId) ?? null),
61
+ listReviews: (actorId) => runProjectorSync('memory', () => {
61
62
  const rows = [...reviews.values()];
62
63
  return actorId ? rows.filter((row) => row.actorId === actorId) : rows;
63
- },
64
- dispose: async () => {
64
+ }),
65
+ dispose: () => runProjectorSync('memory', () => {
65
66
  actors.clear();
66
67
  reviews.clear();
67
- },
68
+ }),
68
69
  };
69
70
  return createIndexProjector('memory', backend);
70
71
  }
@@ -72,6 +73,7 @@ export function memory() {
72
73
  export function noop() {
73
74
  return {
74
75
  name: 'noop',
75
- project: async () => { },
76
+ version: '1',
77
+ project: () => Promise.resolve(),
76
78
  };
77
79
  }
@@ -1,16 +1,19 @@
1
+ import { DateTime, Effect } from 'effect';
1
2
  import createPostgresClient, {} from 'postgres';
2
3
  import { createIndexProjector, } from './index-model.js';
4
+ import { runProjectorPromise } from './projector.js';
3
5
  import { POSTGRES_SCHEMA_SQL } from './schema.js';
4
6
  export { POSTGRES_SCHEMA_SQL } from './schema.js';
5
7
  function ownsClient(options) {
6
8
  return 'url' in options;
7
9
  }
8
- async function applyOp(sql, op) {
10
+ function applyOp(sql, op) {
11
+ const date = DateTime.toDateUtc(DateTime.makeUnsafe(op.updatedAt));
9
12
  switch (op.type) {
10
13
  case 'upsertActor':
11
- await sql `
14
+ return sql `
12
15
  INSERT INTO looms_actors (actor_id, kind, status, definition_name, parent_actor_id, updated_at)
13
- VALUES (${op.actorId}, ${op.kind}, ${op.status}, ${op.definitionName}, ${op.parentActorId}, ${new Date(op.updatedAt)})
16
+ VALUES (${op.actorId}, ${op.kind}, ${op.status}, ${op.definitionName}, ${op.parentActorId}, ${date})
14
17
  ON CONFLICT (actor_id) DO UPDATE SET
15
18
  kind = EXCLUDED.kind,
16
19
  status = EXCLUDED.status,
@@ -18,31 +21,27 @@ async function applyOp(sql, op) {
18
21
  parent_actor_id = EXCLUDED.parent_actor_id,
19
22
  updated_at = EXCLUDED.updated_at
20
23
  `;
21
- return;
22
24
  case 'setActorStatus':
23
- await sql `
25
+ return sql `
24
26
  INSERT INTO looms_actors (actor_id, status, updated_at)
25
- VALUES (${op.actorId}, ${op.status}, ${new Date(op.updatedAt)})
27
+ VALUES (${op.actorId}, ${op.status}, ${date})
26
28
  ON CONFLICT (actor_id) DO UPDATE SET
27
29
  status = EXCLUDED.status,
28
30
  updated_at = EXCLUDED.updated_at
29
31
  `;
30
- return;
31
32
  case 'upsertReview':
32
- await sql `
33
+ return sql `
33
34
  INSERT INTO looms_reviews (review_id, actor_id, status, title, updated_at)
34
- VALUES (${op.reviewId}, ${op.actorId}, ${op.status}, ${op.title}, ${new Date(op.updatedAt)})
35
+ VALUES (${op.reviewId}, ${op.actorId}, ${op.status}, ${op.title}, ${date})
35
36
  ON CONFLICT (review_id) DO UPDATE SET
36
37
  status = EXCLUDED.status,
37
38
  updated_at = EXCLUDED.updated_at
38
39
  `;
39
- return;
40
40
  case 'setReviewStatus':
41
- await sql `
42
- UPDATE looms_reviews SET status = ${op.status}, updated_at = ${new Date(op.updatedAt)}
41
+ return sql `
42
+ UPDATE looms_reviews SET status = ${op.status}, updated_at = ${date}
43
43
  WHERE review_id = ${op.reviewId}
44
44
  `;
45
- return;
46
45
  default: {
47
46
  const exhaustiveCheck = op;
48
47
  return exhaustiveCheck;
@@ -53,56 +52,43 @@ export function postgres(options) {
53
52
  const createdClient = ownsClient(options);
54
53
  const sql = createdClient ? createPostgresClient(options.url) : options.sql;
55
54
  const backend = {
56
- init: async () => {
57
- await sql.unsafe(POSTGRES_SCHEMA_SQL);
58
- },
59
- applyOps: async (ops) => {
60
- await sql.begin(async (tx) => {
61
- for (const op of ops) {
62
- await applyOp(tx, op);
63
- }
64
- });
65
- },
66
- getActor: async (actorId) => {
67
- const rows = await sql `
68
- SELECT actor_id, kind, status, definition_name, parent_actor_id, updated_at
69
- FROM looms_actors WHERE actor_id = ${actorId}
70
- `;
55
+ init: () => runProjectorPromise('postgres', () => sql.unsafe(POSTGRES_SCHEMA_SQL)).then(() => undefined),
56
+ applyOps: (ops) => runProjectorPromise('postgres', () => sql.begin((tx) => Effect.runPromise(Effect.forEach(ops, (op) => Effect.promise(() => Promise.resolve(applyOp(tx, op))), {
57
+ discard: true,
58
+ })))).then(() => undefined),
59
+ getActor: (actorId) => runProjectorPromise('postgres', () => sql `
60
+ SELECT actor_id, kind, status, definition_name, parent_actor_id, updated_at
61
+ FROM looms_actors WHERE actor_id = ${actorId}
62
+ `).then((rows) => {
71
63
  const row = rows[0];
72
- if (!row) {
73
- return null;
74
- }
75
- return {
76
- actorId: row.actor_id,
77
- kind: row.kind,
78
- status: row.status,
79
- definitionName: row.definition_name,
80
- parentActorId: row.parent_actor_id,
81
- updatedAt: row.updated_at.getTime(),
82
- };
83
- },
84
- listReviews: async (actorId) => {
85
- const rows = actorId
86
- ? await sql `
64
+ return row
65
+ ? {
66
+ actorId: row.actor_id,
67
+ kind: row.kind,
68
+ status: row.status,
69
+ definitionName: row.definition_name,
70
+ parentActorId: row.parent_actor_id,
71
+ updatedAt: row.updated_at.getTime(),
72
+ }
73
+ : null;
74
+ }),
75
+ listReviews: (actorId) => runProjectorPromise('postgres', () => actorId
76
+ ? sql `
87
77
  SELECT review_id, actor_id, status, title, updated_at
88
78
  FROM looms_reviews WHERE actor_id = ${actorId}
89
79
  `
90
- : await sql `
80
+ : sql `
91
81
  SELECT review_id, actor_id, status, title, updated_at FROM looms_reviews
92
- `;
93
- return rows.map((row) => ({
94
- reviewId: row.review_id,
95
- actorId: row.actor_id,
96
- status: row.status,
97
- title: row.title,
98
- updatedAt: row.updated_at.getTime(),
99
- }));
100
- },
101
- dispose: async () => {
102
- if (createdClient) {
103
- await sql.end({ timeout: 5 });
104
- }
105
- },
82
+ `).then((rows) => rows.map((row) => ({
83
+ reviewId: row.review_id,
84
+ actorId: row.actor_id,
85
+ status: row.status,
86
+ title: row.title,
87
+ updatedAt: row.updated_at.getTime(),
88
+ }))),
89
+ dispose: () => createdClient
90
+ ? runProjectorPromise('postgres', () => sql.end({ timeout: 5 })).then(() => undefined)
91
+ : Promise.resolve(),
106
92
  };
107
93
  return createIndexProjector('postgres', backend);
108
94
  }
@@ -1,7 +1,23 @@
1
+ import { Effect } from 'effect';
1
2
  import type { EventEnvelope } from '@swirls/looms/core';
3
+ declare const ProjectorError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
4
+ readonly _tag: "ProjectorError";
5
+ } & Readonly<A>;
6
+ export declare class ProjectorError extends ProjectorError_base<{
7
+ readonly projector: string;
8
+ readonly cause: unknown;
9
+ readonly message: string;
10
+ }> {
11
+ constructor(projector: string, cause: unknown);
12
+ }
13
+ export declare function runProjectorEffect<A>(projector: string, operation: () => PromiseLike<A>): Effect.Effect<A, ProjectorError>;
14
+ export declare function runProjectorSync<A>(projector: string, operation: () => A): Promise<A>;
15
+ export declare function runProjectorPromise<A>(projector: string, operation: () => PromiseLike<A>): Promise<A>;
2
16
  export interface Projector {
3
17
  /** Used in error reporting / diagnostics. */
4
18
  readonly name: string;
19
+ /** Changes when projection semantics or its target schema changes. */
20
+ readonly version: string;
5
21
  /** Optional one-time setup (open connection, create tables). Runtime calls this before the first `project`. */
6
22
  readonly init?: () => Promise<void>;
7
23
  /** Apply a batch of events in log order. Must be idempotent (a batch may be redelivered). */
@@ -9,3 +25,4 @@ export interface Projector {
9
25
  readonly dispose?: () => Promise<void>;
10
26
  }
11
27
  export type ProjectorErrorHandler = (error: Error, projector: Projector, events: ReadonlyArray<EventEnvelope>) => void;
28
+ export {};
@@ -1 +1,26 @@
1
- export {};
1
+ import { Data, Effect } from 'effect';
2
+ export class ProjectorError extends Data.TaggedError('ProjectorError') {
3
+ constructor(projector, cause) {
4
+ super({
5
+ projector,
6
+ cause,
7
+ message: cause instanceof Error ? cause.message : String(cause),
8
+ });
9
+ this.name = 'ProjectorError';
10
+ }
11
+ }
12
+ export function runProjectorEffect(projector, operation) {
13
+ return Effect.tryPromise({
14
+ try: () => Promise.resolve(operation()),
15
+ catch: (cause) => new ProjectorError(projector, cause),
16
+ });
17
+ }
18
+ export function runProjectorSync(projector, operation) {
19
+ return Effect.runPromise(Effect.try({
20
+ try: operation,
21
+ catch: (cause) => new ProjectorError(projector, cause),
22
+ }));
23
+ }
24
+ export function runProjectorPromise(projector, operation) {
25
+ return Effect.runPromise(runProjectorEffect(projector, operation));
26
+ }
@@ -0,0 +1,4 @@
1
+ import type { SqliteExec } from '@swirls/looms/core/sqlite-store';
2
+ import { type ProjectorCursorStore } from './delivery';
3
+ export declare const SQLITE_PROJECTOR_DELIVERY_SCHEMA: string;
4
+ export declare function sqliteProjectorCursorStore(exec: SqliteExec): ProjectorCursorStore;
@@ -0,0 +1,147 @@
1
+ import { Effect, Schema } from 'effect';
2
+ import { ProjectorCursorError, } from './delivery.js';
3
+ export const SQLITE_PROJECTOR_DELIVERY_SCHEMA = `
4
+ CREATE TABLE IF NOT EXISTS looms_projector_delivery (
5
+ run_id TEXT NOT NULL,
6
+ projector_name TEXT NOT NULL,
7
+ projector_version TEXT NOT NULL,
8
+ cursor INTEGER NOT NULL DEFAULT 0,
9
+ state TEXT NOT NULL DEFAULT 'idle',
10
+ attempts INTEGER NOT NULL DEFAULT 0,
11
+ failed_seq INTEGER,
12
+ next_retry_at INTEGER,
13
+ last_error TEXT,
14
+ updated_at INTEGER NOT NULL,
15
+ PRIMARY KEY (run_id, projector_name, projector_version)
16
+ );
17
+ CREATE INDEX IF NOT EXISTS looms_projector_delivery_retry_idx
18
+ ON looms_projector_delivery (next_retry_at)
19
+ `.trim();
20
+ const DeliveryRowSchema = Schema.Struct({
21
+ run_id: Schema.String,
22
+ projector_name: Schema.String,
23
+ projector_version: Schema.String,
24
+ cursor: Schema.Finite,
25
+ state: Schema.String,
26
+ attempts: Schema.Finite,
27
+ failed_seq: Schema.NullOr(Schema.Finite),
28
+ next_retry_at: Schema.NullOr(Schema.Finite),
29
+ last_error: Schema.NullOr(Schema.String),
30
+ updated_at: Schema.Finite,
31
+ });
32
+ const decodeDeliveryRow = Schema.decodeUnknownSync(DeliveryRowSchema);
33
+ function parseState(value) {
34
+ switch (value) {
35
+ case 'idle':
36
+ case 'retrying':
37
+ case 'dead-letter':
38
+ return value;
39
+ default:
40
+ throw new Error(`Invalid projector delivery state: ${value}`);
41
+ }
42
+ }
43
+ function deliveryStatus(row) {
44
+ return {
45
+ runId: row.run_id,
46
+ projector: row.projector_name,
47
+ version: row.projector_version,
48
+ cursor: row.cursor,
49
+ state: parseState(row.state),
50
+ attempts: row.attempts,
51
+ failedSeq: row.failed_seq,
52
+ nextRetryAt: row.next_retry_at,
53
+ lastError: row.last_error,
54
+ updatedAt: row.updated_at,
55
+ };
56
+ }
57
+ function initialize(exec) {
58
+ for (const statement of SQLITE_PROJECTOR_DELIVERY_SCHEMA.split(';')) {
59
+ const sql = statement.trim();
60
+ if (sql.length > 0) {
61
+ exec.run(sql);
62
+ }
63
+ }
64
+ }
65
+ function query(run) {
66
+ return Effect.try({
67
+ try: run,
68
+ catch: (cause) => new ProjectorCursorError({
69
+ message: cause instanceof Error ? cause.message : 'Projector cursor query failed',
70
+ }),
71
+ });
72
+ }
73
+ export function sqliteProjectorCursorStore(exec) {
74
+ initialize(exec);
75
+ const get = (runId, projector) => {
76
+ const [row] = exec.rows(`SELECT run_id, projector_name, projector_version, cursor, state, attempts,
77
+ failed_seq, next_retry_at, last_error, updated_at
78
+ FROM looms_projector_delivery
79
+ WHERE run_id = ? AND projector_name = ? AND projector_version = ?`, [runId, projector.name, projector.version]);
80
+ return row === undefined
81
+ ? {
82
+ runId,
83
+ projector: projector.name,
84
+ version: projector.version,
85
+ cursor: 0,
86
+ state: 'idle',
87
+ attempts: 0,
88
+ failedSeq: null,
89
+ nextRetryAt: null,
90
+ lastError: null,
91
+ updatedAt: 0,
92
+ }
93
+ : deliveryStatus(decodeDeliveryRow(row));
94
+ };
95
+ return {
96
+ get: (runId, projector) => query(() => get(runId, projector)),
97
+ advance: (runId, projector, cursor, now) => query(() => {
98
+ exec.run(`INSERT INTO looms_projector_delivery
99
+ (run_id, projector_name, projector_version, cursor, updated_at)
100
+ VALUES (?, ?, ?, ?, ?)
101
+ ON CONFLICT (run_id, projector_name, projector_version) DO UPDATE
102
+ SET cursor = MAX(cursor, excluded.cursor), state = 'idle', attempts = 0,
103
+ failed_seq = NULL, next_retry_at = NULL, last_error = NULL,
104
+ updated_at = excluded.updated_at`, [runId, projector.name, projector.version, cursor, now]);
105
+ }),
106
+ fail: (runId, projector, failure) => query(() => {
107
+ exec.run(`INSERT INTO looms_projector_delivery
108
+ (run_id, projector_name, projector_version, state, attempts, failed_seq,
109
+ next_retry_at, last_error, updated_at)
110
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
111
+ ON CONFLICT (run_id, projector_name, projector_version) DO UPDATE
112
+ SET state = excluded.state, attempts = excluded.attempts,
113
+ failed_seq = excluded.failed_seq, next_retry_at = excluded.next_retry_at,
114
+ last_error = excluded.last_error, updated_at = excluded.updated_at
115
+ WHERE cursor < excluded.failed_seq`, [
116
+ runId,
117
+ projector.name,
118
+ projector.version,
119
+ failure.nextRetryAt === null ? 'dead-letter' : 'retrying',
120
+ failure.attempts,
121
+ failure.failedSeq,
122
+ failure.nextRetryAt,
123
+ failure.error,
124
+ failure.now,
125
+ ]);
126
+ }),
127
+ requeue: (runId, projector, now) => query(() => {
128
+ exec.run(`INSERT INTO looms_projector_delivery
129
+ (run_id, projector_name, projector_version, state, next_retry_at, updated_at)
130
+ VALUES (?, ?, ?, 'retrying', ?, ?)
131
+ ON CONFLICT (run_id, projector_name, projector_version) DO UPDATE
132
+ SET state = 'retrying', attempts = 0, next_retry_at = excluded.next_retry_at,
133
+ last_error = NULL, updated_at = excluded.updated_at`, [runId, projector.name, projector.version, now, now]);
134
+ }),
135
+ list: (runId) => {
136
+ const params = runId === undefined ? [] : [runId];
137
+ const where = runId === undefined ? '' : 'WHERE run_id = ?';
138
+ return query(() => exec
139
+ .rows(`SELECT run_id, projector_name, projector_version, cursor, state, attempts,
140
+ failed_seq, next_retry_at, last_error, updated_at
141
+ FROM looms_projector_delivery ${where}
142
+ ORDER BY run_id, projector_name, projector_version`, params)
143
+ .map((row) => decodeDeliveryRow(row))
144
+ .map(deliveryStatus));
145
+ },
146
+ };
147
+ }