@rikalabs/akter 0.1.0-alpha.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 (603) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +8 -0
  4. package/README.md +116 -0
  5. package/dist/actor/codecs.d.ts +77 -0
  6. package/dist/actor/codecs.js +87 -0
  7. package/dist/actor/connections.d.ts +34 -0
  8. package/dist/actor/connections.js +145 -0
  9. package/dist/actor/definition.d.ts +893 -0
  10. package/dist/actor/definition.js +147 -0
  11. package/dist/actor/descriptor.d.ts +172 -0
  12. package/dist/actor/descriptor.js +367 -0
  13. package/dist/actor/handles.d.ts +77 -0
  14. package/dist/actor/handles.js +265 -0
  15. package/dist/actor/jobs.d.ts +10 -0
  16. package/dist/actor/jobs.js +137 -0
  17. package/dist/actor/layers.d.ts +41 -0
  18. package/dist/actor/layers.js +177 -0
  19. package/dist/actor/reads.d.ts +47 -0
  20. package/dist/actor/reads.js +224 -0
  21. package/dist/actor/served.d.ts +120 -0
  22. package/dist/actor/served.js +72 -0
  23. package/dist/actor/turns.d.ts +13 -0
  24. package/dist/actor/turns.js +332 -0
  25. package/dist/actor/workflows.d.ts +16 -0
  26. package/dist/actor/workflows.js +37 -0
  27. package/dist/client/calls.d.ts +47 -0
  28. package/dist/client/calls.js +425 -0
  29. package/dist/client/clock.d.ts +56 -0
  30. package/dist/client/clock.js +119 -0
  31. package/dist/client/fleet-page.d.ts +15 -0
  32. package/dist/client/fleet-page.js +13 -0
  33. package/dist/client/fleet.d.ts +29 -0
  34. package/dist/client/fleet.js +51 -0
  35. package/dist/client/index.d.ts +17 -0
  36. package/dist/client/index.js +6 -0
  37. package/dist/client/make.d.ts +157 -0
  38. package/dist/client/make.js +221 -0
  39. package/dist/client/offline/queue.d.ts +118 -0
  40. package/dist/client/offline/queue.js +292 -0
  41. package/dist/client/offline/store.d.ts +74 -0
  42. package/dist/client/offline/store.js +78 -0
  43. package/dist/client/optimistic.d.ts +57 -0
  44. package/dist/client/optimistic.js +136 -0
  45. package/dist/client/report.d.ts +2 -0
  46. package/dist/client/report.js +8 -0
  47. package/dist/client/sessions/connection.d.ts +118 -0
  48. package/dist/client/sessions/connection.js +162 -0
  49. package/dist/client/sessions/feed.d.ts +42 -0
  50. package/dist/client/sessions/feed.js +43 -0
  51. package/dist/client/sessions/sse.d.ts +47 -0
  52. package/dist/client/sessions/sse.js +112 -0
  53. package/dist/client/sessions/stream.d.ts +28 -0
  54. package/dist/client/sessions/stream.js +35 -0
  55. package/dist/client/sessions/watch.d.ts +34 -0
  56. package/dist/client/sessions/watch.js +40 -0
  57. package/dist/client/transport.d.ts +37 -0
  58. package/dist/client/transport.js +66 -0
  59. package/dist/contexts/command.d.ts +132 -0
  60. package/dist/contexts/command.js +20 -0
  61. package/dist/contexts/connection.d.ts +68 -0
  62. package/dist/contexts/connection.js +0 -0
  63. package/dist/contexts/job.d.ts +49 -0
  64. package/dist/contexts/job.js +0 -0
  65. package/dist/contexts/workflow.d.ts +147 -0
  66. package/dist/contexts/workflow.js +22 -0
  67. package/dist/errors/actor.d.ts +200 -0
  68. package/dist/errors/actor.js +271 -0
  69. package/dist/errors/content.d.ts +19 -0
  70. package/dist/errors/content.js +14 -0
  71. package/dist/errors/database.d.ts +22 -0
  72. package/dist/errors/database.js +23 -0
  73. package/dist/errors/events.d.ts +17 -0
  74. package/dist/errors/events.js +14 -0
  75. package/dist/errors/subscription.d.ts +12 -0
  76. package/dist/errors/subscription.js +8 -0
  77. package/dist/errors/workflow.d.ts +28 -0
  78. package/dist/errors/workflow.js +17 -0
  79. package/dist/handles/actors.d.ts +17 -0
  80. package/dist/handles/actors.js +7 -0
  81. package/dist/handles/content.d.ts +59 -0
  82. package/dist/handles/content.js +36 -0
  83. package/dist/handles/intents.d.ts +164 -0
  84. package/dist/handles/intents.js +214 -0
  85. package/dist/handles/run.d.ts +31 -0
  86. package/dist/handles/run.js +21 -0
  87. package/dist/handles/workflow.d.ts +27 -0
  88. package/dist/handles/workflow.js +23 -0
  89. package/dist/identity/caller.d.ts +105 -0
  90. package/dist/identity/caller.js +68 -0
  91. package/dist/identity/child.d.ts +18 -0
  92. package/dist/identity/child.js +39 -0
  93. package/dist/identity/command.d.ts +19 -0
  94. package/dist/identity/command.js +21 -0
  95. package/dist/identity/connection.d.ts +24 -0
  96. package/dist/identity/connection.js +29 -0
  97. package/dist/identity/content.d.ts +18 -0
  98. package/dist/identity/content.js +12 -0
  99. package/dist/identity/digest.d.ts +4 -0
  100. package/dist/identity/digest.js +5 -0
  101. package/dist/identity/execution.d.ts +32 -0
  102. package/dist/identity/execution.js +63 -0
  103. package/dist/identity/mint.d.ts +51 -0
  104. package/dist/identity/mint.js +76 -0
  105. package/dist/identity/version.d.ts +2 -0
  106. package/dist/identity/version.js +3 -0
  107. package/dist/index.d.ts +159 -0
  108. package/dist/index.js +97 -0
  109. package/dist/members/blob.d.ts +43 -0
  110. package/dist/members/blob.js +29 -0
  111. package/dist/members/command.d.ts +109 -0
  112. package/dist/members/command.js +61 -0
  113. package/dist/members/connection.d.ts +55 -0
  114. package/dist/members/connection.js +49 -0
  115. package/dist/members/event.d.ts +36 -0
  116. package/dist/members/event.js +24 -0
  117. package/dist/members/job.d.ts +157 -0
  118. package/dist/members/job.js +67 -0
  119. package/dist/members/payload.d.ts +93 -0
  120. package/dist/members/payload.js +145 -0
  121. package/dist/members/reducer.d.ts +64 -0
  122. package/dist/members/reducer.js +27 -0
  123. package/dist/members/stream.d.ts +36 -0
  124. package/dist/members/stream.js +14 -0
  125. package/dist/members/subscription.d.ts +182 -0
  126. package/dist/members/subscription.js +91 -0
  127. package/dist/members/workflow.d.ts +99 -0
  128. package/dist/members/workflow.js +2 -0
  129. package/dist/policies/access.d.ts +35 -0
  130. package/dist/policies/access.js +6 -0
  131. package/dist/policies/command.d.ts +127 -0
  132. package/dist/policies/command.js +45 -0
  133. package/dist/policies/job.d.ts +28 -0
  134. package/dist/policies/job.js +60 -0
  135. package/dist/policies/schedules.d.ts +33 -0
  136. package/dist/policies/schedules.js +131 -0
  137. package/dist/protocol/frames.d.ts +66 -0
  138. package/dist/protocol/frames.js +55 -0
  139. package/dist/protocol/inspection.d.ts +807 -0
  140. package/dist/protocol/inspection.js +243 -0
  141. package/dist/protocol/wire.d.ts +184 -0
  142. package/dist/protocol/wire.js +150 -0
  143. package/dist/runtime/actors.d.ts +155 -0
  144. package/dist/runtime/actors.js +4 -0
  145. package/dist/runtime/admission.d.ts +34 -0
  146. package/dist/runtime/admission.js +72 -0
  147. package/dist/runtime/adoption/backfill.d.ts +27 -0
  148. package/dist/runtime/adoption/backfill.js +144 -0
  149. package/dist/runtime/adoption/enforce.d.ts +55 -0
  150. package/dist/runtime/adoption/enforce.js +241 -0
  151. package/dist/runtime/adoption/observe.d.ts +43 -0
  152. package/dist/runtime/adoption/observe.js +139 -0
  153. package/dist/runtime/adoption/plan.d.ts +45 -0
  154. package/dist/runtime/adoption/plan.js +192 -0
  155. package/dist/runtime/adoption/startup.d.ts +32 -0
  156. package/dist/runtime/adoption/startup.js +76 -0
  157. package/dist/runtime/adoption/status.d.ts +23 -0
  158. package/dist/runtime/adoption/status.js +47 -0
  159. package/dist/runtime/adoption/target.d.ts +33 -0
  160. package/dist/runtime/adoption/target.js +49 -0
  161. package/dist/runtime/connections/holder.d.ts +221 -0
  162. package/dist/runtime/connections/holder.js +816 -0
  163. package/dist/runtime/connections/owner.d.ts +263 -0
  164. package/dist/runtime/connections/owner.js +896 -0
  165. package/dist/runtime/connections/protocol.d.ts +441 -0
  166. package/dist/runtime/connections/protocol.js +222 -0
  167. package/dist/runtime/connections/reads.d.ts +24 -0
  168. package/dist/runtime/connections/reads.js +19 -0
  169. package/dist/runtime/connections/streams.d.ts +62 -0
  170. package/dist/runtime/connections/streams.js +128 -0
  171. package/dist/runtime/connections/transport.d.ts +40 -0
  172. package/dist/runtime/connections/transport.js +68 -0
  173. package/dist/runtime/connections/watch.d.ts +52 -0
  174. package/dist/runtime/connections/watch.js +159 -0
  175. package/dist/runtime/content/grant.d.ts +34 -0
  176. package/dist/runtime/content/grant.js +58 -0
  177. package/dist/runtime/content/store.d.ts +63 -0
  178. package/dist/runtime/content/store.js +294 -0
  179. package/dist/runtime/cron/key.d.ts +2 -0
  180. package/dist/runtime/cron/key.js +2 -0
  181. package/dist/runtime/cron/schedule.d.ts +69 -0
  182. package/dist/runtime/cron/schedule.js +144 -0
  183. package/dist/runtime/database/bounded.d.ts +43 -0
  184. package/dist/runtime/database/bounded.js +108 -0
  185. package/dist/runtime/database/connection.d.ts +8 -0
  186. package/dist/runtime/database/connection.js +18 -0
  187. package/dist/runtime/database/coordination.d.ts +19 -0
  188. package/dist/runtime/database/coordination.js +72 -0
  189. package/dist/runtime/database/flock.d.ts +10 -0
  190. package/dist/runtime/database/flock.js +60 -0
  191. package/dist/runtime/database/gate.d.ts +26 -0
  192. package/dist/runtime/database/gate.js +54 -0
  193. package/dist/runtime/database/keepalive.d.ts +11 -0
  194. package/dist/runtime/database/keepalive.js +22 -0
  195. package/dist/runtime/database/migrations.d.ts +177 -0
  196. package/dist/runtime/database/migrations.js +1018 -0
  197. package/dist/runtime/database/neki/migrations.d.ts +31 -0
  198. package/dist/runtime/database/neki/migrations.js +276 -0
  199. package/dist/runtime/database/neki/session.d.ts +23 -0
  200. package/dist/runtime/database/neki/session.js +31 -0
  201. package/dist/runtime/database/pglite.d.ts +21 -0
  202. package/dist/runtime/database/pglite.js +74 -0
  203. package/dist/runtime/database/replica.d.ts +41 -0
  204. package/dist/runtime/database/replica.js +51 -0
  205. package/dist/runtime/database/shards.d.ts +21 -0
  206. package/dist/runtime/database/shards.js +37 -0
  207. package/dist/runtime/database/snapshot.d.ts +7 -0
  208. package/dist/runtime/database/snapshot.js +7 -0
  209. package/dist/runtime/database/tenancy.d.ts +69 -0
  210. package/dist/runtime/database/tenancy.js +145 -0
  211. package/dist/runtime/database/transient.d.ts +10 -0
  212. package/dist/runtime/database/transient.js +25 -0
  213. package/dist/runtime/drain.d.ts +132 -0
  214. package/dist/runtime/drain.js +109 -0
  215. package/dist/runtime/entity/mailbox.d.ts +86 -0
  216. package/dist/runtime/entity/mailbox.js +121 -0
  217. package/dist/runtime/entity/register.d.ts +424 -0
  218. package/dist/runtime/entity/register.js +669 -0
  219. package/dist/runtime/events/append.d.ts +192 -0
  220. package/dist/runtime/events/append.js +128 -0
  221. package/dist/runtime/events/cursor.d.ts +2 -0
  222. package/dist/runtime/events/cursor.js +5 -0
  223. package/dist/runtime/events/replay.d.ts +20 -0
  224. package/dist/runtime/events/replay.js +45 -0
  225. package/dist/runtime/feeds.d.ts +26 -0
  226. package/dist/runtime/feeds.js +100 -0
  227. package/dist/runtime/fleet/checks.d.ts +17 -0
  228. package/dist/runtime/fleet/checks.js +106 -0
  229. package/dist/runtime/fleet/maintainer.d.ts +38 -0
  230. package/dist/runtime/fleet/maintainer.js +383 -0
  231. package/dist/runtime/fleet/pgoutput.d.ts +41 -0
  232. package/dist/runtime/fleet/pgoutput.js +102 -0
  233. package/dist/runtime/fleet/setup.d.ts +36 -0
  234. package/dist/runtime/fleet/setup.js +58 -0
  235. package/dist/runtime/fleet/subscribe.d.ts +42 -0
  236. package/dist/runtime/fleet/subscribe.js +147 -0
  237. package/dist/runtime/index.d.ts +66 -0
  238. package/dist/runtime/index.js +37 -0
  239. package/dist/runtime/inspector/http.d.ts +19 -0
  240. package/dist/runtime/inspector/http.js +36 -0
  241. package/dist/runtime/inspector/layer.d.ts +46 -0
  242. package/dist/runtime/inspector/layer.js +180 -0
  243. package/dist/runtime/inspector/queries.d.ts +711 -0
  244. package/dist/runtime/inspector/queries.js +494 -0
  245. package/dist/runtime/jobs/attempt.d.ts +254 -0
  246. package/dist/runtime/jobs/attempt.js +413 -0
  247. package/dist/runtime/jobs/isolation.d.ts +20 -0
  248. package/dist/runtime/jobs/isolation.js +10 -0
  249. package/dist/runtime/jobs/progress.d.ts +95 -0
  250. package/dist/runtime/jobs/progress.js +144 -0
  251. package/dist/runtime/layer.d.ts +260 -0
  252. package/dist/runtime/layer.js +876 -0
  253. package/dist/runtime/members.d.ts +381 -0
  254. package/dist/runtime/members.js +8 -0
  255. package/dist/runtime/operators/audit.d.ts +64 -0
  256. package/dist/runtime/operators/audit.js +56 -0
  257. package/dist/runtime/operators/auth.d.ts +29 -0
  258. package/dist/runtime/operators/auth.js +42 -0
  259. package/dist/runtime/operators/export.d.ts +68 -0
  260. package/dist/runtime/operators/export.js +137 -0
  261. package/dist/runtime/operators/grants.d.ts +45 -0
  262. package/dist/runtime/operators/grants.js +40 -0
  263. package/dist/runtime/operators/repair.d.ts +206 -0
  264. package/dist/runtime/operators/repair.js +226 -0
  265. package/dist/runtime/operators/routes.d.ts +35 -0
  266. package/dist/runtime/operators/routes.js +190 -0
  267. package/dist/runtime/operators/seed.d.ts +123 -0
  268. package/dist/runtime/operators/seed.js +123 -0
  269. package/dist/runtime/payloads/versions.d.ts +65 -0
  270. package/dist/runtime/payloads/versions.js +240 -0
  271. package/dist/runtime/peering/authority.d.ts +42 -0
  272. package/dist/runtime/peering/authority.js +132 -0
  273. package/dist/runtime/peering/credentials.d.ts +21 -0
  274. package/dist/runtime/peering/credentials.js +13 -0
  275. package/dist/runtime/peering/transport.d.ts +51 -0
  276. package/dist/runtime/peering/transport.js +175 -0
  277. package/dist/runtime/queries.d.ts +18 -0
  278. package/dist/runtime/queries.js +169 -0
  279. package/dist/runtime/readiness.d.ts +36 -0
  280. package/dist/runtime/readiness.js +44 -0
  281. package/dist/runtime/registration.d.ts +58 -0
  282. package/dist/runtime/registration.js +186 -0
  283. package/dist/runtime/request.d.ts +158 -0
  284. package/dist/runtime/request.js +102 -0
  285. package/dist/runtime/retry.d.ts +8 -0
  286. package/dist/runtime/retry.js +11 -0
  287. package/dist/runtime/runner.d.ts +93 -0
  288. package/dist/runtime/runner.js +122 -0
  289. package/dist/runtime/storage/codec.d.ts +39 -0
  290. package/dist/runtime/storage/codec.js +41 -0
  291. package/dist/runtime/storage/generation.d.ts +69 -0
  292. package/dist/runtime/storage/generation.js +49 -0
  293. package/dist/runtime/storage/placements.d.ts +15 -0
  294. package/dist/runtime/storage/placements.js +38 -0
  295. package/dist/runtime/storage/platform.d.ts +6 -0
  296. package/dist/runtime/storage/platform.js +9 -0
  297. package/dist/runtime/storage/retention.d.ts +73 -0
  298. package/dist/runtime/storage/retention.js +158 -0
  299. package/dist/runtime/subscriptions/identity.d.ts +31 -0
  300. package/dist/runtime/subscriptions/identity.js +45 -0
  301. package/dist/runtime/subscriptions/relay.d.ts +175 -0
  302. package/dist/runtime/subscriptions/relay.js +763 -0
  303. package/dist/runtime/subscriptions/storage.d.ts +40 -0
  304. package/dist/runtime/subscriptions/storage.js +98 -0
  305. package/dist/runtime/sweeps.d.ts +39 -0
  306. package/dist/runtime/sweeps.js +54 -0
  307. package/dist/runtime/telemetry/defects.d.ts +57 -0
  308. package/dist/runtime/telemetry/defects.js +43 -0
  309. package/dist/runtime/telemetry/metrics.d.ts +52 -0
  310. package/dist/runtime/telemetry/metrics.js +138 -0
  311. package/dist/runtime/telemetry/routes.d.ts +19 -0
  312. package/dist/runtime/telemetry/routes.js +18 -0
  313. package/dist/runtime/telemetry/sampler.d.ts +26 -0
  314. package/dist/runtime/telemetry/sampler.js +101 -0
  315. package/dist/runtime/telemetry/spans.d.ts +38 -0
  316. package/dist/runtime/telemetry/spans.js +44 -0
  317. package/dist/runtime/telemetry/usage.d.ts +52 -0
  318. package/dist/runtime/telemetry/usage.js +30 -0
  319. package/dist/runtime/topology/admission.d.ts +13 -0
  320. package/dist/runtime/topology/admission.js +36 -0
  321. package/dist/runtime/topology/locks.d.ts +56 -0
  322. package/dist/runtime/topology/locks.js +81 -0
  323. package/dist/runtime/topology/messages.d.ts +15 -0
  324. package/dist/runtime/topology/messages.js +32 -0
  325. package/dist/runtime/turn/admission.d.ts +22 -0
  326. package/dist/runtime/turn/admission.js +41 -0
  327. package/dist/runtime/turn/blobs.d.ts +38 -0
  328. package/dist/runtime/turn/blobs.js +215 -0
  329. package/dist/runtime/turn/execute.d.ts +208 -0
  330. package/dist/runtime/turn/execute.js +972 -0
  331. package/dist/runtime/turn/group.d.ts +165 -0
  332. package/dist/runtime/turn/group.js +326 -0
  333. package/dist/runtime/turn/hooks.d.ts +44 -0
  334. package/dist/runtime/turn/hooks.js +28 -0
  335. package/dist/runtime/turn/outbox.d.ts +159 -0
  336. package/dist/runtime/turn/outbox.js +269 -0
  337. package/dist/runtime/turn/pipeline.d.ts +68 -0
  338. package/dist/runtime/turn/pipeline.js +83 -0
  339. package/dist/runtime/turn/receipt.d.ts +81 -0
  340. package/dist/runtime/turn/receipt.js +32 -0
  341. package/dist/runtime/turn/relay.d.ts +179 -0
  342. package/dist/runtime/turn/relay.js +685 -0
  343. package/dist/runtime/turn/rows.d.ts +43 -0
  344. package/dist/runtime/turn/rows.js +592 -0
  345. package/dist/runtime/workflows/compatibility.d.ts +125 -0
  346. package/dist/runtime/workflows/compatibility.js +285 -0
  347. package/dist/runtime/workflows/engine.d.ts +107 -0
  348. package/dist/runtime/workflows/engine.js +826 -0
  349. package/dist/runtime/workflows/manifest.d.ts +34 -0
  350. package/dist/runtime/workflows/manifest.js +39 -0
  351. package/dist/runtime/workflows/steps.d.ts +33 -0
  352. package/dist/runtime/workflows/steps.js +152 -0
  353. package/dist/serve/api.d.ts +99 -0
  354. package/dist/serve/api.js +403 -0
  355. package/dist/serve/assertion/binding.d.ts +41 -0
  356. package/dist/serve/assertion/binding.js +92 -0
  357. package/dist/serve/assertion/verify.d.ts +93 -0
  358. package/dist/serve/assertion/verify.js +189 -0
  359. package/dist/serve/auth.d.ts +200 -0
  360. package/dist/serve/auth.js +92 -0
  361. package/dist/serve/clock.d.ts +10 -0
  362. package/dist/serve/clock.js +25 -0
  363. package/dist/serve/jwt.d.ts +65 -0
  364. package/dist/serve/jwt.js +202 -0
  365. package/dist/serve/layer.d.ts +98 -0
  366. package/dist/serve/layer.js +809 -0
  367. package/dist/serve/mcp/endpoint.d.ts +39 -0
  368. package/dist/serve/mcp/endpoint.js +187 -0
  369. package/dist/serve/mcp/tools.d.ts +37 -0
  370. package/dist/serve/mcp/tools.js +125 -0
  371. package/dist/serve/origin.d.ts +6 -0
  372. package/dist/serve/origin.js +21 -0
  373. package/dist/serve/sessions/feed.d.ts +79 -0
  374. package/dist/serve/sessions/feed.js +115 -0
  375. package/dist/serve/sessions/socket.d.ts +56 -0
  376. package/dist/serve/sessions/socket.js +212 -0
  377. package/dist/serve/sessions/sse.d.ts +27 -0
  378. package/dist/serve/sessions/sse.js +26 -0
  379. package/dist/serve/sessions/stream.d.ts +9 -0
  380. package/dist/serve/sessions/stream.js +9 -0
  381. package/dist/serve/sessions/watch.d.ts +12 -0
  382. package/dist/serve/sessions/watch.js +11 -0
  383. package/dist/serve/wire.d.ts +16 -0
  384. package/dist/serve/wire.js +50 -0
  385. package/dist/state/blob.d.ts +71 -0
  386. package/dist/state/blob.js +0 -0
  387. package/dist/state/migration.d.ts +53 -0
  388. package/dist/state/migration.js +29 -0
  389. package/dist/tables/fleet.d.ts +170 -0
  390. package/dist/tables/fleet.js +214 -0
  391. package/dist/tables/owned.d.ts +250 -0
  392. package/dist/tables/owned.js +233 -0
  393. package/dist/testing/actor-test.d.ts +360 -0
  394. package/dist/testing/actor-test.js +337 -0
  395. package/dist/testing/cluster.d.ts +93 -0
  396. package/dist/testing/cluster.js +296 -0
  397. package/dist/testing/conformance/access.d.ts +5 -0
  398. package/dist/testing/conformance/access.js +531 -0
  399. package/dist/testing/conformance/admission.d.ts +11 -0
  400. package/dist/testing/conformance/admission.js +182 -0
  401. package/dist/testing/conformance/adoption.d.ts +2 -0
  402. package/dist/testing/conformance/adoption.js +1019 -0
  403. package/dist/testing/conformance/assertions.d.ts +202 -0
  404. package/dist/testing/conformance/assertions.js +849 -0
  405. package/dist/testing/conformance/batches.d.ts +41 -0
  406. package/dist/testing/conformance/batches.js +388 -0
  407. package/dist/testing/conformance/blobs.d.ts +14 -0
  408. package/dist/testing/conformance/blobs.js +405 -0
  409. package/dist/testing/conformance/capacity.d.ts +8 -0
  410. package/dist/testing/conformance/capacity.js +433 -0
  411. package/dist/testing/conformance/client.d.ts +5 -0
  412. package/dist/testing/conformance/client.js +719 -0
  413. package/dist/testing/conformance/cold-serve.d.ts +16 -0
  414. package/dist/testing/conformance/cold-serve.js +254 -0
  415. package/dist/testing/conformance/connections/actors.d.ts +1143 -0
  416. package/dist/testing/conformance/connections/actors.js +180 -0
  417. package/dist/testing/conformance/connections/harness.d.ts +1754 -0
  418. package/dist/testing/conformance/connections/harness.js +142 -0
  419. package/dist/testing/conformance/connections/holders.d.ts +4 -0
  420. package/dist/testing/conformance/connections/holders.js +165 -0
  421. package/dist/testing/conformance/connections/lifecycle.d.ts +4 -0
  422. package/dist/testing/conformance/connections/lifecycle.js +113 -0
  423. package/dist/testing/conformance/connections/limits.d.ts +4 -0
  424. package/dist/testing/conformance/connections/limits.js +127 -0
  425. package/dist/testing/conformance/connections/reauthorization.d.ts +4 -0
  426. package/dist/testing/conformance/connections/reauthorization.js +170 -0
  427. package/dist/testing/conformance/connections/resync.d.ts +4 -0
  428. package/dist/testing/conformance/connections/resync.js +360 -0
  429. package/dist/testing/conformance/connections/takeover.d.ts +4 -0
  430. package/dist/testing/conformance/connections/takeover.js +287 -0
  431. package/dist/testing/conformance/connections.d.ts +4 -0
  432. package/dist/testing/conformance/connections.js +15 -0
  433. package/dist/testing/conformance/content-blobs.d.ts +16 -0
  434. package/dist/testing/conformance/content-blobs.js +534 -0
  435. package/dist/testing/conformance/counter.d.ts +16 -0
  436. package/dist/testing/conformance/counter.js +813 -0
  437. package/dist/testing/conformance/cron.d.ts +5 -0
  438. package/dist/testing/conformance/cron.js +665 -0
  439. package/dist/testing/conformance/drain.d.ts +26 -0
  440. package/dist/testing/conformance/drain.js +540 -0
  441. package/dist/testing/conformance/effect-control.d.ts +38 -0
  442. package/dist/testing/conformance/effect-control.js +1059 -0
  443. package/dist/testing/conformance/effects.d.ts +23 -0
  444. package/dist/testing/conformance/effects.js +432 -0
  445. package/dist/testing/conformance/events.d.ts +28 -0
  446. package/dist/testing/conformance/events.js +335 -0
  447. package/dist/testing/conformance/export.d.ts +2 -0
  448. package/dist/testing/conformance/export.js +368 -0
  449. package/dist/testing/conformance/fleet.d.ts +444 -0
  450. package/dist/testing/conformance/fleet.js +1009 -0
  451. package/dist/testing/conformance/groups.d.ts +7 -0
  452. package/dist/testing/conformance/groups.js +353 -0
  453. package/dist/testing/conformance/heap.d.ts +3 -0
  454. package/dist/testing/conformance/heap.js +149 -0
  455. package/dist/testing/conformance/http.d.ts +2809 -0
  456. package/dist/testing/conformance/http.js +1234 -0
  457. package/dist/testing/conformance/inspection-views.d.ts +8 -0
  458. package/dist/testing/conformance/inspection-views.js +272 -0
  459. package/dist/testing/conformance/inspector.d.ts +7 -0
  460. package/dist/testing/conformance/inspector.js +650 -0
  461. package/dist/testing/conformance/mint.d.ts +58 -0
  462. package/dist/testing/conformance/mint.js +692 -0
  463. package/dist/testing/conformance/multi-runner.d.ts +8 -0
  464. package/dist/testing/conformance/multi-runner.js +403 -0
  465. package/dist/testing/conformance/neki/plan.d.ts +19 -0
  466. package/dist/testing/conformance/neki/plan.js +30 -0
  467. package/dist/testing/conformance/observability.d.ts +3 -0
  468. package/dist/testing/conformance/observability.js +343 -0
  469. package/dist/testing/conformance/offline.d.ts +4 -0
  470. package/dist/testing/conformance/offline.js +474 -0
  471. package/dist/testing/conformance/operator-harness.d.ts +57 -0
  472. package/dist/testing/conformance/operator-harness.js +78 -0
  473. package/dist/testing/conformance/operator.d.ts +3 -0
  474. package/dist/testing/conformance/operator.js +379 -0
  475. package/dist/testing/conformance/outbox-cross-shard.d.ts +14 -0
  476. package/dist/testing/conformance/outbox-cross-shard.js +160 -0
  477. package/dist/testing/conformance/outbox.d.ts +36 -0
  478. package/dist/testing/conformance/outbox.js +437 -0
  479. package/dist/testing/conformance/payload-migrations.d.ts +3 -0
  480. package/dist/testing/conformance/payload-migrations.js +612 -0
  481. package/dist/testing/conformance/pipeline.d.ts +3 -0
  482. package/dist/testing/conformance/pipeline.js +1040 -0
  483. package/dist/testing/conformance/placement.d.ts +22 -0
  484. package/dist/testing/conformance/placement.js +539 -0
  485. package/dist/testing/conformance/platform.d.ts +40 -0
  486. package/dist/testing/conformance/platform.js +101 -0
  487. package/dist/testing/conformance/progress.d.ts +39 -0
  488. package/dist/testing/conformance/progress.js +666 -0
  489. package/dist/testing/conformance/properties.d.ts +6 -0
  490. package/dist/testing/conformance/properties.js +295 -0
  491. package/dist/testing/conformance/protocols.d.ts +4 -0
  492. package/dist/testing/conformance/protocols.js +618 -0
  493. package/dist/testing/conformance/provider/advertise.d.ts +21 -0
  494. package/dist/testing/conformance/provider/advertise.js +44 -0
  495. package/dist/testing/conformance/read-your-writes.d.ts +20 -0
  496. package/dist/testing/conformance/read-your-writes.js +192 -0
  497. package/dist/testing/conformance/reducers.d.ts +8 -0
  498. package/dist/testing/conformance/reducers.js +258 -0
  499. package/dist/testing/conformance/relay.d.ts +42 -0
  500. package/dist/testing/conformance/relay.js +1115 -0
  501. package/dist/testing/conformance/restore.d.ts +43 -0
  502. package/dist/testing/conformance/restore.js +341 -0
  503. package/dist/testing/conformance/retention.d.ts +14 -0
  504. package/dist/testing/conformance/retention.js +463 -0
  505. package/dist/testing/conformance/rls.d.ts +24 -0
  506. package/dist/testing/conformance/rls.js +576 -0
  507. package/dist/testing/conformance/simulation.d.ts +31 -0
  508. package/dist/testing/conformance/simulation.js +167 -0
  509. package/dist/testing/conformance/single-shard.d.ts +2 -0
  510. package/dist/testing/conformance/single-shard.js +125 -0
  511. package/dist/testing/conformance/singleton.d.ts +3 -0
  512. package/dist/testing/conformance/singleton.js +293 -0
  513. package/dist/testing/conformance/statements.d.ts +60 -0
  514. package/dist/testing/conformance/statements.js +108 -0
  515. package/dist/testing/conformance/streams.d.ts +7 -0
  516. package/dist/testing/conformance/streams.js +280 -0
  517. package/dist/testing/conformance/subscriptions/access.d.ts +4 -0
  518. package/dist/testing/conformance/subscriptions/access.js +98 -0
  519. package/dist/testing/conformance/subscriptions/actors.d.ts +4910 -0
  520. package/dist/testing/conformance/subscriptions/actors.js +372 -0
  521. package/dist/testing/conformance/subscriptions/cluster.d.ts +4 -0
  522. package/dist/testing/conformance/subscriptions/cluster.js +89 -0
  523. package/dist/testing/conformance/subscriptions/delivery.d.ts +4 -0
  524. package/dist/testing/conformance/subscriptions/delivery.js +253 -0
  525. package/dist/testing/conformance/subscriptions/deploys.d.ts +4 -0
  526. package/dist/testing/conformance/subscriptions/deploys.js +162 -0
  527. package/dist/testing/conformance/subscriptions/epochs.d.ts +4 -0
  528. package/dist/testing/conformance/subscriptions/epochs.js +169 -0
  529. package/dist/testing/conformance/subscriptions/failures.d.ts +4 -0
  530. package/dist/testing/conformance/subscriptions/failures.js +108 -0
  531. package/dist/testing/conformance/subscriptions/feeds.d.ts +4 -0
  532. package/dist/testing/conformance/subscriptions/feeds.js +218 -0
  533. package/dist/testing/conformance/subscriptions/harness.d.ts +124 -0
  534. package/dist/testing/conformance/subscriptions/harness.js +143 -0
  535. package/dist/testing/conformance/subscriptions/operator.d.ts +4 -0
  536. package/dist/testing/conformance/subscriptions/operator.js +128 -0
  537. package/dist/testing/conformance/subscriptions/retention.d.ts +4 -0
  538. package/dist/testing/conformance/subscriptions/retention.js +288 -0
  539. package/dist/testing/conformance/subscriptions/routing.d.ts +4 -0
  540. package/dist/testing/conformance/subscriptions/routing.js +179 -0
  541. package/dist/testing/conformance/subscriptions.d.ts +4 -0
  542. package/dist/testing/conformance/subscriptions.js +19 -0
  543. package/dist/testing/conformance/tables.d.ts +125 -0
  544. package/dist/testing/conformance/tables.js +664 -0
  545. package/dist/testing/conformance/transports/actors.d.ts +1907 -0
  546. package/dist/testing/conformance/transports/actors.js +163 -0
  547. package/dist/testing/conformance/transports/client.d.ts +3 -0
  548. package/dist/testing/conformance/transports/client.js +237 -0
  549. package/dist/testing/conformance/transports/feeds.d.ts +3 -0
  550. package/dist/testing/conformance/transports/feeds.js +232 -0
  551. package/dist/testing/conformance/transports/harness.d.ts +93 -0
  552. package/dist/testing/conformance/transports/harness.js +84 -0
  553. package/dist/testing/conformance/transports/streams.d.ts +3 -0
  554. package/dist/testing/conformance/transports/streams.js +96 -0
  555. package/dist/testing/conformance/transports/websocket.d.ts +3 -0
  556. package/dist/testing/conformance/transports/websocket.js +384 -0
  557. package/dist/testing/conformance/transports/wire.d.ts +141 -0
  558. package/dist/testing/conformance/transports/wire.js +204 -0
  559. package/dist/testing/conformance/transports.d.ts +3 -0
  560. package/dist/testing/conformance/transports.js +11 -0
  561. package/dist/testing/conformance/watch.d.ts +59 -0
  562. package/dist/testing/conformance/watch.js +879 -0
  563. package/dist/testing/conformance/workflow-engine.d.ts +97 -0
  564. package/dist/testing/conformance/workflow-engine.js +253 -0
  565. package/dist/testing/conformance/workflow-versions.d.ts +3 -0
  566. package/dist/testing/conformance/workflow-versions.js +545 -0
  567. package/dist/testing/conformance/workflows/access.d.ts +4 -0
  568. package/dist/testing/conformance/workflows/access.js +147 -0
  569. package/dist/testing/conformance/workflows/actors.d.ts +2317 -0
  570. package/dist/testing/conformance/workflows/actors.js +305 -0
  571. package/dist/testing/conformance/workflows/basics.d.ts +4 -0
  572. package/dist/testing/conformance/workflows/basics.js +144 -0
  573. package/dist/testing/conformance/workflows/engine.d.ts +4 -0
  574. package/dist/testing/conformance/workflows/engine.js +80 -0
  575. package/dist/testing/conformance/workflows/guards.d.ts +4 -0
  576. package/dist/testing/conformance/workflows/guards.js +172 -0
  577. package/dist/testing/conformance/workflows/harness.d.ts +14 -0
  578. package/dist/testing/conformance/workflows/harness.js +44 -0
  579. package/dist/testing/conformance/workflows/recovery.d.ts +4 -0
  580. package/dist/testing/conformance/workflows/recovery.js +181 -0
  581. package/dist/testing/conformance/workflows/starts.d.ts +4 -0
  582. package/dist/testing/conformance/workflows/starts.js +57 -0
  583. package/dist/testing/conformance/workflows/suspension.d.ts +4 -0
  584. package/dist/testing/conformance/workflows/suspension.js +106 -0
  585. package/dist/testing/conformance/workflows/waits.d.ts +4 -0
  586. package/dist/testing/conformance/workflows/waits.js +284 -0
  587. package/dist/testing/conformance/workflows.d.ts +4 -0
  588. package/dist/testing/conformance/workflows.js +19 -0
  589. package/dist/testing/conformance.d.ts +330 -0
  590. package/dist/testing/conformance.js +364 -0
  591. package/dist/testing/database.d.ts +31 -0
  592. package/dist/testing/database.js +70 -0
  593. package/dist/testing/foundation.d.ts +41 -0
  594. package/dist/testing/foundation.js +517 -0
  595. package/dist/testing/index.d.ts +20 -0
  596. package/dist/testing/index.js +12 -0
  597. package/dist/testing/property.d.ts +40 -0
  598. package/dist/testing/property.js +95 -0
  599. package/dist/testing/simulate-cluster.d.ts +112 -0
  600. package/dist/testing/simulate-cluster.js +257 -0
  601. package/dist/testing/simulate.d.ts +90 -0
  602. package/dist/testing/simulate.js +127 -0
  603. package/package.json +1 -0
@@ -0,0 +1,763 @@
1
+ import { DateTime, Effect, Match, Result, Schema } from "effect";
2
+ import { SqlClient, SqlError } from "effect/sql";
3
+ import {} from "../database/shards.js";
4
+ import { SubscriptionFailure } from "../../errors/subscription.js";
5
+ import { Outcome, Request } from "../request.js";
6
+ import {} from "../members.js";
7
+ import { ActorRef, System } from "../../identity/caller.js";
8
+ import { decompress, routingKey } from "../storage/codec.js";
9
+ import { databaseTime, FrameworkClock } from "../turn/admission.js";
10
+ import { TurnHooks } from "../turn/hooks.js";
11
+ import { ControlPayload, StringsJson, textArray } from "../turn/outbox.js";
12
+ import { candidates, outboxNow } from "../turn/relay.js";
13
+ import { changeRows, rowColumns } from "./storage.js";
14
+ import { deliveryCommandId } from "./identity.js";
15
+ import { count, Metrics } from "../telemetry/metrics.js";
16
+ import { SpanNames } from "../telemetry/spans.js";
17
+ /** How long a row whose declaration was removed stays due before cleanup deletes it. */
18
+ const REMOVED_AFTER_MS = 86_400_000;
19
+ /** Source-side subscription rows one expansion statement marks at most. */
20
+ const EXPANSION_PAGE = 1000;
21
+ /** A claimed feed row to expand, or control row to register at its source. */
22
+ const OutboxWork = Schema.Struct({
23
+ kind: Schema.Literals(["feed", "control"]),
24
+ routing_key: Schema.String,
25
+ intent_id: Schema.String,
26
+ attempts: Schema.Int,
27
+ tenant_id: Schema.String,
28
+ actor_type: Schema.String,
29
+ actor_id: Schema.String,
30
+ target_type: Schema.String,
31
+ target_id: Schema.String,
32
+ command: Schema.String,
33
+ payload: Schema.String,
34
+ scheduled_at_ms: Schema.String,
35
+ claimed_until: Schema.String,
36
+ });
37
+ /** A claimed subscription row to deliver. */
38
+ const SubscriptionRow = Schema.Struct({
39
+ kind: Schema.Literal("subscription"),
40
+ routing_key: Schema.String,
41
+ tenant_id: Schema.String,
42
+ source_type: Schema.String,
43
+ source_id: Schema.String,
44
+ subscriber_type: Schema.String,
45
+ subscription: Schema.String,
46
+ subscriber_id: Schema.String,
47
+ events: Schema.Array(Schema.String),
48
+ epoch: Schema.String,
49
+ delivered: Schema.String,
50
+ attempts: Schema.Int,
51
+ gap_through: Schema.NullOr(Schema.String),
52
+ claimed_until: Schema.String,
53
+ });
54
+ /** A unit of relay work, as a claim returns it in its `work` column: an outbox row to expand or a subscription row to deliver. */
55
+ const SubscriptionWork = Schema.fromJsonString(Schema.Union([OutboxWork, SubscriptionRow]));
56
+ const JsonText = Schema.fromJsonString(Schema.Unknown);
57
+ /**
58
+ * A delivery as the relay encodes it for any subscriber's handler: the event
59
+ * stays the source's stored JSON, which the handler's own schema decodes.
60
+ */
61
+ const WireEvent = Schema.TaggedStruct("Event", {
62
+ subscription: Schema.String,
63
+ source: ActorRef,
64
+ cursor: Schema.String,
65
+ event: Schema.Unknown,
66
+ commandId: Schema.String,
67
+ timestamp: Schema.String,
68
+ });
69
+ const WireRejected = Schema.TaggedStruct("Rejected", {
70
+ subscription: Schema.String,
71
+ source: ActorRef,
72
+ reason: Schema.Literal("UnknownCursor"),
73
+ cursor: Schema.String,
74
+ });
75
+ const WireGap = Schema.TaggedStruct("RetentionGap", {
76
+ subscription: Schema.String,
77
+ source: ActorRef,
78
+ after: Schema.String,
79
+ resumeAfter: Schema.String,
80
+ });
81
+ const WireDelivery = Schema.Union([WireEvent, WireGap, WireRejected]);
82
+ const encodePayload = Schema.encodeEffect(Schema.fromJsonString(Schema.Struct({ value: WireDelivery })));
83
+ const decodeStrings = Schema.decodeEffect(StringsJson);
84
+ const decodeJson = Schema.decodeEffect(JsonText);
85
+ const decodeWork = Schema.decodeEffect(SubscriptionWork);
86
+ const backoff = (attempts, settings) => Math.max(settings.claimLeaseMs(), Math.min(1000 * 2 ** Math.max(attempts - 1, 0), settings.maxBackoffMs));
87
+ /** A request-shaped value naming the work at a fault point, for `TurnHooks`. */
88
+ const hookRequest = (ref, command, commandId) => Request.make({
89
+ ref,
90
+ caller: System.make({ source: "subscription" }),
91
+ command,
92
+ commandId,
93
+ payload: "",
94
+ });
95
+ /**
96
+ * The relay's subscription work on one runner: expanding source feeds,
97
+ * registering dynamic subscriptions at their sources, and delivering due
98
+ * subscription rows of the subscriber types this runner registers. A runner
99
+ * that registers none claims none of it.
100
+ *
101
+ * Delivery: rows this runner can deliver that an expansion makes due are leased at
102
+ * once, up to its free delivery slots, and delivered without a claim pass; the
103
+ * rest become due for any runner. Updates bound their rows by the page's key
104
+ * range, read from the key index, rather than joining the page, so a planner on
105
+ * stale row counts cannot rescan the page for every stored row. A renewal never
106
+ * shortens a claim, so it cannot undo a lease a test clock moved. Shutdown
107
+ * releases a claim at once; the receipt and cursor make redelivery safe.
108
+ *
109
+ * Subscribing: a source that has never been created can be subscribed to. Only a
110
+ * refused subscribe leaves a tombstone at its own epoch. A subscription is due at
111
+ * once when history after its start already matches, so one to a quiet source
112
+ * still delivers it. Widening only adds this declaration's classes to the row's
113
+ * list. Every write that changes a row's `active` or `events` goes through
114
+ * `changeRows`, which moves the tag summary in the same transaction.
115
+ *
116
+ * Gaps: pruning removes a prefix, so history after `delivered` is missing when the
117
+ * oldest retained event is past its successor or nothing is retained. An
118
+ * operator's skip plants the position it skips through as the gap's end. The
119
+ * detection time fixes the gap's id and its range is kept, so a redelivery reports
120
+ * the same gap even after pruning advances. An id route needs the pruned event to
121
+ * name a subscriber. A short batch scanned every matching event through the head
122
+ * it read, and after a gap older than the pruning it stops at the gap to find the
123
+ * next one.
124
+ *
125
+ * Delivery outcomes: the subscriber reads the current shape, and a value its chain
126
+ * cannot read backs the row off at this event rather than skipping it. A failure
127
+ * backs the row off, and a stale or unsubscribed acknowledgement removes this
128
+ * epoch's row. The marked position is read before the settle in its own snapshot:
129
+ * a commit after it raised `marked` through its expansion, which the settle reads
130
+ * from the row.
131
+ */
132
+ export const subscriptionRelay = Effect.fnUntraced(function* (options) {
133
+ const sql = yield* SqlClient.SqlClient;
134
+ const hooks = yield* TurnHooks;
135
+ const clock = yield* FrameworkClock;
136
+ const { settings } = options;
137
+ const now = () => outboxNow({ sql, offsetMillis: clock.offsetMillis() });
138
+ const sourceWhere = (alias, key, source) => sql `${sql(alias)}.routing_key = ${key} AND ${sql(alias)}.tenant_id = ${source.tenant}
139
+ AND ${sql(alias)}.source_type = ${source.actor} AND ${sql(alias)}.source_id = ${source.id}`;
140
+ const eventsOf = (alias, key, source) => sql `${sql(alias)}.routing_key = ${key} AND ${sql(alias)}.tenant_id = ${source.tenant}
141
+ AND ${sql(alias)}.actor_type = ${source.actor} AND ${sql(alias)}.actor_id = ${source.id}`;
142
+ /** When a claim of the row aliased `alias` ends: one lease, or its backoff once that is longer. */
143
+ const leaseEnd = (alias) => sql `${now()} + greatest(${settings.claimLeaseMs()}::bigint,
144
+ least(1000 * power(2, least(${sql(alias)}.attempts, 31)), ${settings.maxBackoffMs}::bigint))::bigint`;
145
+ const outboxClaim = (kind, now, limit, range) => {
146
+ const probe = 4 * limit;
147
+ const found = sql.literal(`${kind}_candidates`);
148
+ const locked = sql.literal(`${kind}_locked`);
149
+ const claimed = sql.literal(`${kind}_claimed`);
150
+ return sql `${found} AS (
151
+ ${candidates({ sql, kind, now, limit: probe, range })}
152
+ ORDER BY o.due_at_ms LIMIT ${probe}
153
+ ),
154
+ ${locked} AS (
155
+ SELECT o.routing_key, o.intent_id FROM actor_outbox o
156
+ JOIN ${found} USING (routing_key, intent_id)
157
+ WHERE o.kind = ${kind} AND o.due_at_ms <= ${now}
158
+ ORDER BY o.due_at_ms LIMIT ${limit}
159
+ FOR UPDATE OF o SKIP LOCKED
160
+ ),
161
+ ${claimed} AS (
162
+ UPDATE actor_outbox o SET attempts = o.attempts + 1, due_at_ms = ${leaseEnd("o")}
163
+ FROM ${locked} c
164
+ WHERE o.routing_key = c.routing_key AND o.intent_id = c.intent_id
165
+ RETURNING jsonb_build_object('kind', o.kind, 'routing_key', o.routing_key::text,
166
+ 'intent_id', o.intent_id, 'attempts', o.attempts, 'tenant_id', o.tenant_id,
167
+ 'actor_type', o.actor_type, 'actor_id', o.actor_id, 'target_type', o.target_type,
168
+ 'target_id', o.target_id, 'command', o.command, 'payload', o.payload,
169
+ 'scheduled_at_ms', o.scheduled_at_ms::text,
170
+ 'claimed_until', o.due_at_ms::text)::text AS work
171
+ )`;
172
+ };
173
+ const subscribed = (local) => sql `subscribed (subscriber_type, subscription, known) AS (VALUES ${sql.csv(local.map(({ subscriberType, subscription }) => sql `(${subscriberType}::text, ${subscription.tag}::text, ${textArray({
174
+ sql,
175
+ values: [...subscription.events, ...subscription.retired],
176
+ })})`))})`;
177
+ const claimedRow = sql `jsonb_build_object('kind', 'subscription', 'routing_key', s.routing_key::text,
178
+ 'tenant_id', s.tenant_id, 'source_type', s.source_type, 'source_id', s.source_id,
179
+ 'subscriber_type', s.subscriber_type, 'subscription', s.subscription,
180
+ 'subscriber_id', s.subscriber_id, 'events', to_jsonb(s.events),
181
+ 'epoch', s.epoch::text, 'delivered', s.delivered::text, 'attempts', s.attempts,
182
+ 'gap_through', s.gap_through::text, 'claimed_until', s.due_at_ms::text)`;
183
+ /**
184
+ * Claims due feed and control rows and due subscription rows, each up to
185
+ * its free slots, as parts of the relay's claim statement. Subscription
186
+ * rows are probed per bucket and subscriber type through
187
+ * `actor_subscriptions_due`, so caught-up rows and other types' rows are
188
+ * never read, and only rows whose event tags this runner's declaration
189
+ * knows are taken: a runner never settles past a class it can't decode.
190
+ */
191
+ const claim = (slots, range) => {
192
+ const local = options.local();
193
+ if (local.length === 0)
194
+ return undefined;
195
+ const parts = [];
196
+ const results = [];
197
+ for (const kind of ["feed", "control"])
198
+ if (slots[kind] > 0) {
199
+ parts.push(outboxClaim(kind, now(), slots[kind], range));
200
+ results.push(sql `SELECT work FROM ${sql.literal(`${kind}_claimed`)}`);
201
+ }
202
+ if (slots.subscription > 0) {
203
+ const probe = 4 * slots.subscription;
204
+ parts.push(sql `${subscribed(local)},
205
+ subscription_candidates AS (
206
+ SELECT s.* FROM generate_series(${range.first}::int, ${range.last}::int) AS b(bucket)
207
+ CROSS JOIN (SELECT DISTINCT subscriber_type FROM subscribed) AS t
208
+ CROSS JOIN LATERAL (
209
+ SELECT routing_key, tenant_id, source_type, source_id, subscriber_type, subscription,
210
+ subscriber_id, due_at_ms
211
+ FROM actor_subscriptions
212
+ WHERE actor_subscriptions.bucket = b.bucket
213
+ AND actor_subscriptions.subscriber_type = t.subscriber_type
214
+ AND actor_subscriptions.due_at_ms <= ${now()}
215
+ AND EXISTS (SELECT 1 FROM subscribed m WHERE m.subscriber_type = actor_subscriptions.subscriber_type
216
+ AND m.subscription = actor_subscriptions.subscription
217
+ AND m.known @> actor_subscriptions.events)
218
+ ORDER BY actor_subscriptions.due_at_ms LIMIT ${probe}
219
+ ) s
220
+ ORDER BY s.due_at_ms LIMIT ${probe}
221
+ ),
222
+ subscription_locked AS (
223
+ SELECT s.routing_key, s.tenant_id, s.source_type, s.source_id, s.subscriber_type,
224
+ s.subscription, s.subscriber_id
225
+ FROM actor_subscriptions s
226
+ JOIN subscription_candidates c USING (routing_key, tenant_id, source_type, source_id,
227
+ subscriber_type, subscription, subscriber_id)
228
+ WHERE s.due_at_ms <= ${now()} AND s.active
229
+ ORDER BY s.due_at_ms LIMIT ${slots.subscription}
230
+ FOR UPDATE OF s SKIP LOCKED
231
+ ),
232
+ subscription_claimed AS (
233
+ UPDATE actor_subscriptions s SET attempts = s.attempts + 1, due_at_ms = ${leaseEnd("s")}
234
+ FROM subscription_locked c
235
+ WHERE s.routing_key = c.routing_key AND s.tenant_id = c.tenant_id
236
+ AND s.source_type = c.source_type AND s.source_id = c.source_id
237
+ AND s.subscriber_type = c.subscriber_type AND s.subscription = c.subscription
238
+ AND s.subscriber_id = c.subscriber_id
239
+ RETURNING ${claimedRow}::text AS work
240
+ )`);
241
+ results.push(sql `SELECT work FROM subscription_claimed`);
242
+ }
243
+ return results.length === 0 ? undefined : { parts, results };
244
+ };
245
+ const decode = (work) => Effect.orDie(decodeWork(work));
246
+ const placementKey = Effect.fnUntraced(function* (ref) {
247
+ const placement = yield* options.placementOf(ref.actor);
248
+ if (placement === undefined)
249
+ return yield* SubscriptionFailure.make({
250
+ message: `Source type ${ref.actor} has no recorded placement`,
251
+ });
252
+ return routingKey({ ref, placement });
253
+ });
254
+ const rowKey = (alias, row, key, source) => sql `${sourceWhere(alias, key, source)} AND ${sql(alias)}.subscriber_type = ${row.subscriber_type}
255
+ AND ${sql(alias)}.subscription = ${row.subscription} AND ${sql(alias)}.subscriber_id = ${row.subscriber_id}`;
256
+ /** Deletes the rows `old_rows` locked, returning each as no longer active. */
257
+ const deleteLocked = sql `DELETE FROM actor_subscriptions s USING old_rows o
258
+ WHERE s.routing_key = o.routing_key AND s.tenant_id = o.tenant_id
259
+ AND s.source_type = o.source_type AND s.source_id = o.source_id
260
+ AND s.subscriber_type = o.subscriber_type AND s.subscription = o.subscription
261
+ AND s.subscriber_id = o.subscriber_id
262
+ RETURNING s.routing_key, s.tenant_id, s.source_type, s.source_id, s.subscriber_type,
263
+ s.subscription, s.subscriber_id, false AS active, s.events`;
264
+ /**
265
+ * Deletes a dynamic row the subscriber acknowledged as stale or
266
+ * unsubscribed, but only at the delivery's epoch, so an old acknowledgement
267
+ * never removes a newer subscription.
268
+ */
269
+ const deleteRow = (row, key, source) => changeRows(sql `SELECT ${rowColumns({ sql, alias: "s" })} FROM actor_subscriptions s
270
+ WHERE ${rowKey("s", row, key, source)} AND s.epoch = ${row.epoch} FOR UPDATE`, deleteLocked);
271
+ /**
272
+ * Expands one source's feed: a claimed or backing-off row gets `marked`
273
+ * raised to the source's head, and a caught-up row with a matching event
274
+ * after its `delivered` becomes due. A row already due and unclaimed is left
275
+ * alone, so a burst of commits beside many waiting rows writes nothing. A claimed or backing-off row keeps its due
276
+ * time, so a commit can't cut a retry short, and `marked` alone keeps it
277
+ * due after its settle. The feed row goes only if no commit reset its
278
+ * `attempts` meanwhile; otherwise it stays due and is expanded again.
279
+ */
280
+ const expand = Effect.fnUntraced(function* (row, handoff) {
281
+ const key = BigInt(row.routing_key);
282
+ const source = { tenant: row.tenant_id, actor: row.actor_type, id: row.actor_id };
283
+ yield* hooks.at("afterClaim", hookRequest(source, "$feed", row.intent_id));
284
+ const local = options.local();
285
+ let after = ["", "", ""];
286
+ for (;;) {
287
+ const free = local.length === 0 ? 0 : Math.max(0, yield* handoff.free);
288
+ const leasable = free === 0
289
+ ? sql `leasable AS MATERIALIZED (
290
+ SELECT NULL::text AS subscriber_type, NULL::text AS subscription,
291
+ NULL::text AS subscriber_id WHERE false)`
292
+ : sql `${subscribed(local)},
293
+ leasable AS MATERIALIZED (
294
+ SELECT p.subscriber_type, p.subscription, p.subscriber_id
295
+ FROM page p JOIN subscribed m ON m.subscriber_type = p.subscriber_type
296
+ AND m.subscription = p.subscription AND m.known @> p.events, head
297
+ WHERE p.due_at_ms IS NULL AND p.marked < head.h
298
+ -- OFFSET 0 keeps this a per-row probe bounded by the row's
299
+ -- cursor in the index, rather than a join that reads the
300
+ -- source's whole event log for every row of the page.
301
+ AND EXISTS (
302
+ SELECT 1 FROM actor_events e
303
+ WHERE ${eventsOf("e", key, source)}
304
+ AND e.sequence > p.delivered AND e.sequence <= head.h
305
+ AND e.event = ANY(p.events) OFFSET 0)
306
+ LIMIT ${free})`;
307
+ const inPage = sql `(s.subscriber_type, s.subscription, s.subscriber_id) > (${after[0]}, ${after[1]}, ${after[2]})
308
+ AND (s.subscriber_type, s.subscription, s.subscriber_id)
309
+ <= (SELECT subscriber_type, subscription, subscriber_id FROM last)`;
310
+ const [page] = yield* sql `
311
+ WITH head AS (
312
+ SELECT event_sequence AS h FROM actor_generations g
313
+ WHERE ${eventsOf("g", key, source)}),
314
+ page AS MATERIALIZED (
315
+ SELECT s.subscriber_type, s.subscription, s.subscriber_id, s.due_at_ms, s.marked,
316
+ s.delivered, s.events
317
+ FROM actor_subscriptions s
318
+ WHERE ${sourceWhere("s", key, source)} AND s.active
319
+ AND (s.subscriber_type, s.subscription, s.subscriber_id) > (${after[0]}, ${after[1]}, ${after[2]})
320
+ ORDER BY s.subscriber_type, s.subscription, s.subscriber_id
321
+ LIMIT ${EXPANSION_PAGE}),
322
+ last AS (
323
+ SELECT subscriber_type, subscription, subscriber_id FROM page
324
+ ORDER BY subscriber_type DESC, subscription DESC, subscriber_id DESC LIMIT 1),
325
+ ${leasable},
326
+ -- Leased only while still unclaimed in the version this updates, so a
327
+ -- row another runner claimed meanwhile is left to the marking below.
328
+ leased AS (
329
+ UPDATE actor_subscriptions s SET marked = greatest(s.marked, head.h),
330
+ due_at_ms = ${leaseEnd("s")}, attempts = s.attempts + 1
331
+ FROM leasable l, head
332
+ WHERE ${sourceWhere("s", key, source)} AND ${inPage}
333
+ AND s.subscriber_type = l.subscriber_type AND s.subscription = l.subscription
334
+ AND s.subscriber_id = l.subscriber_id AND s.due_at_ms IS NULL
335
+ RETURNING s.subscriber_type, s.subscription, s.subscriber_id, ${claimedRow}::text AS work),
336
+ marked AS (
337
+ UPDATE actor_subscriptions s SET marked = greatest(s.marked, head.h),
338
+ due_at_ms = coalesce(s.due_at_ms, ${now()})
339
+ FROM head
340
+ WHERE NOT EXISTS (SELECT 1 FROM leased x WHERE x.subscriber_type = s.subscriber_type
341
+ AND x.subscription = s.subscription AND x.subscriber_id = s.subscriber_id)
342
+ AND ${sourceWhere("s", key, source)} AND s.active
343
+ AND ${inPage} AND s.marked < head.h
344
+ -- A row due and unclaimed since its last settle reads through the
345
+ -- head when it is claimed, so it needs no write.
346
+ AND NOT (s.due_at_ms IS NOT NULL AND s.due_at_ms <= ${now()} AND s.attempts = 0)
347
+ AND (s.due_at_ms IS NOT NULL OR EXISTS (
348
+ SELECT 1 FROM actor_events e
349
+ WHERE ${eventsOf("e", key, source)}
350
+ AND e.sequence > s.delivered AND e.sequence <= head.h AND e.event = ANY(s.events)))
351
+ RETURNING 1)
352
+ SELECT (SELECT count(*) FROM page)::int AS rows,
353
+ (SELECT json_agg(work)::text FROM leased) AS leased,
354
+ (SELECT json_build_array(subscriber_type, subscription, subscriber_id)::text FROM last) AS last`;
355
+ if (page.leased !== null)
356
+ for (const work of yield* decodeStrings(page.leased))
357
+ yield* handoff.start(yield* decode(work));
358
+ if (page.rows < EXPANSION_PAGE || page.last === null)
359
+ break;
360
+ after = yield* decodeStrings(page.last);
361
+ }
362
+ yield* hooks.at("afterExpand", hookRequest(source, "$feed", row.intent_id));
363
+ yield* sql `DELETE FROM actor_outbox WHERE routing_key = ${key} AND intent_id = ${row.intent_id}
364
+ AND kind = 'feed' AND attempts = ${row.attempts}`;
365
+ yield* options.wake;
366
+ });
367
+ /** Delivers one subscription delivery from `source` and answers how it settled. */
368
+ const deliverOne = Effect.fnUntraced(function* (subscriber, handler, source, delivery, issuedAt, value) {
369
+ const envelope = { ...delivery, sourceType: source.actor, sourceId: source.id };
370
+ const commandId = yield* deliveryCommandId({
371
+ subscriber,
372
+ envelope,
373
+ issuedAt,
374
+ retryWindowMs: settings.retryWindowMs,
375
+ });
376
+ const request = Request.make({
377
+ ref: subscriber,
378
+ caller: System.make({ source: "subscription", ref: source }),
379
+ command: handler,
380
+ commandId,
381
+ payload: yield* encodePayload({ value }),
382
+ delivery: envelope,
383
+ });
384
+ return yield* options.deliver(request).pipe(Effect.result);
385
+ });
386
+ const failure = (outcome) => {
387
+ if (Result.isFailure(outcome))
388
+ return outcome.failure.reason._tag;
389
+ return Outcome.guards.Defect(outcome.success) ? String(outcome.success.cause) : undefined;
390
+ };
391
+ /**
392
+ * Registers one dynamic subscription change at its source with a framework
393
+ * statement, never a turn, so the source isn't activated. Holding the
394
+ * source's generation row `FOR SHARE` orders it against the emit path's
395
+ * `FOR UPDATE`, and a row changes only for a strictly newer epoch, so a
396
+ * rerun after a crash, or an older change delivered late, changes nothing.
397
+ * A cursor past the source's head is refused: any older row becomes a
398
+ * tombstone at this epoch and the subscriber receives `Rejected`.
399
+ */
400
+ const register = Effect.fnUntraced(function* (row) {
401
+ const change = yield* Schema.decodeEffect(ControlPayload)(row.payload);
402
+ const subscriber = { tenant: row.tenant_id, actor: row.actor_type, id: row.actor_id };
403
+ const source = { tenant: row.tenant_id, actor: row.target_type, id: row.target_id };
404
+ const epoch = BigInt(change.epoch);
405
+ yield* hooks.at("afterClaim", hookRequest(source, "$control", row.intent_id));
406
+ const key = yield* placementKey(source);
407
+ const pk = sourceWhere("s", key, source);
408
+ const target = sql `${pk} AND s.subscriber_type = ${subscriber.actor}
409
+ AND s.subscription = ${row.command} AND s.subscriber_id = ${subscriber.id}`;
410
+ const rejected = yield* sql.withTransaction(Effect.gen(function* () {
411
+ yield* sql `INSERT INTO actor_generations (routing_key, tenant_id, actor_type, actor_id)
412
+ VALUES (${key}, ${source.tenant}, ${source.actor}, ${source.id}) ON CONFLICT DO NOTHING`;
413
+ const [generation] = yield* sql `
414
+ SELECT event_sequence::text AS head FROM actor_generations g
415
+ WHERE ${eventsOf("g", key, source)} FOR SHARE`;
416
+ const head = BigInt(generation.head);
417
+ const [existing] = yield* sql `
418
+ SELECT s.epoch::text AS epoch, s.active FROM actor_subscriptions s
419
+ WHERE ${target} FOR UPDATE`;
420
+ const refused = change.op === "subscribe" &&
421
+ change.start !== "now" &&
422
+ change.start !== "start" &&
423
+ BigInt(change.start) > head;
424
+ if (existing !== undefined && BigInt(existing.epoch) >= epoch)
425
+ return refused && BigInt(existing.epoch) === epoch && !existing.active;
426
+ const old = sql `SELECT ${rowColumns({ sql, alias: "s" })} FROM actor_subscriptions s
427
+ WHERE ${target} FOR UPDATE`;
428
+ if (change.op === "remove" || refused) {
429
+ yield* changeRows(old, sql `INSERT INTO actor_subscriptions AS s (routing_key, tenant_id, source_type, source_id,
430
+ subscriber_type, subscription, subscriber_id, events, epoch, active, delivered, bucket)
431
+ VALUES (${key}, ${source.tenant}, ${source.actor}, ${source.id}, ${subscriber.actor},
432
+ ${row.command}, ${subscriber.id}, ${textArray({ sql, values: change.events })}, ${epoch}, false,
433
+ ${head}, ${Number(key >> 56n)})
434
+ ON CONFLICT (routing_key, tenant_id, source_type, source_id, subscriber_type, subscription, subscriber_id)
435
+ DO UPDATE SET epoch = EXCLUDED.epoch, active = false, due_at_ms = NULL, attempts = 0,
436
+ last_error = NULL, gap_at_ms = NULL, gap_through = NULL
437
+ RETURNING ${rowColumns({ sql, alias: "s" })}`);
438
+ return refused;
439
+ }
440
+ const delivered = Match.value(change.start).pipe(Match.when("now", () => head), Match.when("start", () => 0n), Match.orElse((cursor) => BigInt(cursor)));
441
+ yield* changeRows(old, sql `INSERT INTO actor_subscriptions AS s (routing_key, tenant_id, source_type, source_id,
442
+ subscriber_type, subscription, subscriber_id, events, epoch, active, delivered, bucket,
443
+ due_at_ms)
444
+ SELECT ${key}, ${source.tenant}, ${source.actor}, ${source.id}, ${subscriber.actor},
445
+ ${row.command}, ${subscriber.id}, x.events, ${epoch}, true, ${delivered},
446
+ ${Number(key >> 56n)},
447
+ CASE WHEN EXISTS (SELECT 1 FROM actor_events e
448
+ WHERE ${eventsOf("e", key, source)}
449
+ AND e.sequence > ${delivered} AND e.sequence <= ${head} AND e.event = ANY(x.events))
450
+ THEN ${now()} END
451
+ FROM (SELECT ${textArray({ sql, values: change.events })} AS events) AS x
452
+ ON CONFLICT (routing_key, tenant_id, source_type, source_id, subscriber_type, subscription, subscriber_id)
453
+ DO UPDATE SET events = EXCLUDED.events, epoch = EXCLUDED.epoch, active = true,
454
+ delivered = EXCLUDED.delivered, marked = 0, due_at_ms = EXCLUDED.due_at_ms, attempts = 0,
455
+ last_error = NULL, gap_at_ms = NULL, gap_through = NULL
456
+ RETURNING ${rowColumns({ sql, alias: "s" })}`);
457
+ return false;
458
+ }));
459
+ const claim = sql `routing_key = ${BigInt(row.routing_key)} AND intent_id = ${row.intent_id}
460
+ AND kind = 'control' AND due_at_ms = ${BigInt(row.claimed_until)}`;
461
+ if (rejected) {
462
+ const outcome = yield* deliverOne(subscriber, yield* handlerOf(subscriber.actor, row.command), source, {
463
+ subscription: row.command,
464
+ epoch: change.epoch,
465
+ kind: "rejected",
466
+ position: change.start,
467
+ }, Number(row.scheduled_at_ms), WireRejected.make({
468
+ subscription: row.command,
469
+ source,
470
+ reason: "UnknownCursor",
471
+ cursor: change.start,
472
+ }));
473
+ const failed = failure(outcome);
474
+ if (failed !== undefined) {
475
+ yield* sql `UPDATE actor_outbox SET last_error = ${failed},
476
+ due_at_ms = ${(yield* databaseTime) + backoff(row.attempts, settings)}
477
+ WHERE ${claim}`;
478
+ return;
479
+ }
480
+ }
481
+ yield* hooks.at("beforeOutboxDelete", hookRequest(source, "$control", row.intent_id));
482
+ yield* sql `DELETE FROM actor_outbox WHERE ${claim}`;
483
+ yield* options.wake;
484
+ });
485
+ const handlerOf = Effect.fnUntraced(function* (subscriberType, tag) {
486
+ const found = options
487
+ .local()
488
+ .find((local) => local.subscriberType === subscriberType && local.subscription.tag === tag);
489
+ if (found === undefined)
490
+ return yield* SubscriptionFailure.make({
491
+ message: `Subscription ${subscriberType}.${tag} is not declared on this runner`,
492
+ });
493
+ return found.subscription.handler;
494
+ });
495
+ /**
496
+ * Delivers one claimed subscription row: up to `batch` matching events
497
+ * after `delivered`, one command at a time in cursor order, each waiting
498
+ * for the previous one's outcome, renewing the claim after each. It then
499
+ * settles `delivered` to the position it scanned through, fenced on the
500
+ * claim and the epoch. A failed delivery settles the progress before it and
501
+ * backs the row off; later events wait behind it.
502
+ */
503
+ const deliverRow = Effect.fnUntraced(function* (row, claim) {
504
+ const local = options
505
+ .local()
506
+ .find(({ subscriberType, subscription }) => subscriberType === row.subscriber_type && subscription.tag === row.subscription);
507
+ const source = { tenant: row.tenant_id, actor: row.source_type, id: row.source_id };
508
+ const key = BigInt(row.routing_key);
509
+ const pk = sql `${sourceWhere("s", key, source)}
510
+ AND s.subscriber_type = ${row.subscriber_type} AND s.subscription = ${row.subscription}
511
+ AND s.subscriber_id = ${row.subscriber_id}`;
512
+ const held = () => sql `${pk} AND s.epoch = ${row.epoch} AND s.due_at_ms = ${claim.lease}`;
513
+ const name = `${row.subscriber_type}.${row.subscription}`;
514
+ const lostClaim = (step) => Effect.logWarning(`Subscription ${step} lost its claim`).pipe(Effect.annotateLogs({ subscription: name }));
515
+ const at = (point) => hooks.at(point, hookRequest(source, row.subscription, row.subscriber_id));
516
+ yield* at("afterClaim");
517
+ const backOff = (progress, cause) => Effect.gen(function* () {
518
+ yield* Effect.logWarning("Subscription delivery failed; retrying with backoff").pipe(Effect.annotateLogs({
519
+ subscription: name,
520
+ source: `${source.actor}/${source.id}`,
521
+ tenant: source.tenant,
522
+ cause,
523
+ }));
524
+ yield* sql `UPDATE actor_subscriptions s SET delivered = greatest(s.delivered, ${progress}),
525
+ last_error = ${cause},
526
+ due_at_ms = ${(yield* databaseTime) + backoff(row.attempts, settings)}
527
+ WHERE ${held()}`;
528
+ yield* count(Metrics.relayRetried, { kind: "subscription" }, 1);
529
+ });
530
+ if (local === undefined)
531
+ return yield* backOff(BigInt(row.delivered), "Undeclared subscription");
532
+ const { subscription } = local;
533
+ const batch = yield* sql `SELECT (SELECT event_sequence::text FROM actor_generations g
534
+ WHERE ${eventsOf("g", key, source)}) AS head,
535
+ (SELECT min(o.sequence)::text FROM actor_events o
536
+ WHERE ${eventsOf("o", key, source)}) AS oldest,
537
+ e.sequence::text AS sequence, e.event, e.command_id, e.value, e.payload_version,
538
+ e.emitted_at_ms::text AS emitted_at_ms
539
+ FROM (VALUES (1)) AS one (x)
540
+ LEFT JOIN LATERAL (
541
+ SELECT sequence, event, command_id, value, payload_version, emitted_at_ms FROM actor_events e
542
+ WHERE ${eventsOf("e", key, source)}
543
+ AND e.sequence > ${BigInt(row.delivered)}
544
+ AND e.event = ANY(${textArray({ sql, values: subscription.events })})
545
+ ORDER BY e.sequence LIMIT ${settings.batch}) e ON true`;
546
+ const head = BigInt(batch[0].head);
547
+ const oldest = batch[0].oldest;
548
+ const events = batch.filter((event) => event.sequence !== null);
549
+ let progress = BigInt(row.delivered);
550
+ let continuous = true;
551
+ let uncountedGaps = 0;
552
+ const settleOutcome = Effect.fnUntraced(function* (outcome, position) {
553
+ const failed = failure(outcome);
554
+ if (failed !== undefined) {
555
+ yield* backOff(progress, failed);
556
+ return false;
557
+ }
558
+ const settled = outcome.success;
559
+ if (Outcome.guards.Acknowledged(settled) &&
560
+ (settled.reason === "Stale" || settled.reason === "Unsubscribed")) {
561
+ yield* deleteRow(row, key, source);
562
+ return false;
563
+ }
564
+ if (Outcome.guards.Acknowledged(settled) && settled.reason === "NotCreated")
565
+ yield* Effect.logInfo("Subscription event skipped: the subscriber is not created").pipe(Effect.annotateLogs({
566
+ subscription: name,
567
+ cursor: String(position),
568
+ }));
569
+ progress = position;
570
+ return yield* renew;
571
+ });
572
+ const renew = Effect.gen(function* () {
573
+ const renewed = yield* sql `UPDATE actor_subscriptions s
574
+ SET due_at_ms = greatest(s.due_at_ms, ${(yield* databaseTime) + settings.claimLeaseMs()})
575
+ WHERE ${held()} RETURNING s.due_at_ms::text AS due_at_ms`;
576
+ if (renewed.length === 0)
577
+ return yield* lostClaim("delivery").pipe(Effect.as(false));
578
+ claim.lease = BigInt(renewed[0].due_at_ms);
579
+ return true;
580
+ });
581
+ const planted = row.gap_through === null ? undefined : BigInt(row.gap_through);
582
+ if (progress < head &&
583
+ ((planted !== undefined && planted > progress) ||
584
+ oldest === null ||
585
+ BigInt(oldest) > progress + 1n)) {
586
+ const resumeAfter = planted !== undefined && planted > progress
587
+ ? planted
588
+ : oldest === null
589
+ ? head
590
+ : BigInt(oldest) - 1n;
591
+ const [gap] = yield* sql `UPDATE actor_subscriptions s
592
+ SET gap_at_ms = coalesce(s.gap_at_ms, ${yield* databaseTime}),
593
+ gap_through = coalesce(s.gap_through, ${resumeAfter})
594
+ WHERE ${held()}
595
+ RETURNING s.gap_at_ms::text AS at, s.gap_through::text AS through`;
596
+ if (gap === undefined)
597
+ return yield* lostClaim("delivery");
598
+ const through = BigInt(gap.through);
599
+ continuous = through === resumeAfter;
600
+ if (subscription.routed === "id") {
601
+ yield* Effect.logWarning("Subscription gap without a recipient").pipe(Effect.annotateLogs({
602
+ subscription: name,
603
+ source: `${source.actor}/${source.id}`,
604
+ after: String(progress),
605
+ resumeAfter: String(through),
606
+ }));
607
+ uncountedGaps = 1;
608
+ progress = through;
609
+ }
610
+ else {
611
+ const outcome = yield* deliverOne({
612
+ tenant: source.tenant,
613
+ actor: row.subscriber_type,
614
+ id: subscription.routed === "singleton" ? "singleton" : row.subscriber_id,
615
+ }, subscription.handler, source, {
616
+ subscription: row.subscription,
617
+ epoch: row.epoch,
618
+ kind: "gap",
619
+ position: String(through),
620
+ }, Number(gap.at), WireGap.make({
621
+ subscription: row.subscription,
622
+ source,
623
+ after: String(progress),
624
+ resumeAfter: String(through),
625
+ }));
626
+ if (!(yield* settleOutcome(outcome, through)))
627
+ return;
628
+ }
629
+ }
630
+ for (const event of continuous ? events.filter((e) => BigInt(e.sequence) > progress) : []) {
631
+ const upcast = yield* subscription
632
+ .upcast(event.event, event.payload_version, decompress(event.value))
633
+ .pipe(Effect.result);
634
+ if (Result.isFailure(upcast))
635
+ return yield* backOff(progress, `Event does not decode: ${upcast.failure.message}`);
636
+ const value = upcast.success;
637
+ const subscriberId = subscription.routed === undefined
638
+ ? Result.succeed(row.subscriber_id)
639
+ : yield* subscription.route(event.event, value, source).pipe(Effect.result);
640
+ if (Result.isFailure(subscriberId))
641
+ return yield* backOff(progress, `Route failed: ${String(subscriberId.failure)}`);
642
+ if (subscriberId.success.length === 0)
643
+ return yield* backOff(progress, "Route failed: route returned an empty id");
644
+ const subscriber = {
645
+ tenant: source.tenant,
646
+ actor: row.subscriber_type,
647
+ id: subscriberId.success,
648
+ };
649
+ const outcome = yield* deliverOne(subscriber, subscription.handler, source, {
650
+ subscription: row.subscription,
651
+ epoch: row.epoch,
652
+ kind: "event",
653
+ position: event.sequence,
654
+ }, Number(event.emitted_at_ms), WireEvent.make({
655
+ subscription: row.subscription,
656
+ source,
657
+ cursor: event.sequence,
658
+ event: yield* decodeJson(value),
659
+ commandId: event.command_id,
660
+ timestamp: DateTime.formatIso(DateTime.makeUnsafe(Number(event.emitted_at_ms))),
661
+ }));
662
+ if (!(yield* settleOutcome(outcome, BigInt(event.sequence))))
663
+ return;
664
+ }
665
+ const delivered = continuous && events.length < settings.batch ? (head > progress ? head : progress) : progress;
666
+ yield* at("beforeSettle");
667
+ const [pending] = yield* sql `SELECT EXISTS (
668
+ SELECT 1 FROM actor_events e
669
+ WHERE ${eventsOf("e", key, source)}
670
+ AND e.sequence > ${delivered} AND e.event = ANY(${textArray({ sql, values: subscription.events })})
671
+ ) AS due`;
672
+ yield* at("afterSettleSnapshot");
673
+ const declared = textArray({ sql, values: subscription.events });
674
+ const settled = yield* changeRows(sql `SELECT ${rowColumns({ sql, alias: "s" })} FROM actor_subscriptions s WHERE ${held()} FOR UPDATE`, sql `UPDATE actor_subscriptions s SET delivered = ${delivered}, attempts = 0, last_error = NULL,
675
+ gaps = s.gaps + ${uncountedGaps}, gap_at_ms = NULL, gap_through = NULL,
676
+ due_at_ms = CASE WHEN ${pending.due} OR s.marked > ${delivered} THEN ${now()} END,
677
+ events = CASE WHEN s.events @> ${declared} THEN s.events
678
+ ELSE ARRAY(SELECT DISTINCT x FROM unnest(s.events || ${declared}) AS u(x) ORDER BY x) END
679
+ WHERE ${held()}
680
+ RETURNING ${rowColumns({ sql, alias: "s" })}`);
681
+ if (settled === 0)
682
+ return yield* lostClaim("settle");
683
+ yield* count(Metrics.relayDelivered, { kind: "subscription" }, 1);
684
+ yield* count(Metrics.undeliverableGaps, { subscriber_type: row.subscriber_type, subscription: row.subscription }, uncountedGaps);
685
+ });
686
+ const release = (work, claim) => Effect.gen(function* () {
687
+ const at = yield* databaseTime;
688
+ if (work.kind !== "subscription")
689
+ return yield* sql `UPDATE actor_outbox SET due_at_ms = ${at}
690
+ WHERE routing_key = ${BigInt(work.routing_key)} AND intent_id = ${work.intent_id}
691
+ AND kind = ${work.kind} AND due_at_ms = ${claim.lease}`;
692
+ return yield* sql `UPDATE actor_subscriptions SET due_at_ms = ${at}
693
+ WHERE routing_key = ${BigInt(work.routing_key)} AND tenant_id = ${work.tenant_id}
694
+ AND source_type = ${work.source_type} AND source_id = ${work.source_id}
695
+ AND subscriber_type = ${work.subscriber_type} AND subscription = ${work.subscription}
696
+ AND subscriber_id = ${work.subscriber_id} AND epoch = ${work.epoch}
697
+ AND due_at_ms = ${claim.lease}`;
698
+ }).pipe(Effect.ignore);
699
+ /**
700
+ * Widens the rows of one dynamic subscription to its declaration's event
701
+ * classes, 1,000 rows per transaction, with their tag summary. It keeps every cursor and epoch, never
702
+ * narrows a row, and is idempotent, so every runner of a rolling deploy may
703
+ * run it. A widened row with an event of an added class after its
704
+ * `delivered` becomes due. Rows claimed right now are skipped; their settle
705
+ * widens them.
706
+ */
707
+ const widen = Effect.fnUntraced(function* (subscriberType, declared) {
708
+ const tags = textArray({ sql, values: declared.events });
709
+ for (;;) {
710
+ const widened = yield* changeRows(sql `SELECT ${rowColumns({ sql, alias: "s" })} FROM actor_subscriptions s
711
+ WHERE s.subscriber_type = ${subscriberType} AND s.subscription = ${declared.tag}
712
+ AND s.active AND NOT s.events @> ${tags}
713
+ LIMIT ${EXPANSION_PAGE}
714
+ FOR UPDATE SKIP LOCKED`, sql `UPDATE actor_subscriptions s
715
+ SET events = ARRAY(SELECT DISTINCT u.x FROM unnest(s.events || ${tags}) AS u(x) ORDER BY u.x),
716
+ due_at_ms = CASE WHEN s.due_at_ms IS NULL AND EXISTS (
717
+ SELECT 1 FROM actor_events e
718
+ WHERE e.routing_key = s.routing_key AND e.tenant_id = s.tenant_id
719
+ AND e.actor_type = s.source_type AND e.actor_id = s.source_id
720
+ AND e.sequence > s.delivered AND e.event = ANY(${tags})
721
+ AND NOT e.event = ANY(o.events))
722
+ THEN ${now()} ELSE s.due_at_ms END
723
+ FROM old_rows o
724
+ WHERE s.routing_key = o.routing_key AND s.tenant_id = o.tenant_id
725
+ AND s.source_type = o.source_type AND s.source_id = o.source_id
726
+ AND s.subscriber_type = o.subscriber_type AND s.subscription = o.subscription
727
+ AND s.subscriber_id = o.subscriber_id
728
+ RETURNING ${rowColumns({ sql, alias: "s" })}`);
729
+ if (widened < EXPANSION_PAGE)
730
+ break;
731
+ }
732
+ });
733
+ /**
734
+ * Deletes rows of `subscriberType` whose subscription this runner doesn't
735
+ * declare once they have been due for a day. Such a row is never claimed
736
+ * here, so a rolling deploy's newer runners get a day to take it first.
737
+ */
738
+ const cleanupRemoved = Effect.fnUntraced(function* (subscriberType, declared) {
739
+ const cutoff = (yield* databaseTime) - REMOVED_AFTER_MS;
740
+ return yield* changeRows(sql `SELECT ${rowColumns({ sql, alias: "s" })} FROM actor_subscriptions s
741
+ WHERE s.subscriber_type = ${subscriberType}
742
+ AND NOT s.subscription = ANY(${textArray({ sql, values: declared })})
743
+ AND s.due_at_ms < ${cutoff}
744
+ FOR UPDATE`, deleteLocked);
745
+ });
746
+ const run = (work, handoff) => {
747
+ const claim = { lease: BigInt(work.claimed_until) };
748
+ const running = work.kind === "subscription"
749
+ ? deliverRow(work, claim).pipe(Effect.withSpan(SpanNames.relaySubscription, {
750
+ kind: "producer",
751
+ attributes: {
752
+ "actor.type": work.subscriber_type,
753
+ "actor.tenant": work.tenant_id,
754
+ "subscription.name": work.subscription,
755
+ "subscription.source_type": work.source_type,
756
+ "relay.attempt": work.attempts,
757
+ },
758
+ }, { captureStackTrace: false }))
759
+ : Match.value(work.kind).pipe(Match.when("feed", () => expand(work, handoff)), Match.orElse(() => register(work)));
760
+ return running.pipe(Effect.onInterrupt(() => release(work, claim)));
761
+ };
762
+ return { claim, decode, run, widen, cleanupRemoved };
763
+ });