@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,1040 @@
1
+ import { createServer, connect } from "node:net";
2
+ import { pgTable, text } from "drizzle-orm/pg-core";
3
+ import { Cause, Crypto, Deferred, Duration, Effect, Exit, Fiber, Layer, Option, Redacted, Schedule, Schema, Tracer, } from "effect";
4
+ import { SqlClient } from "effect/sql";
5
+ import { retryPoolRefusal } from "../../runtime/database/bounded.js";
6
+ import { Actor, ActorError, ActorUnavailable, Actors, Intent } from "../../index.js";
7
+ import { Database } from "../../runtime/layer.js";
8
+ import { RetryTurn, TurnHooks } from "../../runtime/turn/hooks.js";
9
+ import { NekiTurnSessions } from "../../runtime/database/neki/session.js";
10
+ import { TurnPoolSettings } from "../../runtime/turn/pipeline.js";
11
+ import { ActorTest, ClusterMember } from "../actor-test.js";
12
+ import { enqueue, holding } from "./batches.js";
13
+ import { SERVED_COMMAND, wireStatements } from "./statements.js";
14
+ const marks = Actor.table(pgTable("pipeline_marks", { id: text("id").primaryKey() }));
15
+ const Add = Actor.command("Add", {
16
+ payload: Schema.Finite,
17
+ success: Schema.Finite,
18
+ });
19
+ const Mark = Actor.command("Mark", {
20
+ payload: Schema.String,
21
+ success: Schema.Finite,
22
+ });
23
+ const Tap = Actor.command("Tap", { success: Schema.Finite });
24
+ const Meter = Actor.make("Meter", {
25
+ key: Schema.String,
26
+ state: Actor.state({
27
+ count: Schema.Finite.pipe(Schema.withDecodingDefault(Effect.succeed(0))),
28
+ }),
29
+ tables: [marks],
30
+ api: { Add, Mark, Tap },
31
+ });
32
+ const Defer = Actor.command("Defer", {
33
+ payload: Schema.Finite,
34
+ success: Schema.Finite,
35
+ });
36
+ const Remind = Actor.command("Remind", {});
37
+ const Ping = Actor.job("Ping", { payload: {}, success: Schema.String });
38
+ const PingLater = Actor.command("PingLater", { payload: Schema.Finite });
39
+ const CancelPing = Actor.command("CancelPing", {});
40
+ const Plain = Actor.make("Plain", {
41
+ key: Schema.String,
42
+ state: Actor.state({
43
+ count: Schema.Finite.pipe(Schema.withDecodingDefault(Effect.succeed(0))),
44
+ }),
45
+ jobs: { Ping: { job: Ping } },
46
+ api: { Add, Defer, PingLater, CancelPing },
47
+ internal: { Remind },
48
+ });
49
+ const AddPayload = Schema.fromJsonString(Schema.Struct({ value: Schema.Finite }));
50
+ const add = Effect.fnUntraced(function* (probe, amount) {
51
+ probe.handled += 1;
52
+ const turn = yield* Meter.Turn;
53
+ yield* turn.state.set({ count: turn.state.count + amount });
54
+ return turn.state.count;
55
+ });
56
+ const actorsLive = (probe) => Layer.unwrap(Effect.gen(function* () {
57
+ const sql = yield* SqlClient.SqlClient;
58
+ yield* retryPoolRefusal(sql.unsafe(`CREATE TABLE IF NOT EXISTS pipeline_marks (
59
+ routing_key bigint, tenant_id text, actor_id text, id text,
60
+ PRIMARY KEY (routing_key, tenant_id, actor_id, id))`));
61
+ return Layer.mergeAll(Meter.toLayer(Effect.succeed({
62
+ Add: (amount) => add(probe, amount),
63
+ Mark: Effect.fnUntraced(function* (id) {
64
+ probe.handled += 1;
65
+ const turn = yield* Meter.Turn;
66
+ yield* turn.rows(marks).insert({ id });
67
+ return yield* turn.rows(marks).count();
68
+ }),
69
+ Tap: Effect.fnUntraced(function* () {
70
+ probe.handled += 1;
71
+ return (yield* Meter.Turn).state.count;
72
+ }),
73
+ })), Plain.toLayer(Effect.succeed({
74
+ Add: Effect.fnUntraced(function* (amount) {
75
+ probe.handled += 1;
76
+ const turn = yield* Plain.Turn;
77
+ yield* turn.state.set({ count: turn.state.count + amount });
78
+ return turn.state.count;
79
+ }),
80
+ Defer: Effect.fnUntraced(function* (pauseMs) {
81
+ probe.handled += 1;
82
+ yield* Effect.sleep(pauseMs);
83
+ const turn = yield* Plain.Turn;
84
+ const self = yield* Plain.intents(turn.id);
85
+ yield* self.Remind().pipe(Intent.after("1 hour"));
86
+ return turn.state.count;
87
+ }),
88
+ Remind: () => Effect.void,
89
+ PingLater: Effect.fnUntraced(function* (pauseMs) {
90
+ probe.handled += 1;
91
+ yield* Effect.sleep(pauseMs);
92
+ yield* (yield* Plain.Turn).enqueue(Ping.make({}), {
93
+ key: "ping",
94
+ after: Duration.hours(1),
95
+ });
96
+ }),
97
+ CancelPing: Effect.fnUntraced(function* () {
98
+ probe.handled += 1;
99
+ yield* (yield* Plain.Turn).cancelJob("ping");
100
+ }),
101
+ })), Plain.toJobLayer(Effect.succeed({ Ping: () => Effect.succeed("pong") })));
102
+ }).pipe(Effect.orDie));
103
+ /** A `CommandComplete` message whose tag is `COMMIT`. */
104
+ const COMMIT_COMPLETE = Buffer.concat([
105
+ Buffer.of(0x43, 0, 0, 0, 11),
106
+ new TextEncoder().encode("COMMIT\0"),
107
+ ]);
108
+ /**
109
+ * A TCP relay in front of Postgres, for the turn pool unless a case routes
110
+ * every pool through it. The relay counts a flight each time the client
111
+ * writes after the server has answered, which is one network round trip no
112
+ * matter how the kernel splits the bytes.
113
+ */
114
+ const relay = (url, probe) => Effect.acquireRelease(Effect.callback((resume) => {
115
+ const sockets = new Set();
116
+ let refusing = false;
117
+ let armed = false;
118
+ let lost = 0;
119
+ const server = createServer((client) => {
120
+ if (refusing)
121
+ return void client.destroy();
122
+ const upstream = connect({
123
+ host: url.hostname,
124
+ port: Number(url.port || 5432),
125
+ });
126
+ let answered = true;
127
+ let losing = false;
128
+ let replies = Buffer.alloc(0);
129
+ const statementsOf = wireStatements();
130
+ sockets.add(client);
131
+ sockets.add(upstream);
132
+ client.setNoDelay(true);
133
+ upstream.setNoDelay(true);
134
+ client.on("data", (chunk) => {
135
+ if (answered) {
136
+ probe.flights += 1;
137
+ probe.sent.push(chunk);
138
+ }
139
+ else
140
+ probe.sent[probe.sent.length - 1] = Buffer.concat([probe.sent.at(-1), chunk]);
141
+ probe.statements += statementsOf(chunk);
142
+ if (armed && chunk.includes("COMMIT")) {
143
+ armed = false;
144
+ losing = true;
145
+ }
146
+ answered = false;
147
+ upstream.write(chunk);
148
+ });
149
+ upstream.on("data", (chunk) => {
150
+ answered = true;
151
+ if (!losing)
152
+ return void client.write(chunk);
153
+ replies = Buffer.concat([replies, chunk]);
154
+ if (!replies.includes(COMMIT_COMPLETE))
155
+ return;
156
+ lost += 1;
157
+ client.destroy();
158
+ upstream.destroy();
159
+ });
160
+ const end = () => {
161
+ client.destroy();
162
+ upstream.destroy();
163
+ };
164
+ client.on("close", end);
165
+ upstream.on("close", end);
166
+ client.on("error", end);
167
+ upstream.on("error", end);
168
+ });
169
+ server.listen(0, "127.0.0.1", () => {
170
+ const address = server.address();
171
+ resume(Effect.succeed({
172
+ port: address.port,
173
+ close: () => {
174
+ for (const socket of sockets)
175
+ socket.destroy();
176
+ server.close();
177
+ },
178
+ cut: () => {
179
+ refusing = true;
180
+ for (const socket of sockets)
181
+ socket.destroy();
182
+ },
183
+ restore: () => {
184
+ refusing = false;
185
+ },
186
+ loseCommitReply: () => {
187
+ armed = true;
188
+ },
189
+ lostCommits: () => lost,
190
+ }));
191
+ });
192
+ }), (listening) => Effect.sync(listening.close));
193
+ /**
194
+ * Runs `body` on a runner over a fresh database whose turn sessions go
195
+ * through the counting relay. `prepare: false` sends every statement's text,
196
+ * so a case can read the order of statements inside a flight.
197
+ */
198
+ const withProbe = (environment, options, body) => environment
199
+ .run(Effect.all([Effect.service(Crypto.Crypto), Effect.service(NekiTurnSessions)]))
200
+ .then(([crypto, neki]) => Effect.runPromise(Effect.gen(function* () {
201
+ yield* Effect.acquireRelease(environment.stop, () => environment.restart);
202
+ const database = yield* environment.freshDatabase;
203
+ if (!Redacted.isRedacted(database))
204
+ return yield* Effect.die(new Error("The pipeline cases need a Postgres database"));
205
+ const probe = { flights: 0, statements: 0, sent: [], handled: 0 };
206
+ const relayed = yield* relay(new URL(Redacted.value(database)), probe);
207
+ const stream = () => connect({ host: "127.0.0.1", port: relayed.port, noDelay: true });
208
+ const context = yield* Layer.build(actorsLive(probe).pipe(Layer.provideMerge(ActorTest.layer({
209
+ database,
210
+ }).pipe(Layer.provide(Layer.succeed(TurnHooks, options.hooks ?? { at: () => Effect.void })))), Layer.provide(Layer.succeed(Tracer.Tracer, options.tracer ?? Tracer.nativeTracer)), Layer.provide(Layer.mergeAll(Layer.succeed(TurnPoolSettings, options.turnSessions === undefined
211
+ ? { stream, prepare: options.prepare !== false }
212
+ : {
213
+ stream,
214
+ prepare: options.prepare !== false,
215
+ maxConnections: options.turnSessions,
216
+ }), Layer.succeed(NekiTurnSessions, neki), options.everyPool === true
217
+ ? Layer.succeed(ClusterMember, { tenant: "pipeline", connect: stream })
218
+ : Layer.empty))));
219
+ return yield* body(probe, database, relayed).pipe(Effect.provideContext(context));
220
+ }).pipe(Effect.scoped, Effect.provideService(Crypto.Crypto, crypto))));
221
+ /** Flights `effect` sends through the relay once warm connections are open. */
222
+ const flightsOf = (probe, effect) => Effect.gen(function* () {
223
+ const before = probe.flights;
224
+ const statements = probe.statements;
225
+ const sent = probe.sent.length;
226
+ const value = yield* effect;
227
+ return {
228
+ value,
229
+ flights: probe.flights - before,
230
+ statements: probe.statements - statements,
231
+ sent: probe.sent.slice(sent),
232
+ };
233
+ });
234
+ const wire = (flight) => flight.toString("latin1");
235
+ /** Positions of `fragments` in `flight`, in the order given; -1 when absent. */
236
+ const positions = (flight, fragments) => fragments.map((fragment) => wire(flight).indexOf(fragment));
237
+ const increasing = (values) => values.every((value, index) => value >= 0 && (index === 0 || value > values[index - 1]));
238
+ /**
239
+ * Queues a rival behind the held turn's lock on the generation table, ahead of
240
+ * the next batch's admission. Postgres grants a queued table lock to its
241
+ * waiter when the holder commits, so a `FOR UPDATE` sent in the same flight as
242
+ * that COMMIT waits behind the rival. A row lock gives no such order: the
243
+ * statement can lock the row it finds committed before the waiting rival wakes,
244
+ * and the batch then runs ahead of the takeover the case needs to fence it.
245
+ */
246
+ const takeoverLock = "LOCK TABLE actor_generations IN EXCLUSIVE MODE";
247
+ /** Another runner's connection to the same database. */
248
+ const rival = (database) => Layer.build(Database.postgres({
249
+ url: database,
250
+ maxConnections: 1,
251
+ offTurnConnections: 1,
252
+ }));
253
+ /**
254
+ * Warms each `Plain` actor with one `Add(1)`; `start` then sends one call to
255
+ * each with a fresh command id and holds every turn before its handler until
256
+ * all of them arrived, so they joined one group before any handed over.
257
+ */
258
+ const gathered = Effect.fnUntraced(function* (keys) {
259
+ const test = yield* ActorTest;
260
+ const actors = yield* Actors;
261
+ for (const key of keys)
262
+ yield* (yield* Plain.get(key)).Add(1);
263
+ const ids = yield* Effect.forEach(keys, () => actors.mintCommandId);
264
+ return {
265
+ start: (call) => Effect.gen(function* () {
266
+ const pauses = yield* Effect.forEach(ids, (commandId) => test.pauseNext("beforeHandler", { commandId }));
267
+ const fibers = yield* Effect.forEach(keys, (key, index) => Effect.flatMap(Plain.get(key), (plain) => Effect.forkChild(call(plain).pipe(Actor.commandId(ids[index]), Effect.orDie))));
268
+ yield* Effect.forEach(pauses, ({ reached }) => reached, { concurrency: "unbounded" });
269
+ return {
270
+ fibers,
271
+ release: Effect.forEach(pauses, ({ release }) => release, { discard: true }),
272
+ };
273
+ }),
274
+ };
275
+ });
276
+ /** Pipeline cases: round trips per turn, statement grouping and order across admission, handler, and commit, and batching of the pipelined worker. */
277
+ export const pipelineConformance = [
278
+ {
279
+ name: "pipeline: a warm turn and a wake each take two round trips, and a replay writes nothing",
280
+ requiresIndependentConnections: true,
281
+ run: ({ expect, environment }) => withProbe(environment, {}, (probe) => Effect.gen(function* () {
282
+ const test = yield* ActorTest;
283
+ const meter = yield* Plain.get("warm");
284
+ const first = yield* flightsOf(probe, meter.Add(1));
285
+ expect(first.value).toBe(1);
286
+ yield* meter.Add(1);
287
+ const warm = yield* flightsOf(probe, meter.Add(1));
288
+ expect(warm).toMatchObject({ value: 3, flights: 2 });
289
+ const wake = yield* flightsOf(probe, (yield* Plain.get("woken")).Add(1));
290
+ expect(wake).toMatchObject({ value: 1, flights: 2 });
291
+ yield* test.invalidate(meter.ref);
292
+ const reloaded = yield* flightsOf(probe, meter.Add(1));
293
+ expect(reloaded).toMatchObject({ value: 4, flights: 4 });
294
+ const id = yield* (yield* Actors).mintCommandId;
295
+ expect(yield* meter.Add(5).pipe(Actor.commandId(id))).toBe(9);
296
+ const handled = probe.handled;
297
+ const replay = yield* flightsOf(probe, meter.Add(5).pipe(Actor.commandId(id)));
298
+ expect(replay.value).toBe(9);
299
+ expect(replay.flights <= 2).toBe(true);
300
+ expect(replay.sent.every((flight) => !wire(flight).includes("INSERT INTO actor_receipts"))).toBe(true);
301
+ expect(probe.handled).toBe(handled);
302
+ expect(yield* test.receiptsFor(meter.ref, "Add")).toBe(5);
303
+ })),
304
+ },
305
+ {
306
+ name: "pipeline: a command and its replay each take two round trips across every pool, reading nothing before delivery or after the turn",
307
+ requiresIndependentConnections: true,
308
+ run: ({ expect, environment }) => withProbe(environment, { everyPool: true }, (probe) => Effect.gen(function* () {
309
+ const meter = yield* Plain.get("every-pool");
310
+ yield* meter.Add(1);
311
+ yield* meter.Add(1);
312
+ const actors = yield* Actors;
313
+ const fewest = (effects) => Effect.map(Effect.forEach(effects, (effect) => flightsOf(probe, effect)), (costs) => ({
314
+ values: costs.map(({ value }) => value),
315
+ flights: Math.min(...costs.map(({ flights }) => flights)),
316
+ statements: Math.min(...costs.map(({ statements }) => statements)),
317
+ }));
318
+ const ids = yield* Effect.forEach(Array.from({ length: 5 }), () => actors.mintCommandId);
319
+ const warm = yield* fewest(ids.map((id) => meter.Add(1).pipe(Actor.commandId(id))));
320
+ expect(warm).toEqual({ values: [3, 4, 5, 6, 7], ...SERVED_COMMAND.warm });
321
+ const id = yield* actors.mintCommandId;
322
+ expect(yield* meter.Add(10).pipe(Actor.commandId(id))).toBe(17);
323
+ const handled = probe.handled;
324
+ const replay = yield* fewest(Array.from(ids, () => meter.Add(10).pipe(Actor.commandId(id))));
325
+ expect(replay).toEqual({ values: [17, 17, 17, 17, 17], ...SERVED_COMMAND.replay });
326
+ expect(probe.handled).toBe(handled);
327
+ })),
328
+ },
329
+ {
330
+ name: "pipeline: each awaited handler statement adds one round trip on the turn session",
331
+ requiresIndependentConnections: true,
332
+ run: ({ expect, environment }) => withProbe(environment, {}, (probe) => Effect.gen(function* () {
333
+ const meter = yield* Meter.get("rows");
334
+ yield* meter.Mark("a");
335
+ yield* meter.Tap();
336
+ const tap = yield* flightsOf(probe, meter.Tap());
337
+ const mark = yield* flightsOf(probe, meter.Mark("b"));
338
+ expect(mark.value).toBe(2);
339
+ expect(mark.flights).toBe(tap.flights + 2);
340
+ expect(tap.flights).toBe(2);
341
+ })),
342
+ },
343
+ {
344
+ name: "pipeline: admission, handler, and commit statements keep their groups and order",
345
+ requiresIndependentConnections: true,
346
+ run: ({ expect, environment }) => withProbe(environment, { prepare: false }, (probe) => Effect.gen(function* () {
347
+ yield* (yield* Plain.get("warmup")).Add(1);
348
+ const meter = yield* Plain.get("order");
349
+ const cold = yield* flightsOf(probe, meter.Add(1));
350
+ const warm = yield* flightsOf(probe, meter.Add(1));
351
+ expect([cold.flights, warm.flights]).toEqual([2, 2]);
352
+ const [coldAdmission, coldCommit] = cold.sent;
353
+ expect(increasing(positions(coldAdmission, [
354
+ "BEGIN",
355
+ "INSERT INTO actor_generations",
356
+ "set_config",
357
+ "FOR UPDATE OF g",
358
+ "UPDATE actor_generations SET generation",
359
+ "SELECT key, value FROM actor_state",
360
+ ]))).toBe(true);
361
+ expect(wire(coldAdmission)).not.toContain("COMMIT");
362
+ expect(increasing(positions(coldCommit, [
363
+ "INSERT INTO actor_state",
364
+ "INSERT INTO actor_receipts",
365
+ "COMMIT",
366
+ "pg_current_wal_insert_lsn()",
367
+ "clock_timestamp()",
368
+ ]))).toBe(true);
369
+ const [warmAdmission, warmCommit] = warm.sent;
370
+ expect(increasing(positions(warmAdmission, ["BEGIN", "set_config", "FOR UPDATE OF g"]))).toBe(true);
371
+ expect(wire(warmAdmission)).not.toContain("INSERT INTO actor_generations");
372
+ expect(wire(warmAdmission)).not.toContain("FROM actor_state");
373
+ expect(wire(warmCommit)).not.toContain("BEGIN");
374
+ expect(increasing(positions(warmCommit, [
375
+ "INSERT INTO actor_state",
376
+ "INSERT INTO actor_receipts",
377
+ "COMMIT",
378
+ "pg_current_wal_insert_lsn()",
379
+ "clock_timestamp()",
380
+ ]))).toBe(true);
381
+ })),
382
+ },
383
+ {
384
+ name: "pipeline: a stale cached generation sends no writes, rolls back, and reloads committed state",
385
+ requiresIndependentConnections: true,
386
+ timeoutMs: 60_000,
387
+ run: ({ expect, environment }) => withProbe(environment, { prepare: false }, (probe, database) => Effect.gen(function* () {
388
+ const test = yield* ActorTest;
389
+ const meter = yield* Plain.get("stale");
390
+ expect(yield* meter.Add(1)).toBe(1);
391
+ expect(yield* meter.Add(1)).toBe(2);
392
+ const before = Number((yield* test.inspect(meter.ref)).generation);
393
+ const context = yield* rival(database);
394
+ yield* Effect.gen(function* () {
395
+ const sql = yield* SqlClient.SqlClient;
396
+ yield* sql.withTransaction(Effect.gen(function* () {
397
+ yield* sql `UPDATE actor_generations SET generation = generation + 1
398
+ WHERE actor_type = 'Plain' AND actor_id = ${meter.ref.id}`;
399
+ yield* sql `DELETE FROM actor_state WHERE actor_type = 'Plain' AND actor_id = ${meter.ref.id}`;
400
+ }));
401
+ }).pipe(Effect.provideContext(context), Effect.orDie);
402
+ const handled = probe.handled;
403
+ const stale = yield* flightsOf(probe, meter.Add(10));
404
+ expect(stale.value).toBe(10);
405
+ expect(probe.handled).toBe(handled + 1);
406
+ const [staleAdmission, staleEnd] = stale.sent;
407
+ expect(wire(staleAdmission)).toContain("FOR UPDATE OF g");
408
+ expect(wire(staleAdmission)).not.toContain("UPDATE actor_generations SET generation");
409
+ expect(wire(staleEnd)).toContain("ROLLBACK");
410
+ expect(wire(staleEnd)).not.toContain("INSERT INTO actor_state");
411
+ expect(wire(staleEnd)).not.toContain("INSERT INTO actor_receipts");
412
+ const inspection = yield* test.inspect(meter.ref);
413
+ expect(inspection).toMatchObject({
414
+ state: { count: 10 },
415
+ receipts: 3,
416
+ });
417
+ expect(Number(inspection.generation)).toBe(before + 2);
418
+ })),
419
+ },
420
+ {
421
+ name: "pipeline: a turn waiting on the fence of a rival takeover reloads instead of committing",
422
+ requiresIndependentConnections: true,
423
+ timeoutMs: 60_000,
424
+ run: ({ expect, environment }) => withProbe(environment, {}, (probe, database) => Effect.gen(function* () {
425
+ const test = yield* ActorTest;
426
+ const meter = yield* Plain.get("fenced");
427
+ expect(yield* meter.Add(1)).toBe(1);
428
+ expect(yield* meter.Add(1)).toBe(2);
429
+ const context = yield* rival(database);
430
+ const handled = probe.handled;
431
+ const add = meter.Add(10);
432
+ let turn;
433
+ yield* Effect.gen(function* () {
434
+ const sql = yield* SqlClient.SqlClient;
435
+ yield* sql.withTransaction(Effect.gen(function* () {
436
+ yield* sql `SELECT 1 FROM actor_generations
437
+ WHERE actor_type = 'Plain' AND actor_id = ${meter.ref.id} FOR UPDATE`;
438
+ turn = yield* Effect.forkDetach(add);
439
+ yield* Effect.gen(function* () {
440
+ const waiting = yield* sql `
441
+ SELECT count(*) > 0 AS waiting FROM pg_stat_activity
442
+ WHERE wait_event_type = 'Lock' AND datname = current_database()
443
+ AND pid <> pg_backend_pid()`;
444
+ if (!waiting[0].waiting)
445
+ return yield* Effect.fail("not yet");
446
+ }).pipe(Effect.retry(Schedule.spaced("20 millis")));
447
+ expect(probe.handled).toBe(handled);
448
+ yield* sql `UPDATE actor_generations SET generation = generation + 1
449
+ WHERE actor_type = 'Plain' AND actor_id = ${meter.ref.id}`;
450
+ yield* sql `DELETE FROM actor_state WHERE actor_type = 'Plain' AND actor_id = ${meter.ref.id}`;
451
+ }));
452
+ }).pipe(Effect.provideContext(context), Effect.orDie);
453
+ expect(yield* Fiber.join(turn)).toBe(10);
454
+ expect(probe.handled).toBe(handled + 1);
455
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
456
+ state: { count: 10 },
457
+ receipts: 3,
458
+ });
459
+ })),
460
+ },
461
+ {
462
+ name: "pipeline: a failed statement in the commit group makes COMMIT roll back, fails the turn, and discards the cache",
463
+ requiresIndependentConnections: true,
464
+ timeoutMs: 60_000,
465
+ run: ({ expect, environment }) => withProbe(environment, { prepare: false }, (probe) => Effect.gen(function* () {
466
+ const sql = yield* SqlClient.SqlClient;
467
+ const test = yield* ActorTest;
468
+ const meter = yield* Plain.get("poisoned");
469
+ expect(yield* meter.Add(1)).toBe(1);
470
+ const handled = probe.handled;
471
+ yield* sql.unsafe(`CREATE SEQUENCE pipeline_poison`);
472
+ yield* sql.unsafe(`CREATE FUNCTION pipeline_poison() RETURNS trigger LANGUAGE plpgsql AS $$
473
+ BEGIN
474
+ IF nextval('pipeline_poison') = 1 THEN RAISE EXCEPTION 'poisoned receipt'; END IF;
475
+ RETURN NEW;
476
+ END $$`);
477
+ yield* sql.unsafe(`CREATE TRIGGER pipeline_poison BEFORE INSERT ON actor_receipts
478
+ FOR EACH ROW WHEN (NEW.actor_type = 'Plain') EXECUTE FUNCTION pipeline_poison()`);
479
+ const sent = probe.sent.length;
480
+ const failed = yield* Effect.exit(meter.Add(2));
481
+ expect(failed._tag).toBe("Failure");
482
+ const flights = probe.sent.slice(sent).map(wire);
483
+ expect(flights.some((flight) => flight.includes("INSERT INTO actor_state") && flight.includes("COMMIT"))).toBe(true);
484
+ expect(probe.handled).toBe(handled + 1);
485
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
486
+ state: { count: 1 },
487
+ receipts: 1,
488
+ });
489
+ expect(yield* meter.Add(4)).toBe(5);
490
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
491
+ state: { count: 5 },
492
+ receipts: 2,
493
+ });
494
+ })),
495
+ },
496
+ {
497
+ name: "pipeline: a crash before commit rolls the turn back and the retry starts a fresh transaction",
498
+ requiresIndependentConnections: true,
499
+ timeoutMs: 60_000,
500
+ run: ({ expect, environment }) => withProbe(environment, {}, (probe) => Effect.gen(function* () {
501
+ const test = yield* ActorTest;
502
+ const meter = yield* Plain.get("crash");
503
+ expect(yield* meter.Add(1)).toBe(1);
504
+ yield* test.crashNext("beforeCommit");
505
+ expect(yield* meter.Add(2)).toBe(3);
506
+ expect(yield* meter.Add(3)).toBe(6);
507
+ expect(probe.handled).toBe(4);
508
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
509
+ state: { count: 6 },
510
+ receipts: 3,
511
+ });
512
+ })),
513
+ },
514
+ {
515
+ name: "pipeline: a crash after commit resolves the retry through the receipt without rerunning the handler",
516
+ requiresIndependentConnections: true,
517
+ timeoutMs: 60_000,
518
+ run: ({ expect, environment }) => withProbe(environment, {}, (probe) => Effect.gen(function* () {
519
+ const test = yield* ActorTest;
520
+ const meter = yield* Plain.get("after-commit");
521
+ expect(yield* meter.Add(1)).toBe(1);
522
+ yield* test.crashNext("afterCommit");
523
+ expect(yield* meter.Add(2)).toBe(3);
524
+ expect(yield* meter.Add(3)).toBe(6);
525
+ expect(probe.handled).toBe(3);
526
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
527
+ state: { count: 6 },
528
+ receipts: 3,
529
+ });
530
+ })),
531
+ },
532
+ {
533
+ name: "pipeline: commands queued behind or arriving after a turn that crashes after commit are answered promptly and commit once",
534
+ requiresIndependentConnections: true,
535
+ timeoutMs: 60_000,
536
+ run: ({ expect, environment }) => {
537
+ const crash = { commandId: undefined, reached: Deferred.makeUnsafe() };
538
+ return withProbe(environment, {
539
+ hooks: {
540
+ at: (point, request) => {
541
+ if (point !== "afterCommit" || request.commandId !== crash.commandId)
542
+ return Effect.void;
543
+ crash.commandId = undefined;
544
+ return Deferred.succeed(crash.reached, undefined).pipe(Effect.andThen(Effect.die(RetryTurn.make({ message: "Injected afterCommit crash" }))));
545
+ },
546
+ },
547
+ }, (probe) => Effect.gen(function* () {
548
+ const test = yield* ActorTest;
549
+ const actors = yield* Actors;
550
+ const meter = yield* Plain.get("queued-behind-crash");
551
+ expect(yield* meter.Add(1)).toBe(1);
552
+ const [crashing, queued, arriving] = yield* Effect.forEach([1, 2, 3], () => actors.mintCommandId);
553
+ crash.commandId = crashing;
554
+ const handled = probe.handled;
555
+ const answered = (call) => call.pipe(Effect.retry({
556
+ while: (error) => error.isRetryable,
557
+ schedule: Schedule.spaced("100 millis"),
558
+ times: 50,
559
+ }), Effect.timeoutOption("5 seconds"), Effect.orDie);
560
+ const first = yield* holding(meter.Add(2).pipe(Actor.commandId(crashing), answered));
561
+ const [behind] = yield* enqueue([meter.Add(3).pipe(Actor.commandId(queued), answered)]);
562
+ yield* first.release;
563
+ yield* Deferred.await(crash.reached);
564
+ const after = yield* Effect.forkChild(meter.Add(4).pipe(Actor.commandId(arriving), answered));
565
+ expect(yield* Fiber.join(first.fiber)).toEqual(Option.some(3));
566
+ expect(Option.isSome(yield* Fiber.join(behind))).toBe(true);
567
+ expect(Option.isSome(yield* Fiber.join(after))).toBe(true);
568
+ expect(probe.handled - handled).toBe(3);
569
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
570
+ state: { count: 10 },
571
+ receipts: 4,
572
+ });
573
+ }));
574
+ },
575
+ },
576
+ {
577
+ name: "pipeline: a turn that chains no batch returns its session before its callers are answered",
578
+ requiresIndependentConnections: true,
579
+ timeoutMs: 60_000,
580
+ run: ({ expect, environment }) => {
581
+ const publishing = { commandId: undefined };
582
+ const reached = Deferred.makeUnsafe();
583
+ const resume = Deferred.makeUnsafe();
584
+ return withProbe(environment, {
585
+ turnSessions: 1,
586
+ hooks: {
587
+ at: (point, request) => point === "afterCommit" && request.commandId === publishing.commandId
588
+ ? Deferred.succeed(reached, undefined).pipe(Effect.andThen(Deferred.await(resume)))
589
+ : Effect.void,
590
+ },
591
+ }, () => Effect.gen(function* () {
592
+ const test = yield* ActorTest;
593
+ const held = yield* Plain.get("publishing");
594
+ const other = yield* Plain.get("waiting-for-a-session");
595
+ expect(yield* held.Add(1)).toBe(1);
596
+ expect(yield* other.Add(1)).toBe(1);
597
+ publishing.commandId = yield* (yield* Actors).mintCommandId;
598
+ const first = yield* Effect.forkChild(held.Add(2).pipe(Actor.commandId(publishing.commandId)));
599
+ yield* Deferred.await(reached);
600
+ expect(yield* other.Add(5).pipe(Effect.timeoutOption("5 seconds"))).toEqual(Option.some(6));
601
+ yield* Deferred.succeed(resume, undefined);
602
+ expect(yield* Fiber.join(first)).toBe(3);
603
+ expect(yield* test.inspect(held.ref)).toMatchObject({
604
+ state: { count: 3 },
605
+ receipts: 2,
606
+ });
607
+ }));
608
+ },
609
+ },
610
+ {
611
+ name: "pipeline: a delayed intent keeps two round trips and is due its delay after commit",
612
+ requiresIndependentConnections: true,
613
+ timeoutMs: 60_000,
614
+ run: ({ expect, environment }) => withProbe(environment, { prepare: false }, (probe) => Effect.gen(function* () {
615
+ const sql = yield* SqlClient.SqlClient;
616
+ const meter = yield* Plain.get("delayed");
617
+ yield* meter.Add(1);
618
+ yield* meter.Add(1);
619
+ const clock = sql `
620
+ SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::text AS now`;
621
+ const before = Number((yield* clock)[0].now);
622
+ const deferred = yield* flightsOf(probe, meter.Defer(400));
623
+ expect(deferred).toMatchObject({ value: 2, flights: 2 });
624
+ const [row] = yield* sql `
625
+ SELECT due_at_ms::text AS due, scheduled_at_ms::text AS scheduled FROM actor_outbox
626
+ WHERE actor_type = 'Plain' AND actor_id = ${meter.ref.id} AND command = 'Remind'`;
627
+ expect(Number(row.due) >= before + 400 + 3_600_000).toBe(true);
628
+ expect(row.scheduled).toBe(row.due);
629
+ const commit = wire(deferred.sent[1]);
630
+ const moved = commit.slice(commit.indexOf("UPDATE actor_outbox"));
631
+ const shift = moved.slice(0, moved.indexOf("WHERE"));
632
+ expect(shift.split("clock_timestamp()").length - 1).toBe(1);
633
+ })),
634
+ },
635
+ {
636
+ name: "pipeline: a batch of waiting commands takes two round trips, and its savepoints add none",
637
+ requiresIndependentConnections: true,
638
+ timeoutMs: 60_000,
639
+ run: ({ expect, environment }) => withProbe(environment, { prepare: false }, (probe) => Effect.gen(function* () {
640
+ const meter = yield* Meter.get("batched");
641
+ yield* meter.Tap();
642
+ const taps = yield* flightsOf(probe, Effect.gen(function* () {
643
+ const first = yield* holding(meter.Tap());
644
+ const waiting = yield* enqueue(Array.from({ length: 8 }, () => Effect.orDie(meter.Tap())));
645
+ const before = probe.flights;
646
+ yield* first.release;
647
+ yield* Fiber.join(first.fiber);
648
+ return { before, replies: yield* Effect.forEach(waiting, Fiber.join) };
649
+ }));
650
+ expect(probe.flights - taps.value.before).toBe(2);
651
+ const batch = probe.sent.slice(-2).map(wire);
652
+ expect(batch[0].indexOf("COMMIT") < batch[0].indexOf("FOR UPDATE OF g")).toBe(true);
653
+ expect(batch[1]).toContain("COMMIT");
654
+ expect(batch.join("").split("SAVEPOINT durable_handler").length - 1).toBe(3);
655
+ const first = yield* holding(meter.Tap());
656
+ const waiting = yield* enqueue(["m1", "m2", "m3", "m4"].map((id) => Effect.orDie(meter.Mark(id))));
657
+ const before = probe.flights;
658
+ yield* first.release;
659
+ yield* Fiber.join(first.fiber);
660
+ expect(yield* Effect.forEach(waiting, Fiber.join)).toEqual([1, 2, 3, 4]);
661
+ expect(probe.flights - before).toBe(1 + 4 * 2 + 1);
662
+ expect(probe.sent
663
+ .slice(-9)
664
+ .map(wire)
665
+ .every((flight) => /insert|select|commit/i.test(flight))).toBe(true);
666
+ })),
667
+ },
668
+ {
669
+ name: "pipeline: the next batch's admission rides in the previous batch's commit flight, and its handlers wait for their own fence",
670
+ requiresIndependentConnections: true,
671
+ timeoutMs: 60_000,
672
+ run: ({ expect, environment }) => withProbe(environment, { prepare: false }, (probe, database) => Effect.gen(function* () {
673
+ const sql = yield* SqlClient.SqlClient;
674
+ const meter = yield* Plain.get("riding");
675
+ expect(yield* meter.Add(1)).toBe(1);
676
+ const context = yield* rival(database);
677
+ const first = yield* holding(meter.Add(1));
678
+ const waiting = yield* enqueue([meter.Add(10), meter.Add(100)].map(Effect.orDie));
679
+ const handled = probe.handled;
680
+ const sent = probe.sent.length;
681
+ const gate = yield* Deferred.make();
682
+ const rivalPid = yield* Deferred.make();
683
+ const lockWaiters = sql `SELECT pid FROM pg_stat_activity
684
+ WHERE wait_event_type = 'Lock' AND datname = current_database()`;
685
+ const waitFor = (found) => Effect.gen(function* () {
686
+ const pids = (yield* lockWaiters).map(({ pid }) => pid);
687
+ if (!found(pids))
688
+ return yield* Effect.fail("not yet");
689
+ }).pipe(Effect.retry(Schedule.spaced("20 millis")), Effect.orDie);
690
+ const takeover = yield* Effect.forkDetach(Effect.gen(function* () {
691
+ const rivalSql = yield* SqlClient.SqlClient;
692
+ yield* rivalSql.withTransaction(Effect.gen(function* () {
693
+ const [row] = yield* rivalSql `SELECT pg_backend_pid() AS pid`;
694
+ yield* Deferred.succeed(rivalPid, row.pid);
695
+ yield* rivalSql.unsafe(takeoverLock);
696
+ yield* Deferred.await(gate);
697
+ yield* rivalSql `UPDATE actor_generations SET generation = generation + 1
698
+ WHERE actor_type = 'Plain' AND actor_id = ${meter.ref.id}`;
699
+ }));
700
+ }).pipe(Effect.provideContext(context), Effect.orDie));
701
+ const pid = yield* Deferred.await(rivalPid);
702
+ yield* waitFor((pids) => pids.includes(pid));
703
+ yield* first.release;
704
+ expect(yield* Fiber.join(first.fiber)).toBe(2);
705
+ yield* waitFor((pids) => pids.some((waiter) => waiter !== pid));
706
+ expect(probe.handled).toBe(handled);
707
+ const riding = probe.sent
708
+ .slice(sent)
709
+ .map(wire)
710
+ .find((flight) => flight.includes("COMMIT") && flight.includes("FOR UPDATE OF g"));
711
+ expect(riding === undefined).toBe(false);
712
+ expect(riding.indexOf("COMMIT") < riding.indexOf("FOR UPDATE OF g")).toBe(true);
713
+ yield* Deferred.succeed(gate, undefined);
714
+ yield* Fiber.join(takeover);
715
+ expect(yield* Effect.forEach(waiting, Fiber.join)).toEqual([12, 112]);
716
+ expect(probe.handled).toBe(handled + 2);
717
+ })),
718
+ },
719
+ {
720
+ name: "pipeline: a batch whose commit fails rolls back the next batch before its handlers run, and every caller retries once",
721
+ requiresIndependentConnections: true,
722
+ timeoutMs: 60_000,
723
+ run: ({ expect, environment }) => withProbe(environment, { prepare: false }, (probe) => Effect.gen(function* () {
724
+ const sql = yield* SqlClient.SqlClient;
725
+ const test = yield* ActorTest;
726
+ const meter = yield* Plain.get("failing");
727
+ expect(yield* meter.Add(1)).toBe(1);
728
+ const handled = probe.handled;
729
+ const first = yield* holding(meter.Add(1));
730
+ const failing = yield* enqueue([meter.Add(10), meter.Add(100)].map(Effect.orDie));
731
+ const paused = yield* test.pauseNext("beforeCommit");
732
+ yield* first.release;
733
+ expect(yield* Fiber.join(first.fiber)).toBe(2);
734
+ yield* paused.reached;
735
+ const next = yield* enqueue([meter.Add(1000), meter.Add(10000)].map(Effect.orDie));
736
+ yield* sql.unsafe(`CREATE SEQUENCE pipeline_batch_poison`);
737
+ yield* sql.unsafe(`CREATE FUNCTION pipeline_batch_poison() RETURNS trigger LANGUAGE plpgsql AS $$
738
+ BEGIN
739
+ IF nextval('pipeline_batch_poison') = 1 THEN RAISE EXCEPTION 'poisoned receipt'; END IF;
740
+ RETURN NEW;
741
+ END $$`);
742
+ yield* sql.unsafe(`CREATE TRIGGER pipeline_batch_poison BEFORE INSERT ON actor_receipts
743
+ FOR EACH ROW WHEN (NEW.actor_type = 'Plain') EXECUTE FUNCTION pipeline_batch_poison()`);
744
+ const sent = probe.sent.length;
745
+ yield* paused.release;
746
+ expect(yield* Effect.forEach(failing, Fiber.join)).toEqual([12, 112]);
747
+ expect(yield* Effect.forEach(next, Fiber.join)).toEqual([1112, 11112]);
748
+ expect(probe.sent
749
+ .slice(sent)
750
+ .map(wire)
751
+ .some((flight) => flight.includes("INSERT INTO actor_receipts") &&
752
+ flight.indexOf("COMMIT") < flight.lastIndexOf("FOR UPDATE OF g"))).toBe(true);
753
+ expect(probe.handled - handled).toBe(1 + 2 * 2 + 2);
754
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
755
+ state: { count: 11112 },
756
+ receipts: 6,
757
+ });
758
+ })),
759
+ },
760
+ {
761
+ name: "pipeline: a later batch stays hidden until its own commit",
762
+ requiresIndependentConnections: true,
763
+ timeoutMs: 60_000,
764
+ run: ({ expect, environment }) => withProbe(environment, {}, () => Effect.gen(function* () {
765
+ const sql = yield* SqlClient.SqlClient;
766
+ const test = yield* ActorTest;
767
+ const meter = yield* Plain.get("hidden");
768
+ expect(yield* meter.Add(1)).toBe(1);
769
+ const first = yield* holding(meter.Add(1));
770
+ const later = yield* enqueue([meter.Defer(0), meter.Defer(0)].map(Effect.orDie));
771
+ const paused = yield* test.pauseNext("beforeCommit");
772
+ yield* first.release;
773
+ expect(yield* Fiber.join(first.fiber)).toBe(2);
774
+ yield* paused.reached;
775
+ const reminders = sql `SELECT count(*)::integer AS count
776
+ FROM actor_outbox WHERE actor_type = 'Plain' AND actor_id = ${meter.ref.id}`;
777
+ expect(later.map((fiber) => fiber.pollUnsafe())).toEqual([undefined, undefined]);
778
+ expect((yield* reminders)[0].count).toBe(0);
779
+ yield* paused.release;
780
+ expect(yield* Effect.forEach(later, Fiber.join)).toEqual([2, 2]);
781
+ expect((yield* reminders)[0].count).toBe(2);
782
+ })),
783
+ },
784
+ {
785
+ name: "pipeline: a defect after a batch commits restarts the activation, and every caller gets its committed outcome",
786
+ requiresIndependentConnections: true,
787
+ timeoutMs: 60_000,
788
+ run: ({ expect, environment }) => {
789
+ const failing = new Set([2, 20]);
790
+ const hooks = {
791
+ at: (point, request) => point !== "afterCommit" || request.command !== "Add"
792
+ ? Effect.void
793
+ : Schema.decodeEffect(AddPayload)(request.payload).pipe(Effect.orDie, Effect.flatMap(({ value }) => failing.delete(value)
794
+ ? Effect.die(new Error("Defect after commit"))
795
+ : Effect.void)),
796
+ };
797
+ return withProbe(environment, { hooks }, (probe) => Effect.gen(function* () {
798
+ const test = yield* ActorTest;
799
+ const meter = yield* Plain.get("after-commit-defect");
800
+ expect(yield* meter.Add(1)).toBe(1);
801
+ const handled = probe.handled;
802
+ expect(yield* meter.Add(2)).toBe(3);
803
+ expect(probe.handled).toBe(handled + 1);
804
+ const first = yield* holding(meter.Add(4));
805
+ const waiting = yield* enqueue([meter.Add(20), meter.Add(40)].map(Effect.orDie));
806
+ yield* first.release;
807
+ expect(yield* Fiber.join(first.fiber)).toBe(7);
808
+ expect(yield* Effect.forEach(waiting, Fiber.join)).toEqual([27, 67]);
809
+ expect(probe.handled).toBe(handled + 4);
810
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
811
+ state: { count: 67 },
812
+ receipts: 5,
813
+ });
814
+ }));
815
+ },
816
+ },
817
+ {
818
+ name: "pipeline: each pipelined batch has its own span, not the span of the batch before it",
819
+ requiresIndependentConnections: true,
820
+ timeoutMs: 60_000,
821
+ run: ({ expect, environment }) => {
822
+ const spans = [];
823
+ const tracer = Tracer.make({
824
+ span: (options) => {
825
+ const span = new Tracer.NativeSpan(options);
826
+ spans.push(span);
827
+ return span;
828
+ },
829
+ });
830
+ return withProbe(environment, { tracer }, () => Effect.gen(function* () {
831
+ const meter = yield* Plain.get("spans");
832
+ yield* meter.Add(1);
833
+ const held = yield* (yield* Actors).mintCommandId;
834
+ const before = spans.length;
835
+ const first = yield* holding(meter.Add(1).pipe(Actor.commandId(held)));
836
+ const waiting = yield* enqueue([meter.Add(10), meter.Add(100)].map(Effect.orDie));
837
+ yield* first.release;
838
+ yield* Fiber.join(first.fiber);
839
+ yield* Effect.forEach(waiting, Fiber.join);
840
+ const turns = spans.slice(before).filter((span) => span.name.startsWith("akter.Plain/"));
841
+ const lone = turns.filter((span) => span.attributes.get("command.id") === held);
842
+ const batch = turns.filter((span) => span.name === "akter.Plain/batch");
843
+ expect(lone.map((span) => span.name)).toEqual(["akter.Plain/Add"]);
844
+ expect(batch.map((span) => span.attributes.get("batch.size"))).toEqual([2]);
845
+ expect(batch[0].links.length).toBe(2);
846
+ expect(Option.getOrUndefined(batch[0].parent)?.spanId === lone[0].spanId).toBe(false);
847
+ }));
848
+ },
849
+ },
850
+ {
851
+ name: "pipeline: a keyed delayed effect, its replacement, and its cancellation each keep two round trips",
852
+ requiresIndependentConnections: true,
853
+ timeoutMs: 60_000,
854
+ run: ({ expect, environment }) => withProbe(environment, {}, (probe) => Effect.gen(function* () {
855
+ const sql = yield* SqlClient.SqlClient;
856
+ const plain = yield* Plain.get("pinged");
857
+ yield* plain.Add(1);
858
+ yield* plain.Add(1);
859
+ const clock = sql `
860
+ SELECT floor(extract(epoch FROM clock_timestamp()) * 1000)::text AS now`;
861
+ const pings = sql `
862
+ SELECT due_at_ms::text AS due, ready_at_ms::text AS ready, timer_key AS key
863
+ FROM actor_outbox WHERE actor_type = 'Plain' AND actor_id = ${plain.ref.id}
864
+ AND kind = 'job' AND command = 'Ping'`;
865
+ const before = Number((yield* clock)[0].now);
866
+ expect((yield* flightsOf(probe, plain.PingLater(400))).flights).toBe(2);
867
+ const [first] = yield* pings;
868
+ expect(Number(first.due) >= before + 400 + 3_600_000).toBe(true);
869
+ expect(first.ready).toBe(first.due);
870
+ expect((yield* flightsOf(probe, plain.PingLater(0))).flights).toBe(2);
871
+ expect((yield* pings).length).toBe(1);
872
+ expect((yield* flightsOf(probe, plain.CancelPing())).flights).toBe(2);
873
+ expect(yield* pings).toEqual([]);
874
+ })),
875
+ },
876
+ {
877
+ name: "pipeline: an unreachable database fails a command id mint as ActorUnavailable, not a defect",
878
+ requiresIndependentConnections: true,
879
+ run: ({ expect, environment }) => withProbe(environment, { everyPool: true }, (_probe, _database, relayed) => Effect.gen(function* () {
880
+ const meter = yield* Plain.get("unreachable");
881
+ expect(yield* meter.Add(1)).toBe(1);
882
+ const unavailable = (exit) => {
883
+ if (Exit.isSuccess(exit) || Cause.hasDies(exit.cause))
884
+ return false;
885
+ const error = Cause.findErrorOption(exit.cause);
886
+ return (Option.isSome(error) &&
887
+ Schema.is(ActorError)(error.value) &&
888
+ Schema.is(ActorUnavailable)(error.value.reason));
889
+ };
890
+ relayed.cut();
891
+ const minted = yield* Effect.exit((yield* Actors).mintCommandId);
892
+ const called = yield* Effect.exit(meter.Add(1));
893
+ relayed.restore();
894
+ expect(unavailable(minted)).toBe(true);
895
+ expect(unavailable(called)).toBe(true);
896
+ const retried = yield* meter
897
+ .Add(1)
898
+ .pipe(Effect.retry({ schedule: Schedule.spaced("100 millis"), times: 50 }));
899
+ expect(retried).toBe(2);
900
+ })),
901
+ },
902
+ {
903
+ name: "pipeline: a turn that loses the database mid-commit is answered, and its caller's retry commits it once",
904
+ requiresIndependentConnections: true,
905
+ timeoutMs: 60_000,
906
+ run: ({ expect, environment }) => withProbe(environment, { everyPool: true }, (probe, _database, relayed) => Effect.gen(function* () {
907
+ const test = yield* ActorTest;
908
+ const meter = yield* Plain.get("lost-mid-commit");
909
+ expect(yield* meter.Add(1)).toBe(1);
910
+ const id = yield* (yield* Actors).mintCommandId;
911
+ const committing = yield* test.pauseNext("beforeCommit");
912
+ const handled = probe.handled;
913
+ const call = yield* meter.Add(2).pipe(Actor.commandId(id), Effect.retry({
914
+ while: (error) => error.isRetryable,
915
+ schedule: Schedule.spaced("100 millis"),
916
+ times: 50,
917
+ }), Effect.timeoutOption("10 seconds"), Effect.forkChild);
918
+ yield* committing.reached;
919
+ relayed.cut();
920
+ yield* committing.release;
921
+ yield* Effect.sleep("1 second");
922
+ relayed.restore();
923
+ expect(yield* Fiber.join(call)).toEqual(Option.some(3));
924
+ expect(probe.handled - handled).toBe(2);
925
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
926
+ state: { count: 3 },
927
+ receipts: 2,
928
+ });
929
+ })),
930
+ },
931
+ {
932
+ name: "pipeline: a turn whose COMMIT applied but whose reply was lost is answered from its receipt, never rerun or failed",
933
+ requiresIndependentConnections: true,
934
+ timeoutMs: 60_000,
935
+ run: ({ expect, environment }) => withProbe(environment, { prepare: false }, (probe, _database, relayed) => Effect.gen(function* () {
936
+ const test = yield* ActorTest;
937
+ const meter = yield* Plain.get("lost-commit-reply");
938
+ expect(yield* meter.Add(1)).toBe(1);
939
+ const id = yield* (yield* Actors).mintCommandId;
940
+ const handled = probe.handled;
941
+ relayed.loseCommitReply();
942
+ const reply = yield* meter
943
+ .Add(2)
944
+ .pipe(Actor.commandId(id), Effect.timeoutOption("20 seconds"));
945
+ expect(relayed.lostCommits()).toBe(1);
946
+ expect(reply).toEqual(Option.some(3));
947
+ expect(probe.handled - handled).toBe(1);
948
+ expect(yield* meter.Add(2).pipe(Actor.commandId(id))).toBe(3);
949
+ expect(probe.handled - handled).toBe(1);
950
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
951
+ state: { count: 3 },
952
+ receipts: 2,
953
+ });
954
+ })),
955
+ },
956
+ {
957
+ name: "pipeline: a batch that loses the database mid-commit answers every command in it, and each caller's retry commits once",
958
+ requiresIndependentConnections: true,
959
+ timeoutMs: 60_000,
960
+ run: ({ expect, environment }) => withProbe(environment, { everyPool: true }, (probe, _database, relayed) => Effect.gen(function* () {
961
+ const test = yield* ActorTest;
962
+ const actors = yield* Actors;
963
+ const meter = yield* Plain.get("batch-lost-mid-commit");
964
+ expect(yield* meter.Add(1)).toBe(1);
965
+ const amounts = [2, 3, 4];
966
+ const ids = yield* Effect.forEach(amounts, () => actors.mintCommandId);
967
+ const handled = probe.handled;
968
+ const first = yield* holding(meter.Add(10));
969
+ const calls = yield* enqueue(amounts.map((amount, index) => meter.Add(amount).pipe(Actor.commandId(ids[index]), Effect.retry({
970
+ while: (error) => error.isRetryable,
971
+ schedule: Schedule.spaced("100 millis"),
972
+ times: 50,
973
+ }), Effect.timeoutOption("10 seconds"), Effect.orDie)));
974
+ const inBatch = yield* test.pauseNext("beforeCommit");
975
+ const thenInBatch = yield* test.pauseNext("beforeCommit");
976
+ yield* first.release;
977
+ yield* Fiber.join(first.fiber);
978
+ yield* inBatch.reached;
979
+ yield* inBatch.release;
980
+ yield* thenInBatch.reached;
981
+ expect(probe.handled - handled).toBe(1 + 2);
982
+ relayed.cut();
983
+ yield* thenInBatch.release;
984
+ yield* Effect.sleep("1 second");
985
+ relayed.restore();
986
+ const replies = yield* Effect.forEach(calls, Fiber.join);
987
+ expect(replies.every(Option.isSome)).toBe(true);
988
+ expect(probe.handled - handled).toBe(1 + amounts.length * 2);
989
+ expect(yield* test.inspect(meter.ref)).toMatchObject({
990
+ state: { count: 20 },
991
+ receipts: 5,
992
+ });
993
+ })),
994
+ },
995
+ {
996
+ name: "pipeline: concurrent warm turns of three actors share one BEGIN, settings statement, COMMIT and version read: thirteen statements",
997
+ requiresIndependentConnections: true,
998
+ timeoutMs: 60_000,
999
+ run: ({ expect, environment }) => withProbe(environment, { prepare: false }, (probe) => Effect.gen(function* () {
1000
+ const test = yield* ActorTest;
1001
+ const ids = yield* gathered(["shared-a", "shared-b", "shared-c"]);
1002
+ const statements = probe.statements;
1003
+ const sent = probe.sent.length;
1004
+ const { fibers, release } = yield* ids.start((plain) => plain.Add(1));
1005
+ yield* release;
1006
+ expect(yield* Effect.forEach(fibers, Fiber.join)).toEqual([2, 2, 2]);
1007
+ expect(probe.statements - statements).toBe(13);
1008
+ const wired = probe.sent.slice(sent).map(wire).join("");
1009
+ expect(wired.split("BEGIN").length - 1).toBe(1);
1010
+ expect(wired.split("COMMIT").length - 1).toBe(1);
1011
+ expect(wired.split("pg_current_wal_insert_lsn").length - 1).toBe(1);
1012
+ expect(yield* test.receiptsFor((yield* Plain.get("shared-a")).ref, "Add")).toBe(2);
1013
+ })),
1014
+ },
1015
+ {
1016
+ name: "pipeline: a group whose COMMIT applied but whose reply was lost answers every member from its receipt, with one handler run each",
1017
+ requiresIndependentConnections: true,
1018
+ timeoutMs: 60_000,
1019
+ run: ({ expect, environment }) => withProbe(environment, { prepare: false }, (probe, _database, relayed) => Effect.gen(function* () {
1020
+ const test = yield* ActorTest;
1021
+ const ids = yield* gathered(["lost-a", "lost-b", "lost-c"]);
1022
+ const handled = probe.handled;
1023
+ const { fibers, release } = yield* ids.start((plain) => plain.Add(2).pipe(Effect.timeoutOption("20 seconds")));
1024
+ relayed.loseCommitReply();
1025
+ yield* release;
1026
+ expect(yield* Effect.forEach(fibers, Fiber.join)).toEqual([
1027
+ Option.some(3),
1028
+ Option.some(3),
1029
+ Option.some(3),
1030
+ ]);
1031
+ expect(relayed.lostCommits()).toBe(1);
1032
+ expect(probe.handled - handled).toBe(3);
1033
+ for (const key of ["lost-a", "lost-b", "lost-c"])
1034
+ expect(yield* test.inspect((yield* Plain.get(key)).ref)).toMatchObject({
1035
+ state: { count: 3 },
1036
+ receipts: 2,
1037
+ });
1038
+ })),
1039
+ },
1040
+ ];