@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,38 @@
1
+ import { Context, Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ import type { AnyFleetView } from "../../tables/fleet.ts";
4
+ /** The logical replication slot and publication every fleet view reads. */
5
+ export declare const FLEET_SLOT = "durable_fleet";
6
+ export declare const FLEET_PUBLICATION = "durable_fleet";
7
+ /** How often a runner without the lock tries to take it. */
8
+ export declare const LOCK_RETRY = "2 seconds";
9
+ /**
10
+ * Test points of the fleet engine: `afterApply` runs after a batch's derived
11
+ * rows commit and before the slot advances, where a crash replays the batch;
12
+ * `poll` and `page` count a runner's subscription reads.
13
+ */
14
+ export declare const FleetHooks: Context.Reference<{
15
+ readonly afterApply: Effect.Effect<void>;
16
+ /** Runs before each read of a view's state by a runner's subscription poller. */
17
+ readonly poll: (view: string) => Effect.Effect<void>;
18
+ /** Runs before each page query of a subscription. */
19
+ readonly page: (view: string) => Effect.Effect<void>;
20
+ }>;
21
+ /** A view with the schema names its tables resolve to on this database. */
22
+ export interface ResolvedView {
23
+ readonly view: AnyFleetView;
24
+ readonly sourceSchema: string;
25
+ readonly derivedSchema: string;
26
+ }
27
+ /**
28
+ * Maintains every registered view while this runner holds the fleet lock:
29
+ * peeks a batch of committed changes, recomputes each touched group in one
30
+ * transaction, commits, and only then advances the slot, so a crash between
31
+ * the two replays a batch whose recompute is idempotent. When no change is
32
+ * waiting it rebuilds `building` and `stale` views a few tenants at a time.
33
+ * A view whose recompute fails is marked stale with its error and skipped
34
+ * until `durable fleet rebuild`, so it never stops the others. A slot that
35
+ * is missing, lost, or moved by anyone else marks every view stale. The
36
+ * runner that cannot take the lock retries every `LOCK_RETRY`.
37
+ */
38
+ export declare const maintain: (views: readonly ResolvedView[]) => Effect.Effect<void, never, SqlClient.SqlClient>;
@@ -0,0 +1,383 @@
1
+ import { sql as fragment } from "drizzle-orm";
2
+ import { PgDialect } from "drizzle-orm/pg-core";
3
+ import { Cause, Clock, Context, Effect, Schedule } from "effect";
4
+ import { SqlClient } from "effect/sql";
5
+ import { Coordination } from "../database/coordination.js";
6
+ import { tenantRoutingKey } from "../storage/codec.js";
7
+ import { count, Metrics, record } from "../telemetry/metrics.js";
8
+ import { filterSql } from "../turn/rows.js";
9
+ import { decode } from "./pgoutput.js";
10
+ /** The logical replication slot and publication every fleet view reads. */
11
+ export const FLEET_SLOT = "durable_fleet";
12
+ export const FLEET_PUBLICATION = "durable_fleet";
13
+ /** The session-level advisory lock the one maintainer holds. */
14
+ const LOCK = "akter/fleet";
15
+ /** Changes asked for per peek; the peek still ends on a transaction boundary. */
16
+ const PEEK_CHANGES = 500;
17
+ /** How often an idle maintainer polls the slot. */
18
+ const POLL_INTERVAL = "200 millis";
19
+ /** How often a runner without the lock tries to take it. */
20
+ export const LOCK_RETRY = "2 seconds";
21
+ /** Groups a rebuild writes per poll: about 200 a second. */
22
+ const REBUILD_GROUPS_PER_POLL = 40;
23
+ /**
24
+ * Test points of the fleet engine: `afterApply` runs after a batch's derived
25
+ * rows commit and before the slot advances, where a crash replays the batch;
26
+ * `poll` and `page` count a runner's subscription reads.
27
+ */
28
+ export const FleetHooks = Context.Reference("akter/FleetHooks", {
29
+ defaultValue: () => ({
30
+ afterApply: Effect.void,
31
+ poll: () => Effect.void,
32
+ page: () => Effect.void,
33
+ }),
34
+ });
35
+ const dialect = new PgDialect();
36
+ const identifier = (name) => `"${name.replaceAll('"', '""')}"`;
37
+ const qualified = (schema, table) => `${identifier(schema)}.${identifier(table)}`;
38
+ /** The SQL of each aggregate over its quoted source column. */
39
+ const AGGREGATE_SQL = {
40
+ count: () => "count(*)",
41
+ sum: (column) => `sum(${column})::bigint`,
42
+ avg: (column) => `avg(${column})::float8`,
43
+ min: (column) => `min(${column})`,
44
+ max: (column) => `max(${column})`,
45
+ };
46
+ /**
47
+ * The statement that recomputes one tenant's groups of `view` from the source
48
+ * and writes them: an upsert of every group the source still holds, and a
49
+ * delete of the groups among them it no longer does. `group` narrows it to
50
+ * one group, as a change batch does; without it the whole tenant is rebuilt.
51
+ */
52
+ const recomputeStatement = (resolved, tenant, group, asOf) => {
53
+ const { view } = resolved;
54
+ const source = qualified(resolved.sourceSchema, view.source.table);
55
+ const derived = qualified(resolved.derivedSchema, view.tableName.split(".").at(-1));
56
+ const groupColumns = view.groupColumns.map(identifier);
57
+ const keys = ["tenant_id", ...groupColumns];
58
+ const tenantColumn = identifier(view.tenantColumn);
59
+ const sourceKeys = [`${tenantColumn}::text`, ...groupColumns];
60
+ const grouping = [tenantColumn, ...groupColumns];
61
+ const aggregates = view.aggregates.map(({ kind, column }) => AGGREGATE_SQL[kind](identifier(column ?? "")));
62
+ const columns = [...keys, ...view.aggregates.map(({ key }) => identifier(key)), "as_of"];
63
+ const filter = view.where === undefined
64
+ ? undefined
65
+ : filterSql({ table: view.from, filter: view.where });
66
+ const equal = (prefix) => group === undefined
67
+ ? fragment ``
68
+ : fragment.join(group.map((value, index) => fragment ` AND ${fragment.raw(`${prefix}${groupColumns[index]}`)} = ${value}`));
69
+ const statement = fragment `WITH fresh AS (
70
+ INSERT INTO ${fragment.raw(derived)} (${fragment.raw(columns.join(", "))})
71
+ SELECT ${fragment.raw(sourceKeys.join(", "))}, ${fragment.raw(aggregates.join(", "))}, ${asOf}::numeric
72
+ FROM ${fragment.raw(source)}
73
+ WHERE ${fragment.raw(identifier(view.routingColumn))} = ${String(tenantRoutingKey(tenant))}::bigint
74
+ AND ${fragment.raw(tenantColumn)} = ${tenant}${equal("")}${filter === undefined ? fragment `` : fragment ` AND (${filter})`}
75
+ GROUP BY ${fragment.raw(grouping.join(", "))}
76
+ ON CONFLICT (${fragment.raw(keys.join(", "))}) DO UPDATE SET ${fragment.raw([...view.aggregates.map(({ key }) => identifier(key)), "as_of"]
77
+ .map((column) => `${column} = EXCLUDED.${column}`)
78
+ .join(", "))}
79
+ RETURNING ${fragment.raw(groupColumns.join(", "))}),
80
+ gone AS (
81
+ DELETE FROM ${fragment.raw(derived)} d WHERE d.tenant_id = ${tenant}${equal("d.")}
82
+ AND NOT EXISTS (SELECT 1 FROM fresh f WHERE ${fragment.raw(groupColumns.map((column) => `f.${column} = d.${column}`).join(" AND "))})
83
+ RETURNING 1)
84
+ SELECT (SELECT count(*) FROM fresh)::int + (SELECT count(*) FROM gone)::int AS groups`;
85
+ return dialect.sqlToQuery(statement);
86
+ };
87
+ /** The innermost message of an error's cause chain: the database's own words. */
88
+ const innermost = (error) => {
89
+ let message = error.message;
90
+ let current = error.cause;
91
+ while (current instanceof Error) {
92
+ message = current.message;
93
+ current = current.cause;
94
+ }
95
+ return message;
96
+ };
97
+ /**
98
+ * Maintains every registered view while this runner holds the fleet lock:
99
+ * peeks a batch of committed changes, recomputes each touched group in one
100
+ * transaction, commits, and only then advances the slot, so a crash between
101
+ * the two replays a batch whose recompute is idempotent. When no change is
102
+ * waiting it rebuilds `building` and `stale` views a few tenants at a time.
103
+ * A view whose recompute fails is marked stale with its error and skipped
104
+ * until `durable fleet rebuild`, so it never stops the others. A slot that
105
+ * is missing, lost, or moved by anyone else marks every view stale. The
106
+ * runner that cannot take the lock retries every `LOCK_RETRY`.
107
+ */
108
+ export const maintain = Effect.fnUntraced(function* (views) {
109
+ const sql = yield* SqlClient.SqlClient;
110
+ const coordination = (yield* Coordination) ?? sql;
111
+ const hooks = yield* FleetHooks;
112
+ const holding = Effect.gen(function* () {
113
+ const connection = yield* coordination.reserve;
114
+ const lockOn = (effect) => Effect.provideService(effect, coordination.transactionService, [connection, 0]);
115
+ const [lock] = yield* Effect.acquireRelease(lockOn(coordination `SELECT pg_try_advisory_lock(hashtext(${LOCK})) AS locked`), (locks) => locks[0]?.locked === true
116
+ ? lockOn(coordination `SELECT pg_advisory_unlock(hashtext(${LOCK})) AS unlocked`).pipe(Effect.ignore)
117
+ : Effect.void);
118
+ if (lock?.locked !== true)
119
+ return false;
120
+ const dataConnection = coordination === sql ? connection : yield* sql.reserve;
121
+ const on = (effect) => Effect.provideService(effect, sql.transactionService, [dataConnection, 0]);
122
+ const alive = lockOn(coordination `SELECT 1`).pipe(Effect.andThen(Effect.sleep(POLL_INTERVAL)), Effect.forever);
123
+ return yield* coordination === sql
124
+ ? run(views, on, hooks.afterApply)
125
+ : Effect.raceFirst(run(views, on, hooks.afterApply), alive);
126
+ }).pipe(Effect.scoped);
127
+ yield* holding.pipe(Effect.catchCause((cause) => Cause.hasInterruptsOnly(cause)
128
+ ? Effect.interrupt
129
+ : Effect.logWarning("Fleet maintainer stopped", cause).pipe(Effect.as(false))), Effect.repeat(Schedule.spaced(LOCK_RETRY)));
130
+ });
131
+ const run = Effect.fnUntraced(function* (views, on, afterApply) {
132
+ const sql = yield* SqlClient.SqlClient;
133
+ const relations = new Map();
134
+ const names = views.map(({ view }) => view.name);
135
+ const cursors = new Map();
136
+ let position;
137
+ const now = Clock.currentTimeMillis;
138
+ const markAllStale = Effect.gen(function* () {
139
+ const at = yield* now;
140
+ yield* on(sql `UPDATE actor_fleet_views SET status = 'stale', updated_at_ms = ${at}
141
+ WHERE view_name IN ${sql.in(names)} AND status <> 'stale'`);
142
+ cursors.clear();
143
+ });
144
+ const statuses = on(sql `SELECT view_name, status, last_error FROM actor_fleet_views
145
+ WHERE view_name IN ${sql.in(names)}`).pipe(Effect.map((rows) => new Map(rows.map((row) => [row.view_name, row]))));
146
+ const recompute = (resolved, tenant, group, asOf) => {
147
+ const query = recomputeStatement(resolved, tenant, group, asOf);
148
+ return on(sql.unsafe(query.sql, query.params)).pipe(Effect.map((rows) => rows[0]?.groups ?? 0));
149
+ };
150
+ /** Runs `body` for one view inside a savepoint; a failure marks the view stale with its error. */
151
+ const isolated = (name, body) => on(sql `SAVEPOINT fleet_view`).pipe(Effect.andThen(body), Effect.andThen(on(sql `RELEASE SAVEPOINT fleet_view`)), Effect.as(true), Effect.catchTag("SqlError", (error) => Effect.gen(function* () {
152
+ yield* on(sql `ROLLBACK TO SAVEPOINT fleet_view`);
153
+ const at = yield* now;
154
+ yield* on(sql `UPDATE actor_fleet_views
155
+ SET status = 'stale', last_error = ${innermost(error)}, updated_at_ms = ${at}
156
+ WHERE view_name = ${name}`);
157
+ cursors.delete(name);
158
+ return false;
159
+ })));
160
+ const inTransaction = (body) => on(sql `BEGIN`).pipe(Effect.andThen(body), Effect.tap(() => on(sql `COMMIT`)), Effect.onError(() => on(sql `ROLLBACK`).pipe(Effect.ignore)));
161
+ const touchedBy = (change, touched, truncated) => {
162
+ if (change.kind === "relation")
163
+ relations.set(change.relation.id, change.relation);
164
+ if (change.kind === "truncate") {
165
+ for (const id of change.relations) {
166
+ const relation = relations.get(id);
167
+ for (const { view, sourceSchema } of views)
168
+ if (relation?.schema === sourceSchema && relation.table === view.source.table)
169
+ truncated.add(view.name);
170
+ }
171
+ return;
172
+ }
173
+ if (change.kind !== "insert" && change.kind !== "update" && change.kind !== "delete")
174
+ return;
175
+ const relation = relations.get(change.relation);
176
+ if (relation === undefined)
177
+ return;
178
+ const before = change.kind === "insert" ? undefined : change.before;
179
+ const after = change.kind === "delete" ? undefined : change.after;
180
+ for (const { view, sourceSchema } of views) {
181
+ if (relation.schema !== sourceSchema || relation.table !== view.source.table)
182
+ continue;
183
+ const indexes = [view.tenantColumn, ...view.groupColumns].map((column) => relation.columns.indexOf(column));
184
+ const keyOf = (tuple, fallback) => {
185
+ if (tuple === undefined)
186
+ return undefined;
187
+ const values = [];
188
+ for (const index of indexes) {
189
+ const value = tuple[index] ?? fallback?.[index];
190
+ if (value === null || value === undefined)
191
+ return undefined;
192
+ values.push(value);
193
+ }
194
+ const [tenant, ...rest] = values;
195
+ if (tenant === undefined)
196
+ return undefined;
197
+ const key = [tenant, ...rest];
198
+ return key;
199
+ };
200
+ const groups = touched.get(view.name) ?? new Map();
201
+ for (const key of [keyOf(before, undefined), keyOf(after, before)])
202
+ if (key !== undefined)
203
+ groups.set(JSON.stringify(key), key);
204
+ touched.set(view.name, groups);
205
+ }
206
+ };
207
+ const slot = on(sql `
208
+ SELECT (pg_current_wal_flush_lsn() - '0/0')::text AS flush,
209
+ (s.confirmed_flush_lsn - '0/0')::text AS confirmed, s.wal_status
210
+ FROM (SELECT 1) one LEFT JOIN pg_replication_slots s
211
+ ON s.slot_name = ${FLEET_SLOT} AND s.database = current_database()`).pipe(Effect.map((rows) => rows[0]));
212
+ /**
213
+ * A new maintainer trusts the slot's position, unless a ready view has
214
+ * applied less than it: the slot then moved without this view seeing the
215
+ * changes between, as when it was dropped and recreated.
216
+ */
217
+ const start = Effect.gen(function* () {
218
+ const found = yield* slot;
219
+ if (found.confirmed === null || found.wal_status === "lost")
220
+ return yield* markAllStale;
221
+ position = found.confirmed;
222
+ const behind = yield* on(sql `SELECT view_name FROM actor_fleet_views
223
+ WHERE view_name IN ${sql.in(names)} AND status = 'ready'
224
+ AND (applied_lsn IS NULL OR applied_lsn < ${position}::numeric)`);
225
+ if (behind.length > 0)
226
+ yield* markAllStale;
227
+ });
228
+ let drained = yield* now;
229
+ /** Sets each view's lag gauges: WAL bytes past what was applied, and time since the feed was last drained. */
230
+ const reportLag = (flush) => Effect.gen(function* () {
231
+ const bytes = position === undefined ? 0 : Number(BigInt(flush) - BigInt(position));
232
+ const ms = (yield* now) - drained;
233
+ for (const name of names) {
234
+ yield* record(Metrics.fleetLagBytes, { view: name }, bytes);
235
+ yield* record(Metrics.fleetLag, { view: name }, ms);
236
+ }
237
+ });
238
+ const batch = Effect.gen(function* () {
239
+ const found = yield* slot;
240
+ if (found.confirmed === null || found.wal_status === "lost") {
241
+ if (position !== undefined)
242
+ yield* markAllStale;
243
+ position = undefined;
244
+ return false;
245
+ }
246
+ if (position === undefined || BigInt(found.confirmed) > BigInt(position)) {
247
+ if (position !== undefined)
248
+ yield* markAllStale;
249
+ position = found.confirmed;
250
+ }
251
+ const rows = yield* on(sql `
252
+ SELECT encode(data, 'base64') AS data FROM pg_logical_slot_peek_binary_changes(
253
+ ${FLEET_SLOT}, '0/0'::pg_lsn + ${found.flush}::numeric, ${PEEK_CHANGES},
254
+ 'proto_version', '1', 'publication_names', ${FLEET_PUBLICATION})`);
255
+ const touched = new Map();
256
+ const truncated = new Set();
257
+ let lastEnd;
258
+ for (const row of rows) {
259
+ const change = decode(Buffer.from(row.data, "base64"));
260
+ if (change.kind === "commit")
261
+ lastEnd = change.end;
262
+ else
263
+ touchedBy(change, touched, truncated);
264
+ }
265
+ const target = rows.length >= PEEK_CHANGES && lastEnd !== undefined ? String(lastEnd) : found.flush;
266
+ if (BigInt(target) <= BigInt(position)) {
267
+ drained = yield* now;
268
+ yield* reportLag(found.flush);
269
+ return false;
270
+ }
271
+ const status = yield* statuses;
272
+ const recomputed = new Map();
273
+ yield* inTransaction(Effect.gen(function* () {
274
+ const advanced = [];
275
+ for (const resolved of views) {
276
+ const name = resolved.view.name;
277
+ const row = status.get(name);
278
+ if (row === undefined || row.last_error !== null)
279
+ continue;
280
+ const derived = qualified(resolved.derivedSchema, resolved.view.tableName.split(".").at(-1));
281
+ const groups = [...(touched.get(name)?.values() ?? [])];
282
+ const ok = yield* isolated(name, Effect.gen(function* () {
283
+ if (truncated.has(name)) {
284
+ const tenants = yield* on(sql `SELECT DISTINCT tenant_id FROM ${sql.literal(derived)}`);
285
+ for (const { tenant_id } of tenants)
286
+ yield* recompute(resolved, tenant_id, undefined, target);
287
+ }
288
+ for (const [tenant, ...values] of groups)
289
+ yield* recompute(resolved, tenant, values, target);
290
+ }));
291
+ if (ok)
292
+ recomputed.set(name, groups.length);
293
+ if (ok && row.status === "ready")
294
+ advanced.push(name);
295
+ }
296
+ const at = yield* now;
297
+ if (advanced.length > 0)
298
+ yield* on(sql `UPDATE actor_fleet_views SET applied_lsn = ${target}::numeric, updated_at_ms = ${at}
299
+ WHERE view_name IN ${sql.in(advanced)} AND status = 'ready'
300
+ AND (applied_lsn IS NULL OR applied_lsn < ${target}::numeric)`);
301
+ }));
302
+ yield* afterApply;
303
+ yield* on(sql `SELECT 1 FROM pg_replication_slot_advance(${FLEET_SLOT}, '0/0'::pg_lsn + ${target}::numeric)`);
304
+ position = target;
305
+ yield* count(Metrics.fleetBatches, {}, 1);
306
+ for (const [name, groups] of recomputed)
307
+ yield* count(Metrics.fleetGroupsRecomputed, { view: name }, groups);
308
+ if (target === found.flush)
309
+ drained = yield* now;
310
+ yield* reportLag(found.flush);
311
+ return rows.length > 0;
312
+ });
313
+ /**
314
+ * Rebuilds `building` and stale views without an error, a few tenants per
315
+ * poll, in keyset order over the tenants of the source and of the derived
316
+ * table, so groups that vanished are deleted. Each recompute reads current
317
+ * data and the maintainer is the only writer, so it converges while writes
318
+ * continue; the view is ready, as of the slot's position, after its last
319
+ * tenant.
320
+ */
321
+ const rebuild = Effect.gen(function* () {
322
+ if (position === undefined)
323
+ return;
324
+ const status = yield* statuses;
325
+ let budget = REBUILD_GROUPS_PER_POLL;
326
+ for (const resolved of views) {
327
+ const name = resolved.view.name;
328
+ const row = status.get(name);
329
+ if (row === undefined || row.status === "ready" || row.last_error !== null) {
330
+ cursors.delete(name);
331
+ continue;
332
+ }
333
+ const source = qualified(resolved.sourceSchema, resolved.view.source.table);
334
+ const derived = qualified(resolved.derivedSchema, resolved.view.tableName.split(".").at(-1));
335
+ while (budget > 0) {
336
+ const after = cursors.get(name);
337
+ const [next] = yield* on(sql `SELECT tenant_id FROM (
338
+ SELECT ${sql.literal(identifier(resolved.view.tenantColumn))}::text AS tenant_id
339
+ FROM ${sql.literal(source)}
340
+ UNION SELECT tenant_id FROM ${sql.literal(derived)}) t
341
+ WHERE ${after === undefined ? sql `true` : sql `tenant_id > ${after}`}
342
+ ORDER BY tenant_id LIMIT 1`);
343
+ if (next === undefined) {
344
+ const at = yield* now;
345
+ yield* on(sql `UPDATE actor_fleet_views
346
+ SET status = 'ready', applied_lsn = ${position}::numeric, updated_at_ms = ${at}
347
+ WHERE view_name = ${name} AND status <> 'ready' AND last_error IS NULL`);
348
+ cursors.delete(name);
349
+ break;
350
+ }
351
+ const asOf = position;
352
+ const written = yield* inTransaction(Effect.gen(function* () {
353
+ let groups = 0;
354
+ const ok = yield* isolated(name, recompute(resolved, next.tenant_id, undefined, asOf).pipe(Effect.map((count) => {
355
+ groups = count;
356
+ })));
357
+ return ok ? groups : undefined;
358
+ }));
359
+ if (written === undefined)
360
+ break;
361
+ cursors.set(name, next.tenant_id);
362
+ budget -= Math.max(1, written);
363
+ }
364
+ if (budget <= 0)
365
+ return;
366
+ }
367
+ });
368
+ yield* start;
369
+ return yield* Effect.gen(function* () {
370
+ const changed = yield* batch;
371
+ if (!changed) {
372
+ yield* rebuild;
373
+ yield* Effect.sleep(POLL_INTERVAL);
374
+ }
375
+ }).pipe(Effect.catchTag("SqlError", (error) => /replication slot|logical decoding/i.test(innermost(error))
376
+ ? Effect.gen(function* () {
377
+ if (position !== undefined)
378
+ yield* markAllStale;
379
+ position = undefined;
380
+ yield* Effect.sleep(POLL_INTERVAL);
381
+ })
382
+ : Effect.fail(error)), Effect.forever);
383
+ });
@@ -0,0 +1,41 @@
1
+ /** A tuple's column values in text form: null for SQL null, undefined for an unchanged TOAST value. */
2
+ export type Tuple = ReadonlyArray<string | null | undefined>;
3
+ /** A relation as the stream last described it. */
4
+ export interface Relation {
5
+ readonly id: number;
6
+ readonly schema: string;
7
+ readonly table: string;
8
+ readonly columns: ReadonlyArray<string>;
9
+ }
10
+ /** One decoded pgoutput message the maintainer acts on. */
11
+ export type Change = {
12
+ readonly kind: "relation";
13
+ readonly relation: Relation;
14
+ } | {
15
+ readonly kind: "insert";
16
+ readonly relation: number;
17
+ readonly after: Tuple;
18
+ } | {
19
+ readonly kind: "update";
20
+ readonly relation: number;
21
+ readonly before: Tuple | undefined;
22
+ readonly after: Tuple;
23
+ } | {
24
+ readonly kind: "delete";
25
+ readonly relation: number;
26
+ readonly before: Tuple;
27
+ } | {
28
+ readonly kind: "truncate";
29
+ readonly relations: ReadonlyArray<number>;
30
+ } | {
31
+ readonly kind: "commit";
32
+ readonly end: bigint;
33
+ } | {
34
+ readonly kind: "other";
35
+ };
36
+ /**
37
+ * Decodes one message of the pgoutput protocol, version 1, as
38
+ * `pg_logical_slot_peek_binary_changes` returns it. Messages the maintainer
39
+ * never needs (begin, origin, type, logical messages) decode as `other`.
40
+ */
41
+ export declare const decode: (bytes: Uint8Array) => Change;
@@ -0,0 +1,102 @@
1
+ const text = new TextDecoder();
2
+ /**
3
+ * Decodes one message of the pgoutput protocol, version 1, as
4
+ * `pg_logical_slot_peek_binary_changes` returns it. Messages the maintainer
5
+ * never needs (begin, origin, type, logical messages) decode as `other`.
6
+ */
7
+ export const decode = (bytes) => {
8
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
9
+ let at = 0;
10
+ const byte = () => String.fromCharCode(view.getUint8(at++));
11
+ const int8 = () => view.getInt8(at++);
12
+ const int16 = () => {
13
+ const value = view.getInt16(at);
14
+ at += 2;
15
+ return value;
16
+ };
17
+ const int32 = () => {
18
+ const value = view.getInt32(at);
19
+ at += 4;
20
+ return value;
21
+ };
22
+ const uint64 = () => {
23
+ const value = view.getBigUint64(at);
24
+ at += 8;
25
+ return value;
26
+ };
27
+ const string = () => {
28
+ const end = bytes.indexOf(0, at);
29
+ const value = text.decode(bytes.subarray(at, end));
30
+ at = end + 1;
31
+ return value;
32
+ };
33
+ const tuple = () => {
34
+ const count = int16();
35
+ const values = [];
36
+ for (let index = 0; index < count; index++) {
37
+ const kind = byte();
38
+ if (kind === "n")
39
+ values.push(null);
40
+ else if (kind === "u")
41
+ values.push(undefined);
42
+ else {
43
+ const length = int32();
44
+ values.push(text.decode(bytes.subarray(at, at + length)));
45
+ at += length;
46
+ }
47
+ }
48
+ return values;
49
+ };
50
+ const tag = byte();
51
+ switch (tag) {
52
+ case "R": {
53
+ const id = int32();
54
+ const schema = string();
55
+ const table = string();
56
+ int8();
57
+ const count = int16();
58
+ const columns = [];
59
+ for (let index = 0; index < count; index++) {
60
+ int8();
61
+ columns.push(string());
62
+ int32();
63
+ int32();
64
+ }
65
+ return { kind: "relation", relation: { id, schema, table, columns } };
66
+ }
67
+ case "I": {
68
+ const relation = int32();
69
+ byte();
70
+ return { kind: "insert", relation, after: tuple() };
71
+ }
72
+ case "U": {
73
+ const relation = int32();
74
+ const marker = byte();
75
+ if (marker === "N")
76
+ return { kind: "update", relation, before: undefined, after: tuple() };
77
+ const before = tuple();
78
+ byte();
79
+ return { kind: "update", relation, before, after: tuple() };
80
+ }
81
+ case "D": {
82
+ const relation = int32();
83
+ byte();
84
+ return { kind: "delete", relation, before: tuple() };
85
+ }
86
+ case "T": {
87
+ const count = int32();
88
+ int8();
89
+ const relations = [];
90
+ for (let index = 0; index < count; index++)
91
+ relations.push(int32());
92
+ return { kind: "truncate", relations };
93
+ }
94
+ case "C": {
95
+ int8();
96
+ uint64();
97
+ return { kind: "commit", end: uint64() };
98
+ }
99
+ default:
100
+ return { kind: "other" };
101
+ }
102
+ };
@@ -0,0 +1,36 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ import type { AnyFleetView } from "../../tables/fleet.ts";
4
+ declare const FleetSetupRefused_base: Schema.Class<FleetSetupRefused, Schema.TaggedStruct<"FleetSetupRefused", {
5
+ readonly message: Schema.String;
6
+ }>, import("effect/Cause").YieldableError>;
7
+ /** The server cannot hold fleet views; the message names the fix. */
8
+ export declare class FleetSetupRefused extends FleetSetupRefused_base {
9
+ }
10
+ /** What `setupFleet` changed, for the operator's report. */
11
+ export interface FleetSetup {
12
+ readonly sources: ReadonlyArray<string>;
13
+ readonly slot: "created" | "recreated" | "kept";
14
+ }
15
+ /**
16
+ * The operator step before fleet views run, as a role that may alter the
17
+ * source tables and create publications and slots: gives each source full
18
+ * replica identity, so an update tells the maintainer the group a row left;
19
+ * sets publication `durable_fleet` to exactly the sources; and creates the
20
+ * logical slot, recreating it when it was lost. Refuses a server whose
21
+ * `wal_level` is below logical.
22
+ */
23
+ export declare const setupFleet: (views: readonly AnyFleetView[]) => Effect.Effect<{
24
+ sources: string[];
25
+ slot: "kept";
26
+ } | {
27
+ sources: string[];
28
+ slot: "created" | "recreated";
29
+ }, FleetSetupRefused | import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
30
+ /**
31
+ * Asks the maintainer to rebuild `view` from its source: the view goes back
32
+ * to `building` and its error is cleared, so a poisoned view runs again.
33
+ * Returns false when no runtime has registered the view.
34
+ */
35
+ export declare const rebuildFleetView: (view: string) => Effect.Effect<boolean, import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
36
+ export {};
@@ -0,0 +1,58 @@
1
+ import { Clock, Effect, Schema } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ import { resolveViews } from "./checks.js";
4
+ import { FLEET_PUBLICATION, FLEET_SLOT } from "./maintainer.js";
5
+ /** The server cannot hold fleet views; the message names the fix. */
6
+ export class FleetSetupRefused extends Schema.TaggedError()("FleetSetupRefused", { message: Schema.String }) {
7
+ }
8
+ const identifier = (name) => `"${name.replaceAll('"', '""')}"`;
9
+ /**
10
+ * The operator step before fleet views run, as a role that may alter the
11
+ * source tables and create publications and slots: gives each source full
12
+ * replica identity, so an update tells the maintainer the group a row left;
13
+ * sets publication `durable_fleet` to exactly the sources; and creates the
14
+ * logical slot, recreating it when it was lost. Refuses a server whose
15
+ * `wal_level` is below logical.
16
+ */
17
+ export const setupFleet = Effect.fnUntraced(function* (views) {
18
+ const sql = yield* SqlClient.SqlClient;
19
+ const [level] = yield* sql `SELECT current_setting('wal_level') AS wal_level`;
20
+ if (level?.wal_level !== "logical")
21
+ return yield* FleetSetupRefused.make({
22
+ message: `wal_level is ${level?.wal_level}; set wal_level = logical (ALTER SYSTEM SET wal_level = logical) and restart Postgres`,
23
+ });
24
+ const resolved = yield* resolveViews(views);
25
+ const sources = [
26
+ ...new Set(resolved.map(({ view, sourceSchema }) => `${identifier(sourceSchema)}.${identifier(view.source.table)}`)),
27
+ ];
28
+ for (const source of sources)
29
+ yield* sql.unsafe(`ALTER TABLE ${source} REPLICA IDENTITY FULL`);
30
+ const [publication] = yield* sql `
31
+ SELECT EXISTS (SELECT 1 FROM pg_publication WHERE pubname = ${FLEET_PUBLICATION}) AS found`;
32
+ yield* sql.unsafe(publication?.found === true
33
+ ? `ALTER PUBLICATION ${FLEET_PUBLICATION} SET TABLE ${sources.join(", ")}`
34
+ : `CREATE PUBLICATION ${FLEET_PUBLICATION} FOR TABLE ${sources.join(", ")}
35
+ WITH (publish = 'insert, update, delete, truncate')`);
36
+ const [slot] = yield* sql `
37
+ SELECT wal_status FROM pg_replication_slots
38
+ WHERE slot_name = ${FLEET_SLOT} AND database = current_database()`;
39
+ if (slot !== undefined && slot.wal_status !== "lost")
40
+ return { sources, slot: "kept" };
41
+ if (slot !== undefined)
42
+ yield* sql `SELECT 1 FROM (SELECT pg_drop_replication_slot(${FLEET_SLOT})) dropped`;
43
+ yield* sql `SELECT 1 FROM pg_create_logical_replication_slot(${FLEET_SLOT}, 'pgoutput')`;
44
+ return { sources, slot: slot === undefined ? "created" : "recreated" };
45
+ });
46
+ /**
47
+ * Asks the maintainer to rebuild `view` from its source: the view goes back
48
+ * to `building` and its error is cleared, so a poisoned view runs again.
49
+ * Returns false when no runtime has registered the view.
50
+ */
51
+ export const rebuildFleetView = Effect.fnUntraced(function* (view) {
52
+ const sql = yield* SqlClient.SqlClient;
53
+ const at = yield* Clock.currentTimeMillis;
54
+ const rows = yield* sql `UPDATE actor_fleet_views
55
+ SET status = 'building', last_error = NULL, updated_at_ms = ${at}
56
+ WHERE view_name = ${view} RETURNING view_name`;
57
+ return rows.length > 0;
58
+ });