@rikalabs/akter 0.1.0-alpha.0 → 0.1.0-alpha.3

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 (308) hide show
  1. package/CHANGELOG.md +46 -3
  2. package/README.md +2 -2
  3. package/dist/actor/definition.d.ts +13 -9
  4. package/dist/actor/descriptor.d.ts +2 -2
  5. package/dist/actor/descriptor.js +4 -4
  6. package/dist/client/calls.js +1 -1
  7. package/dist/client/index.d.ts +1 -1
  8. package/dist/client/index.js +1 -1
  9. package/dist/errors/actor.d.ts +12 -42
  10. package/dist/errors/actor.js +13 -42
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/members/payload.d.ts +1 -1
  14. package/dist/protocol/inspection.d.ts +221 -1
  15. package/dist/protocol/inspection.js +151 -2
  16. package/dist/protocol/wire.d.ts +6 -49
  17. package/dist/protocol/wire.js +1 -10
  18. package/dist/runtime/actors.d.ts +3 -0
  19. package/dist/runtime/adoption/backfill.js +1 -1
  20. package/dist/runtime/adoption/enforce.js +2 -2
  21. package/dist/runtime/adoption/observe.js +1 -1
  22. package/dist/runtime/adoption/plan.d.ts +1 -1
  23. package/dist/runtime/adoption/startup.js +1 -1
  24. package/dist/runtime/adoption/status.d.ts +1 -1
  25. package/dist/runtime/app.d.ts +65 -0
  26. package/dist/runtime/app.js +38 -0
  27. package/dist/runtime/connections/holder.d.ts +11 -1
  28. package/dist/runtime/connections/holder.js +8 -4
  29. package/dist/runtime/connections/streams.d.ts +4 -4
  30. package/dist/runtime/content/store.js +15 -11
  31. package/dist/runtime/database/bounded.d.ts +4 -18
  32. package/dist/runtime/database/bounded.js +36 -22
  33. package/dist/runtime/database/coordination.d.ts +12 -3
  34. package/dist/runtime/database/coordination.js +29 -36
  35. package/dist/runtime/database/migrations.d.ts +61 -29
  36. package/dist/runtime/database/migrations.js +375 -139
  37. package/dist/runtime/database/replica.d.ts +4 -1
  38. package/dist/runtime/database/replica.js +4 -1
  39. package/dist/runtime/database/schema.d.ts +13 -0
  40. package/dist/runtime/database/schema.js +13 -0
  41. package/dist/runtime/database/shards.d.ts +31 -13
  42. package/dist/runtime/database/shards.js +38 -26
  43. package/dist/runtime/entity/register.d.ts +8 -1
  44. package/dist/runtime/entity/register.js +58 -7
  45. package/dist/runtime/fleet/checks.js +4 -4
  46. package/dist/runtime/fleet/maintainer.d.ts +1 -1
  47. package/dist/runtime/fleet/maintainer.js +1 -1
  48. package/dist/runtime/index.d.ts +2 -0
  49. package/dist/runtime/index.js +1 -0
  50. package/dist/runtime/inspector/layer.d.ts +20 -0
  51. package/dist/runtime/inspector/layer.js +224 -5
  52. package/dist/runtime/inspector/queries.d.ts +54 -8
  53. package/dist/runtime/inspector/queries.js +84 -15
  54. package/dist/runtime/layer.d.ts +48 -39
  55. package/dist/runtime/layer.js +124 -56
  56. package/dist/runtime/operators/audit.d.ts +3 -0
  57. package/dist/runtime/operators/audit.js +6 -2
  58. package/dist/runtime/operators/repair.js +30 -10
  59. package/dist/runtime/operators/seed.js +4 -2
  60. package/dist/runtime/payloads/versions.d.ts +4 -4
  61. package/dist/runtime/payloads/versions.js +24 -15
  62. package/dist/runtime/queries.js +4 -2
  63. package/dist/runtime/registration.d.ts +5 -2
  64. package/dist/runtime/registration.js +3 -3
  65. package/dist/runtime/runner.d.ts +12 -0
  66. package/dist/runtime/runner.js +12 -0
  67. package/dist/runtime/storage/codec.d.ts +19 -7
  68. package/dist/runtime/storage/codec.js +23 -7
  69. package/dist/runtime/storage/placements.d.ts +6 -1
  70. package/dist/runtime/storage/placements.js +98 -6
  71. package/dist/runtime/storage/retention.d.ts +6 -0
  72. package/dist/runtime/storage/retention.js +25 -13
  73. package/dist/runtime/subscriptions/relay.js +47 -44
  74. package/dist/runtime/subscriptions/storage.d.ts +3 -2
  75. package/dist/runtime/subscriptions/storage.js +9 -6
  76. package/dist/runtime/telemetry/live.d.ts +255 -0
  77. package/dist/runtime/telemetry/live.js +540 -0
  78. package/dist/runtime/telemetry/metrics.d.ts +2 -0
  79. package/dist/runtime/telemetry/metrics.js +4 -1
  80. package/dist/runtime/telemetry/sampler.js +33 -13
  81. package/dist/runtime/telemetry/spans.d.ts +1 -1
  82. package/dist/runtime/telemetry/spans.js +1 -1
  83. package/dist/runtime/topology/locks.d.ts +6 -3
  84. package/dist/runtime/topology/locks.js +27 -4
  85. package/dist/runtime/turn/execute.d.ts +2 -0
  86. package/dist/runtime/turn/execute.js +5 -2
  87. package/dist/runtime/turn/pipeline.d.ts +5 -12
  88. package/dist/runtime/turn/pipeline.js +11 -11
  89. package/dist/runtime/turn/relay.d.ts +13 -3
  90. package/dist/runtime/turn/relay.js +27 -14
  91. package/dist/runtime/turn/rows.js +4 -4
  92. package/dist/runtime/workflows/compatibility.d.ts +1 -1
  93. package/dist/runtime/workflows/compatibility.js +85 -31
  94. package/dist/runtime/workflows/engine.js +20 -17
  95. package/dist/serve/layer.js +3 -2
  96. package/dist/tables/owned.d.ts +1 -1
  97. package/dist/tables/owned.js +1 -1
  98. package/dist/testing/actor-test.d.ts +2 -24
  99. package/dist/testing/actor-test.js +1 -27
  100. package/dist/testing/index.d.ts +0 -12
  101. package/dist/testing/index.js +0 -6
  102. package/package.json +1 -1
  103. package/dist/runtime/database/neki/migrations.d.ts +0 -31
  104. package/dist/runtime/database/neki/migrations.js +0 -276
  105. package/dist/runtime/database/neki/session.d.ts +0 -23
  106. package/dist/runtime/database/neki/session.js +0 -31
  107. package/dist/testing/cluster.d.ts +0 -93
  108. package/dist/testing/cluster.js +0 -296
  109. package/dist/testing/conformance/access.d.ts +0 -5
  110. package/dist/testing/conformance/access.js +0 -531
  111. package/dist/testing/conformance/admission.d.ts +0 -11
  112. package/dist/testing/conformance/admission.js +0 -182
  113. package/dist/testing/conformance/adoption.d.ts +0 -2
  114. package/dist/testing/conformance/adoption.js +0 -1019
  115. package/dist/testing/conformance/assertions.d.ts +0 -202
  116. package/dist/testing/conformance/assertions.js +0 -849
  117. package/dist/testing/conformance/batches.d.ts +0 -41
  118. package/dist/testing/conformance/batches.js +0 -388
  119. package/dist/testing/conformance/blobs.d.ts +0 -14
  120. package/dist/testing/conformance/blobs.js +0 -405
  121. package/dist/testing/conformance/capacity.d.ts +0 -8
  122. package/dist/testing/conformance/capacity.js +0 -433
  123. package/dist/testing/conformance/client.d.ts +0 -5
  124. package/dist/testing/conformance/client.js +0 -719
  125. package/dist/testing/conformance/cold-serve.d.ts +0 -16
  126. package/dist/testing/conformance/cold-serve.js +0 -254
  127. package/dist/testing/conformance/connections/actors.d.ts +0 -1143
  128. package/dist/testing/conformance/connections/actors.js +0 -180
  129. package/dist/testing/conformance/connections/harness.d.ts +0 -1754
  130. package/dist/testing/conformance/connections/harness.js +0 -142
  131. package/dist/testing/conformance/connections/holders.d.ts +0 -4
  132. package/dist/testing/conformance/connections/holders.js +0 -165
  133. package/dist/testing/conformance/connections/lifecycle.d.ts +0 -4
  134. package/dist/testing/conformance/connections/lifecycle.js +0 -113
  135. package/dist/testing/conformance/connections/limits.d.ts +0 -4
  136. package/dist/testing/conformance/connections/limits.js +0 -127
  137. package/dist/testing/conformance/connections/reauthorization.d.ts +0 -4
  138. package/dist/testing/conformance/connections/reauthorization.js +0 -170
  139. package/dist/testing/conformance/connections/resync.d.ts +0 -4
  140. package/dist/testing/conformance/connections/resync.js +0 -360
  141. package/dist/testing/conformance/connections/takeover.d.ts +0 -4
  142. package/dist/testing/conformance/connections/takeover.js +0 -287
  143. package/dist/testing/conformance/connections.d.ts +0 -4
  144. package/dist/testing/conformance/connections.js +0 -15
  145. package/dist/testing/conformance/content-blobs.d.ts +0 -16
  146. package/dist/testing/conformance/content-blobs.js +0 -534
  147. package/dist/testing/conformance/counter.d.ts +0 -16
  148. package/dist/testing/conformance/counter.js +0 -813
  149. package/dist/testing/conformance/cron.d.ts +0 -5
  150. package/dist/testing/conformance/cron.js +0 -665
  151. package/dist/testing/conformance/drain.d.ts +0 -26
  152. package/dist/testing/conformance/drain.js +0 -540
  153. package/dist/testing/conformance/effect-control.d.ts +0 -38
  154. package/dist/testing/conformance/effect-control.js +0 -1059
  155. package/dist/testing/conformance/effects.d.ts +0 -23
  156. package/dist/testing/conformance/effects.js +0 -432
  157. package/dist/testing/conformance/events.d.ts +0 -28
  158. package/dist/testing/conformance/events.js +0 -335
  159. package/dist/testing/conformance/export.d.ts +0 -2
  160. package/dist/testing/conformance/export.js +0 -368
  161. package/dist/testing/conformance/fleet.d.ts +0 -444
  162. package/dist/testing/conformance/fleet.js +0 -1009
  163. package/dist/testing/conformance/groups.d.ts +0 -7
  164. package/dist/testing/conformance/groups.js +0 -353
  165. package/dist/testing/conformance/heap.d.ts +0 -3
  166. package/dist/testing/conformance/heap.js +0 -149
  167. package/dist/testing/conformance/http.d.ts +0 -2809
  168. package/dist/testing/conformance/http.js +0 -1234
  169. package/dist/testing/conformance/inspection-views.d.ts +0 -8
  170. package/dist/testing/conformance/inspection-views.js +0 -272
  171. package/dist/testing/conformance/inspector.d.ts +0 -7
  172. package/dist/testing/conformance/inspector.js +0 -650
  173. package/dist/testing/conformance/mint.d.ts +0 -58
  174. package/dist/testing/conformance/mint.js +0 -692
  175. package/dist/testing/conformance/multi-runner.d.ts +0 -8
  176. package/dist/testing/conformance/multi-runner.js +0 -403
  177. package/dist/testing/conformance/neki/plan.d.ts +0 -19
  178. package/dist/testing/conformance/neki/plan.js +0 -30
  179. package/dist/testing/conformance/observability.d.ts +0 -3
  180. package/dist/testing/conformance/observability.js +0 -343
  181. package/dist/testing/conformance/offline.d.ts +0 -4
  182. package/dist/testing/conformance/offline.js +0 -474
  183. package/dist/testing/conformance/operator-harness.d.ts +0 -57
  184. package/dist/testing/conformance/operator-harness.js +0 -78
  185. package/dist/testing/conformance/operator.d.ts +0 -3
  186. package/dist/testing/conformance/operator.js +0 -379
  187. package/dist/testing/conformance/outbox-cross-shard.d.ts +0 -14
  188. package/dist/testing/conformance/outbox-cross-shard.js +0 -160
  189. package/dist/testing/conformance/outbox.d.ts +0 -36
  190. package/dist/testing/conformance/outbox.js +0 -437
  191. package/dist/testing/conformance/payload-migrations.d.ts +0 -3
  192. package/dist/testing/conformance/payload-migrations.js +0 -612
  193. package/dist/testing/conformance/pipeline.d.ts +0 -3
  194. package/dist/testing/conformance/pipeline.js +0 -1040
  195. package/dist/testing/conformance/placement.d.ts +0 -22
  196. package/dist/testing/conformance/placement.js +0 -539
  197. package/dist/testing/conformance/platform.d.ts +0 -40
  198. package/dist/testing/conformance/platform.js +0 -101
  199. package/dist/testing/conformance/progress.d.ts +0 -39
  200. package/dist/testing/conformance/progress.js +0 -666
  201. package/dist/testing/conformance/properties.d.ts +0 -6
  202. package/dist/testing/conformance/properties.js +0 -295
  203. package/dist/testing/conformance/protocols.d.ts +0 -4
  204. package/dist/testing/conformance/protocols.js +0 -618
  205. package/dist/testing/conformance/provider/advertise.d.ts +0 -21
  206. package/dist/testing/conformance/provider/advertise.js +0 -44
  207. package/dist/testing/conformance/read-your-writes.d.ts +0 -20
  208. package/dist/testing/conformance/read-your-writes.js +0 -192
  209. package/dist/testing/conformance/reducers.d.ts +0 -8
  210. package/dist/testing/conformance/reducers.js +0 -258
  211. package/dist/testing/conformance/relay.d.ts +0 -42
  212. package/dist/testing/conformance/relay.js +0 -1115
  213. package/dist/testing/conformance/restore.d.ts +0 -43
  214. package/dist/testing/conformance/restore.js +0 -341
  215. package/dist/testing/conformance/retention.d.ts +0 -14
  216. package/dist/testing/conformance/retention.js +0 -463
  217. package/dist/testing/conformance/rls.d.ts +0 -24
  218. package/dist/testing/conformance/rls.js +0 -576
  219. package/dist/testing/conformance/simulation.d.ts +0 -31
  220. package/dist/testing/conformance/simulation.js +0 -167
  221. package/dist/testing/conformance/single-shard.d.ts +0 -2
  222. package/dist/testing/conformance/single-shard.js +0 -125
  223. package/dist/testing/conformance/singleton.d.ts +0 -3
  224. package/dist/testing/conformance/singleton.js +0 -293
  225. package/dist/testing/conformance/statements.d.ts +0 -60
  226. package/dist/testing/conformance/statements.js +0 -108
  227. package/dist/testing/conformance/streams.d.ts +0 -7
  228. package/dist/testing/conformance/streams.js +0 -280
  229. package/dist/testing/conformance/subscriptions/access.d.ts +0 -4
  230. package/dist/testing/conformance/subscriptions/access.js +0 -98
  231. package/dist/testing/conformance/subscriptions/actors.d.ts +0 -4910
  232. package/dist/testing/conformance/subscriptions/actors.js +0 -372
  233. package/dist/testing/conformance/subscriptions/cluster.d.ts +0 -4
  234. package/dist/testing/conformance/subscriptions/cluster.js +0 -89
  235. package/dist/testing/conformance/subscriptions/delivery.d.ts +0 -4
  236. package/dist/testing/conformance/subscriptions/delivery.js +0 -253
  237. package/dist/testing/conformance/subscriptions/deploys.d.ts +0 -4
  238. package/dist/testing/conformance/subscriptions/deploys.js +0 -162
  239. package/dist/testing/conformance/subscriptions/epochs.d.ts +0 -4
  240. package/dist/testing/conformance/subscriptions/epochs.js +0 -169
  241. package/dist/testing/conformance/subscriptions/failures.d.ts +0 -4
  242. package/dist/testing/conformance/subscriptions/failures.js +0 -108
  243. package/dist/testing/conformance/subscriptions/feeds.d.ts +0 -4
  244. package/dist/testing/conformance/subscriptions/feeds.js +0 -218
  245. package/dist/testing/conformance/subscriptions/harness.d.ts +0 -124
  246. package/dist/testing/conformance/subscriptions/harness.js +0 -143
  247. package/dist/testing/conformance/subscriptions/operator.d.ts +0 -4
  248. package/dist/testing/conformance/subscriptions/operator.js +0 -128
  249. package/dist/testing/conformance/subscriptions/retention.d.ts +0 -4
  250. package/dist/testing/conformance/subscriptions/retention.js +0 -288
  251. package/dist/testing/conformance/subscriptions/routing.d.ts +0 -4
  252. package/dist/testing/conformance/subscriptions/routing.js +0 -179
  253. package/dist/testing/conformance/subscriptions.d.ts +0 -4
  254. package/dist/testing/conformance/subscriptions.js +0 -19
  255. package/dist/testing/conformance/tables.d.ts +0 -125
  256. package/dist/testing/conformance/tables.js +0 -664
  257. package/dist/testing/conformance/transports/actors.d.ts +0 -1907
  258. package/dist/testing/conformance/transports/actors.js +0 -163
  259. package/dist/testing/conformance/transports/client.d.ts +0 -3
  260. package/dist/testing/conformance/transports/client.js +0 -237
  261. package/dist/testing/conformance/transports/feeds.d.ts +0 -3
  262. package/dist/testing/conformance/transports/feeds.js +0 -232
  263. package/dist/testing/conformance/transports/harness.d.ts +0 -93
  264. package/dist/testing/conformance/transports/harness.js +0 -84
  265. package/dist/testing/conformance/transports/streams.d.ts +0 -3
  266. package/dist/testing/conformance/transports/streams.js +0 -96
  267. package/dist/testing/conformance/transports/websocket.d.ts +0 -3
  268. package/dist/testing/conformance/transports/websocket.js +0 -384
  269. package/dist/testing/conformance/transports/wire.d.ts +0 -141
  270. package/dist/testing/conformance/transports/wire.js +0 -204
  271. package/dist/testing/conformance/transports.d.ts +0 -3
  272. package/dist/testing/conformance/transports.js +0 -11
  273. package/dist/testing/conformance/watch.d.ts +0 -59
  274. package/dist/testing/conformance/watch.js +0 -879
  275. package/dist/testing/conformance/workflow-engine.d.ts +0 -97
  276. package/dist/testing/conformance/workflow-engine.js +0 -253
  277. package/dist/testing/conformance/workflow-versions.d.ts +0 -3
  278. package/dist/testing/conformance/workflow-versions.js +0 -545
  279. package/dist/testing/conformance/workflows/access.d.ts +0 -4
  280. package/dist/testing/conformance/workflows/access.js +0 -147
  281. package/dist/testing/conformance/workflows/actors.d.ts +0 -2317
  282. package/dist/testing/conformance/workflows/actors.js +0 -305
  283. package/dist/testing/conformance/workflows/basics.d.ts +0 -4
  284. package/dist/testing/conformance/workflows/basics.js +0 -144
  285. package/dist/testing/conformance/workflows/engine.d.ts +0 -4
  286. package/dist/testing/conformance/workflows/engine.js +0 -80
  287. package/dist/testing/conformance/workflows/guards.d.ts +0 -4
  288. package/dist/testing/conformance/workflows/guards.js +0 -172
  289. package/dist/testing/conformance/workflows/harness.d.ts +0 -14
  290. package/dist/testing/conformance/workflows/harness.js +0 -44
  291. package/dist/testing/conformance/workflows/recovery.d.ts +0 -4
  292. package/dist/testing/conformance/workflows/recovery.js +0 -181
  293. package/dist/testing/conformance/workflows/starts.d.ts +0 -4
  294. package/dist/testing/conformance/workflows/starts.js +0 -57
  295. package/dist/testing/conformance/workflows/suspension.d.ts +0 -4
  296. package/dist/testing/conformance/workflows/suspension.js +0 -106
  297. package/dist/testing/conformance/workflows/waits.d.ts +0 -4
  298. package/dist/testing/conformance/workflows/waits.js +0 -284
  299. package/dist/testing/conformance/workflows.d.ts +0 -4
  300. package/dist/testing/conformance/workflows.js +0 -19
  301. package/dist/testing/conformance.d.ts +0 -330
  302. package/dist/testing/conformance.js +0 -364
  303. package/dist/testing/foundation.d.ts +0 -41
  304. package/dist/testing/foundation.js +0 -517
  305. package/dist/testing/simulate-cluster.d.ts +0 -112
  306. package/dist/testing/simulate-cluster.js +0 -257
  307. package/dist/testing/simulate.d.ts +0 -90
  308. package/dist/testing/simulate.js +0 -127
@@ -1,6 +1,6 @@
1
1
  import { PgClient, PgTypes } from "@effect/sql-pg";
2
2
  import { PgliteClient } from "@effect/sql-pglite";
3
- import { Cause, Clock, Semaphore, Context, Crypto, Duration, Deferred, Effect, Fiber, Layer, Option, Result, Schedule, Schema, Stream, } from "effect";
3
+ import { Cause, Clock, Semaphore, Context, Crypto, Duration, Deferred, Effect, Exit, Fiber, Layer, Option, Predicate, Result, Schedule, Schema, Stream, } from "effect";
4
4
  import { ClusterError, Entity, EntityId, RunnerHealth, Runners, RunnerStorage, Sharding, ShardingConfig, } from "effect/cluster";
5
5
  import { SqlClient, SqlError } from "effect/sql";
6
6
  import { ActorError, ActorUnavailable, InvalidInput, Unauthorized, Timeout, MailboxFull, RunnerAtCapacity, } from "../errors/actor.js";
@@ -11,7 +11,7 @@ import { InternalActors } from "./actors.js";
11
11
  import { System } from "../identity/caller.js";
12
12
  import { deriveMintId } from "../identity/mint.js";
13
13
  import { migrate } from "./database/migrations.js";
14
- import { NekiTurnSessions } from "./database/neki/session.js";
14
+ import { schemaChange } from "./database/schema.js";
15
15
  import { queryPoolLayer, ReadReplica, replicaLayer } from "./database/replica.js";
16
16
  import { Coordination, coordinationLayer } from "./database/coordination.js";
17
17
  import { withKeepalives } from "./database/keepalive.js";
@@ -30,6 +30,7 @@ import { recordedPlacement } from "./storage/placements.js";
30
30
  import { TurnHooks } from "./turn/hooks.js";
31
31
  import { requestAttributes, SpanNames } from "./telemetry/spans.js";
32
32
  import { DefectLog, boundedDefectLog } from "./telemetry/defects.js";
33
+ import { liveRecorder, LiveRuntime, } from "./telemetry/live.js";
33
34
  import { OperatorRuntime, operatorRuntime } from "./operators/repair.js";
34
35
  import { seedRuntime } from "./operators/seed.js";
35
36
  import { TelemetrySampler } from "./telemetry/sampler.js";
@@ -56,7 +57,7 @@ import { RuntimeControl, runtimeControl, turnGate } from "./drain.js";
56
57
  import { eventFeeds } from "./feeds.js";
57
58
  import { committedReads } from "./queries.js";
58
59
  import { servingReadiness } from "./readiness.js";
59
- import { acquiredShards, checkRunnerConfiguration, RunnerReadiness, LocalRequestSerialization, RunnerWiring, } from "./runner.js";
60
+ import { acquiredShards, checkRunnerConfiguration, RunnerPeers, runnerPeers, RunnerReadiness, LocalRequestSerialization, RunnerWiring, } from "./runner.js";
60
61
  import { actorRegistration } from "./registration.js";
61
62
  import { startSweeps } from "./sweeps.js";
62
63
  import { checkFleet } from "./fleet/checks.js";
@@ -74,6 +75,14 @@ const CLAIM_MARGIN_MS = 5000;
74
75
  const DEFAULT_CLAIM_LEASE_MS = 37_000;
75
76
  /** How long a progress send may take before it is given up as a lost frame. */
76
77
  const PROGRESS_SEND_TIMEOUT = "5 seconds";
78
+ /**
79
+ * The reply to a command the closing runtime scope cut off or never started.
80
+ * The caller cannot interrupt an admitted command, so an interrupt there comes
81
+ * from shutdown, and the caller can retry on a runtime that is still serving.
82
+ */
83
+ const shuttingDown = Exit.fail(ActorError.make({
84
+ reason: ActorUnavailable.make({ cause: new Error("Runtime is shutting down") }),
85
+ }));
77
86
  /**
78
87
  * An entity type with no messages, registered before any actor. The cluster's
79
88
  * idle sweep takes its interval from the first entity type registered and
@@ -92,7 +101,10 @@ const IdleSweep = Entity.make("akter/IdleSweep", []);
92
101
  * Constraints the wiring keeps:
93
102
  * - Turns run through the drain gate, so a drain refuses new turns and
94
103
  * interrupts the rest. The runtime scope, not the caller, owns an in-flight
95
- * turn: interrupting a waiter never cancels it.
104
+ * turn: interrupting a waiter never cancels it. Closing that scope does, and
105
+ * a command it cuts off, or one sent after it starts closing, fails with a
106
+ * retryable `ActorUnavailable` instead of an interrupt or a wait that never
107
+ * ends; delivery stops retrying once the scope is closed.
96
108
  * - Commands are direct, so Cluster keeps no messages and the receipt is the
97
109
  * only admission record; a restarted activation or lost runner drops the
98
110
  * uncommitted attempt, the handle retries with the same command id, and the
@@ -210,6 +222,14 @@ export const layer = (options = {}) => {
210
222
  let refreshedAt;
211
223
  const services = yield* Effect.context();
212
224
  const defectLog = boundedDefectLog(defectCapacity);
225
+ let recorder;
226
+ const currentRecorder = () => recorder;
227
+ const enableRecorder = Effect.map(Clock.currentTimeMillis, (startedAtMs) => {
228
+ recorder ??= liveRecorder({ startedAtMs, epoch: runtimeId.slice(0, 8) });
229
+ return recorder;
230
+ });
231
+ const servedStreams = new Map();
232
+ const peers = yield* RunnerPeers;
213
233
  const database = yield* rowsDatabase;
214
234
  const clockOffset = yield* FrameworkClock;
215
235
  const content = options.content === undefined
@@ -341,7 +361,7 @@ export const layer = (options = {}) => {
341
361
  }).pipe(refreshing.withPermits(1), Effect.provideContext(services), Effect.provideService(FrameworkClock, frameworkClock));
342
362
  /**
343
363
  * A runtime that could not refresh its writer rows within the window
344
- * may already count as gone to `durable payloads clear`, so it starts
364
+ * may already count as gone to `akter payloads clear`, so it starts
345
365
  * no turn until a refresh succeeds. The check is local.
346
366
  */
347
367
  const writable = Effect.gen(function* () {
@@ -466,8 +486,9 @@ export const layer = (options = {}) => {
466
486
  reason: ActorUnavailable.make({ cause: Cause.squash(cause) }),
467
487
  }));
468
488
  }));
469
- const retrying = (attempt) => deliver.pipe(Effect.catchIf((error) => (Schema.is(ActorUnavailable)(error.reason) && !isOverloaded(error)) ||
470
- Schema.is(RunnerAtCapacity)(error.reason), (error) => Effect.sleep(retryDelay(attempt)(error)).pipe(Effect.andThen(Effect.suspend(() => retrying(attempt + 1))))));
489
+ const retrying = (attempt) => deliver.pipe(Effect.catchIf((error) => !Predicate.isTagged(scope.state, "Closed") &&
490
+ ((Schema.is(ActorUnavailable)(error.reason) && !isOverloaded(error)) ||
491
+ Schema.is(RunnerAtCapacity)(error.reason)), (error) => Effect.sleep(retryDelay(attempt)(error)).pipe(Effect.andThen(Effect.suspend(() => retrying(attempt + 1))))));
471
492
  const executed = yield* retrying(0);
472
493
  if (external)
473
494
  yield* authorize(request, executed.endedAtMs);
@@ -613,6 +634,51 @@ export const layer = (options = {}) => {
613
634
  wake: relay.wake,
614
635
  tenantScope,
615
636
  });
637
+ const live = LiveRuntime.of({
638
+ recorder: currentRecorder,
639
+ enable: enableRecorder,
640
+ resident: (tenant) => [...diagnostics].flatMap(([actorType, { resident }]) => resident().flatMap((activation) => activation.tenant === tenant
641
+ ? [{ actorType, actorId: activation.id, mailbox: activation.mailbox }]
642
+ : [])),
643
+ connections: (tenant) => [
644
+ ...(holder?.census(tenant) ?? []).map(({ ref, member, feed }) => ({
645
+ actorType: ref.actor,
646
+ actorId: ref.id,
647
+ kind: member === FEED_MEMBER ? "feed" : isWatchMember(member) ? "watch" : "socket",
648
+ events: member === FEED_MEMBER ? feed : [],
649
+ })),
650
+ ...[...servedStreams.values()].flatMap((served) => served.tenant === tenant
651
+ ? Array.from({ length: served.count }, () => ({
652
+ actorType: served.actorType,
653
+ actorId: served.actorId,
654
+ kind: "stream",
655
+ events: [],
656
+ }))
657
+ : []),
658
+ ],
659
+ schedules: () => [...registrations.values()].flatMap((registration) => registration.cron.map((entry) => ({
660
+ actorType: registration.name,
661
+ key: entry.key,
662
+ command: entry.command,
663
+ }))),
664
+ peers: peers.peers,
665
+ streamOpened: (ref) => {
666
+ const key = JSON.stringify([ref.tenant, ref.actor, ref.id]);
667
+ const served = servedStreams.get(key) ?? {
668
+ tenant: ref.tenant,
669
+ actorType: ref.actor,
670
+ actorId: ref.id,
671
+ count: 0,
672
+ };
673
+ served.count += 1;
674
+ servedStreams.set(key, served);
675
+ return () => {
676
+ served.count -= 1;
677
+ if (served.count === 0)
678
+ servedStreams.delete(key);
679
+ };
680
+ },
681
+ });
616
682
  const internalActors = InternalActors.of({
617
683
  fleet: (request) => fleetSubscribe === undefined
618
684
  ? ActorError.make({ reason: InvalidInput.make({ code: "unknown_route" }) })
@@ -661,6 +727,7 @@ export const layer = (options = {}) => {
661
727
  writable,
662
728
  defectLog,
663
729
  outbox,
730
+ recorder: currentRecorder,
664
731
  }),
665
732
  ...committedReads({
666
733
  registrations,
@@ -683,15 +750,18 @@ export const layer = (options = {}) => {
683
750
  deliverProgress: (message) => deliverProgress(message).pipe(Effect.ignoreCause, Effect.provideContext(services)),
684
751
  transport,
685
752
  holder,
753
+ live,
686
754
  hibernate: (ref) => Effect.flatMap(entityId(ref), (id) => owners.get(ref.actor)?.hibernate(id) ?? Effect.void).pipe(Effect.provideContext(services)),
687
755
  execute: (request) => Effect.gen(function* () {
688
756
  const reply = yield* Deferred.make();
689
757
  const pending = new Set();
690
- yield* admission
758
+ const settle = (exit) => Deferred.done(reply, Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause) ? shuttingDown : exit);
759
+ const admitted = yield* admission
691
760
  .admit(dispatch(request, true, pending).pipe(Effect.tap(observe), Effect.onExit((exit) => [...pending].some((fiber) => fiber.pollUnsafe() === undefined)
692
- ? Deferred.done(reply, exit)
761
+ ? settle(exit)
693
762
  : Effect.void), Effect.ensuring(Effect.suspend(() => Effect.forEach(pending, Fiber.await, { discard: true })))))
694
- .pipe(Effect.onExit((exit) => Deferred.done(reply, exit)), Effect.ignoreCause, Effect.forkIn(scope));
763
+ .pipe(Effect.onExit(settle), Effect.ignoreCause, Effect.forkIn(scope));
764
+ admitted.addObserver(() => Deferred.doneUnsafe(reply, shuttingDown));
695
765
  return yield* Deferred.await(reply);
696
766
  }),
697
767
  overloaded: admission.full,
@@ -739,9 +809,10 @@ export const layer = (options = {}) => {
739
809
  if (!registration.blobs.some((declared) => declared.name === blob && isContent(declared)))
740
810
  return Option.none();
741
811
  const sql = yield* SqlClient.SqlClient;
812
+ const key = routingKey({ ref, placement: registration.placement });
742
813
  const [found] = yield* sql `
743
814
  SELECT hash, size::float8 AS size FROM actor_content_refs
744
- WHERE routing_key = ${routingKey({ ref, placement: registration.placement })}
815
+ WHERE routing_key = ${key}
745
816
  AND tenant_id = ${ref.tenant} AND actor_type = ${ref.actor} AND actor_id = ${ref.id}
746
817
  AND blob = ${blob} AND name = ${name}`;
747
818
  const timeoutMs = "policy" in registration ? registration.policy.executionMs : registration.timeoutMs;
@@ -794,7 +865,7 @@ export const layer = (options = {}) => {
794
865
  yield* checkRunnerConfiguration(wiring);
795
866
  const sharding = (wiring?.sharding ?? Sharding.layer.pipe(Layer.provide(Runners.layerNoop))).pipe(Layer.merge(Layer.effect(RunnerReadiness, wiring?.production === true
796
867
  ? acquiredShards
797
- : Effect.succeed({ acquired: () => Effect.succeed(true) }))), Layer.provideMerge(directMessages), Layer.provideMerge(mailboxRefusals), Layer.provide([
868
+ : Effect.succeed({ acquired: () => Effect.succeed(true) })).pipe(Layer.merge(Layer.effect(RunnerPeers, runnerPeers)))), Layer.provideMerge(directMessages), Layer.provideMerge(mailboxRefusals), Layer.provide([
798
869
  runnerStorage === "memory"
799
870
  ? Layer.effect(RunnerStorage.RunnerStorage, Effect.map(RunnerStorage.makeMemory, keepAcquiredShards))
800
871
  : Layer.effect(RunnerStorage.RunnerStorage, coordinatedRunnerStorage.pipe(Effect.map(wiring?.storage ?? ((storage) => storage)), Effect.map(keepAcquiredShards))).pipe(Layer.orDie),
@@ -826,51 +897,48 @@ export const layer = (options = {}) => {
826
897
  })));
827
898
  }));
828
899
  };
900
+ /**
901
+ * A runner holds three primary pools, plus optional replica and coordination
902
+ * pools. Turns lease sessions from the turn pool, `maxConnections` (default
903
+ * 50): a command holds one session until its transaction ends, so a pool
904
+ * smaller than the commands in flight queues callers behind
905
+ * it. Queries read from the query pool, `queryConnections` (default 10),
906
+ * except those of types with owned tables or blobs. Command admission,
907
+ * receipt replays, the relay, migrations, and cluster storage use the
908
+ * off-turn pool, `offTurnConnections` (default 10), unless `coordination`
909
+ * moves Cluster storage and deployment locks to an independent unsharded
910
+ * primary. All runners must designate the same authority. Every pool opens
911
+ * connections only as load needs them, refuses checkouts past its
912
+ * connections plus a bounded waiter allowance, and hands its connections out
913
+ * first come, first served. Keep the sum across runners below the server's
914
+ * `max_connections`.
915
+ *
916
+ * `replica` is this runner's nearest streaming replica of the same primary.
917
+ * Queries read there once it has replayed the commit version their caller
918
+ * last saw, and read the primary when it is behind or fails. Its pool
919
+ * (`maxConnections` default 10) opens connections only as queries need them.
920
+ * Every pool requests server TCP keepalives at 5 seconds idle, 2 seconds
921
+ * between probes, and 3 probes. `startupParameters` overrides these defaults
922
+ * independently on the primary and replica configurations.
923
+ *
924
+ * Registers a `regclass` codec because the pinned driver lacks one and the
925
+ * migrator needs it on restart; remove once Effect #8309 lands.
926
+ */
927
+ function postgres(options) {
928
+ const types = PgTypes.makeRegistry();
929
+ types.register(2205, {
930
+ encode: (value) => PgTypes.encode(value, PgTypes.OID.oid),
931
+ decode: (bytes) => bytes.length === 4
932
+ ? Result.succeed(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0))
933
+ : Result.fail(new PgTypes.CodecError({ message: "Invalid regclass value" })),
934
+ });
935
+ const { offTurnConnections, queryConnections, replica, coordination, ...configured } = options;
936
+ const pool = withKeepalives(configured);
937
+ return Layer.mergeAll(boundedLayer({ ...pool, maxConnections: offTurnConnections ?? 10, types }), turnGroups.pipe(Layer.provideMerge(turnConnections({ ...pool, maxConnections: pool.maxConnections ?? 50, types }))), queryPoolLayer({ ...pool, maxConnections: queryConnections ?? 10, types }), replicaLayer(replica === undefined ? undefined : { ...withKeepalives(replica), types }), coordinationLayer(coordination === undefined ? undefined : { ...withKeepalives(coordination), types }));
938
+ }
829
939
  /** Database layers for `Actors.layer`: `postgres` for real deployments, `pglite` for embedded and test use. */
830
940
  export const Database = {
831
- /**
832
- * A runner holds three primary pools, plus optional replica and coordination
833
- * pools. Turns lease sessions from the turn pool, `maxConnections` (default
834
- * 50): a command holds one session until its transaction ends, so a pool
835
- * smaller than the commands in flight queues callers behind
836
- * it. Queries read from the query pool, `queryConnections` (default 10),
837
- * except those of types with owned tables or blobs. Command admission,
838
- * receipt replays, the relay, migrations, and cluster storage use the
839
- * off-turn pool, `offTurnConnections` (default 10), unless `coordination`
840
- * moves Cluster storage and deployment locks to an independent unsharded
841
- * primary. All runners must designate the same authority. Every pool opens
842
- * connections only as load needs them, refuses checkouts past its
843
- * connections plus a bounded waiter allowance, and hands its connections out
844
- * first come, first served. Keep the sum across runners below the server's
845
- * `max_connections`.
846
- *
847
- * `replica` is this runner's nearest streaming replica of the same primary.
848
- * Queries read there once it has replayed the commit version their caller
849
- * last saw, and read the primary when it is behind or fails. Its pool
850
- * (`maxConnections` default 10) opens connections only as queries need them.
851
- * Every pool requests server TCP keepalives at 5 seconds idle, 2 seconds
852
- * between probes, and 3 probes. `startupParameters` overrides these defaults
853
- * independently on the primary and replica configurations.
854
- *
855
- * Registers a `regclass` codec because the pinned driver lacks one and the
856
- * migrator needs it on restart; remove once Effect #8309 lands.
857
- */
858
- postgres: (options) => {
859
- const types = PgTypes.makeRegistry();
860
- types.register(2205, {
861
- encode: (value) => PgTypes.encode(value, PgTypes.OID.oid),
862
- decode: (bytes) => bytes.length === 4
863
- ? Result.succeed(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0))
864
- : Result.fail(new PgTypes.CodecError({ message: "Invalid regclass value" })),
865
- });
866
- const { offTurnConnections, queryConnections, replica, neki, coordination, ...configured } = options;
867
- const pool = withKeepalives(configured);
868
- const database = Layer.mergeAll(boundedLayer({ ...pool, maxConnections: offTurnConnections ?? 10, types }), neki === true
869
- ? turnConnections({ ...pool, maxConnections: pool.maxConnections ?? 50, types })
870
- : turnGroups.pipe(Layer.provideMerge(turnConnections({ ...pool, maxConnections: pool.maxConnections ?? 50, types }))), queryPoolLayer({ ...pool, maxConnections: queryConnections ?? 10, types }), replicaLayer(replica === undefined ? undefined : { ...withKeepalives(replica), types }), coordinationLayer(coordination === undefined ? undefined : { ...withKeepalives(coordination), types }));
871
- return neki === undefined
872
- ? database
873
- : database.pipe(Layer.provideMerge(Layer.succeed(NekiTurnSessions, neki)));
874
- },
941
+ postgres,
875
942
  pglite,
943
+ schemaChange,
876
944
  };
@@ -1,6 +1,7 @@
1
1
  import { Crypto, Effect, Option, Schema } from "effect";
2
2
  import { SqlClient } from "effect/sql";
3
3
  import { type Placement } from "../storage/codec.ts";
4
+ import { type BucketRange } from "../database/shards.ts";
4
5
  import { Capability, type OperatorAction } from "./grants.ts";
5
6
  /** One operator action as the audit log records it. */
6
7
  export interface AuditEntry {
@@ -49,6 +50,8 @@ export type AuditRecord = typeof AuditRecord.Type;
49
50
  export declare const listAudit: (args_0: {
50
51
  readonly tenant: string;
51
52
  readonly limit: number;
53
+ /** The buckets read, when each shard is read on its own. */
54
+ readonly range?: BucketRange;
52
55
  }) => Effect.Effect<readonly {
53
56
  readonly auditId: string;
54
57
  readonly atMs: number;
@@ -1,6 +1,8 @@
1
1
  import { Crypto, Effect, Option, Schema } from "effect";
2
2
  import { SqlClient } from "effect/sql";
3
3
  import { routingKey } from "../storage/codec.js";
4
+ import { withinRange } from "../database/shards.js";
5
+ import { BUCKETS } from "../turn/outbox.js";
4
6
  import { databaseTime } from "../turn/admission.js";
5
7
  import { Capability } from "./grants.js";
6
8
  const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Json));
@@ -45,9 +47,11 @@ export const AuditRecord = Schema.Struct({
45
47
  outcome: Schema.String,
46
48
  });
47
49
  /** A tenant's newest audit rows, or every tenant's for `"*"`, newest first. */
48
- export const listAudit = Effect.fnUntraced(function* ({ tenant, limit, }) {
50
+ export const listAudit = Effect.fnUntraced(function* ({ tenant, limit, range = BUCKETS, }) {
49
51
  const sql = yield* SqlClient.SqlClient;
50
- const filter = tenant === "*" ? sql `` : sql `WHERE tenant_id = ${tenant}`;
52
+ const filter = tenant === "*"
53
+ ? withinRange({ sql, range, column: "routing_key", keyword: "WHERE" })
54
+ : sql `WHERE tenant_id = ${tenant} ${withinRange({ sql, range, column: "routing_key" })}`;
51
55
  return yield* sql `SELECT audit_id AS "auditId", at_ms::float8 AS "atMs", operator,
52
56
  action, tenant_id AS tenant, actor_type AS "actorType", actor_id AS "actorId", target,
53
57
  capability, reason, outcome
@@ -5,6 +5,7 @@ import { ActorRef, System } from "../../identity/caller.js";
5
5
  import * as Queries from "../inspector/queries.js";
6
6
  import { TenantScope, withTenant } from "../database/tenancy.js";
7
7
  import { routingKey } from "../storage/codec.js";
8
+ import { forEachRange, withinRange } from "../database/shards.js";
8
9
  import { recordedPlacement } from "../storage/placements.js";
9
10
  import { databaseTime, FrameworkClock } from "../turn/admission.js";
10
11
  import { OutboxRuntime, outboxStatements } from "../turn/outbox.js";
@@ -31,6 +32,16 @@ export class JobNotServed extends Schema.TaggedError()("JobNotServed", {
31
32
  */
32
33
  export class OperatorRuntime extends Context.Service()("@rikalabs/akter/runtime/operators/repair/OperatorRuntime") {
33
34
  }
35
+ const encoder = new TextEncoder();
36
+ /** Compares two strings by their UTF-8 bytes, as Postgres orders them under `COLLATE "C"`. */
37
+ const byteOrder = (left, right) => {
38
+ const a = encoder.encode(left);
39
+ const b = encoder.encode(right);
40
+ for (let index = 0; index < Math.min(a.length, b.length); index++)
41
+ if (a[index] !== b[index])
42
+ return a[index] - b[index];
43
+ return a.length - b.length;
44
+ };
34
45
  /**
35
46
  * Builds the service over the runtime's SQL, crypto, clock, and outbox, and
36
47
  * its job registrations, which say whether a retried job is capped.
@@ -98,7 +109,7 @@ export const operatorRuntime = (deps) => {
98
109
  expiresAtMs: found.expires_at_ms,
99
110
  }));
100
111
  }).pipe(Effect.catchTag("OperatorNotFound", () => Effect.succeedNone), provided),
101
- exportSeed: (target) => exportActor(target).pipe(Effect.catchIf(SqlError.isSqlError, Effect.die), provided),
112
+ exportSeed: (target) => keyOf(target).pipe(Effect.andThen(exportActor(target)), Effect.catchTag("OperatorNotFound", () => Effect.succeedNone), Effect.catchIf(SqlError.isSqlError, Effect.die), provided),
102
113
  retry: ({ target, jobId, providerChecked, audit }) => Effect.gen(function* () {
103
114
  const ref = ActorRef.make({
104
115
  tenant: target.tenant,
@@ -192,7 +203,7 @@ export const operatorRuntime = (deps) => {
192
203
  yield* deps.wake;
193
204
  return { through };
194
205
  }).pipe(provided),
195
- lagging: ({ tenant, minAttempts, limit }) => Queries.readOnly(tenant)(Effect.gen(function* () {
206
+ lagging: ({ tenant, minAttempts, limit }) => forEachRange((range) => Queries.readOnly(tenant)(Effect.gen(function* () {
196
207
  const sql = yield* SqlClient.SqlClient;
197
208
  return yield* sql `
198
209
  SELECT s.source_type AS "sourceType", s.source_id AS "sourceId",
@@ -206,21 +217,30 @@ export const operatorRuntime = (deps) => {
206
217
  AND g.tenant_id = s.tenant_id AND g.actor_type = s.source_type
207
218
  AND g.actor_id = s.source_id
208
219
  WHERE s.tenant_id = ${tenant} AND s.active AND s.last_error IS NOT NULL
209
- AND s.attempts >= ${minAttempts}
220
+ AND s.attempts >= ${minAttempts} ${withinRange({ sql, range, column: "s.routing_key" })}
210
221
  ORDER BY s.attempts DESC, g.event_sequence - s.delivered DESC,
211
222
  s.source_type COLLATE "C", s.source_id COLLATE "C",
212
223
  s.subscriber_type COLLATE "C", s.subscription COLLATE "C",
213
224
  s.subscriber_id COLLATE "C"
214
225
  LIMIT ${limit}`;
215
- })).pipe(provided, Effect.orDie),
216
- audit: (page) => Queries.readOnly(page.tenant)(listAudit(page)).pipe(provided, Effect.orDie),
226
+ }))).pipe(Effect.map((shards) => shards
227
+ .flat()
228
+ .sort((a, b) => b.attempts - a.attempts ||
229
+ Number(BigInt(b.lag) - BigInt(a.lag)) ||
230
+ byteOrder(a.sourceType, b.sourceType) ||
231
+ byteOrder(a.sourceId, b.sourceId) ||
232
+ byteOrder(a.subscriberType, b.subscriberType) ||
233
+ byteOrder(a.subscription, b.subscription) ||
234
+ byteOrder(a.subscriberId, b.subscriberId))
235
+ .slice(0, limit)), provided, Effect.orDie),
236
+ audit: (page) => forEachRange((range) => Queries.readOnly(page.tenant)(listAudit({ ...page, range }))).pipe(Effect.map((shards) => shards
237
+ .flat()
238
+ .sort((a, b) => b.atMs - a.atMs || byteOrder(a.auditId, b.auditId))
239
+ .slice(0, page.limit)), provided, Effect.orDie),
217
240
  record: (entry, outcome) => Effect.gen(function* () {
218
241
  const found = entry.actorType === undefined ? Option.none() : yield* placement(entry.actorType);
219
- yield* writeAudit({
220
- entry,
221
- key: auditRoutingKey({ entry, placement: Option.getOrUndefined(found) }),
222
- outcome,
223
- });
242
+ const key = auditRoutingKey({ entry, placement: Option.getOrUndefined(found) });
243
+ yield* writeAudit({ entry, key, outcome });
224
244
  }).pipe(provided, Effect.orDie),
225
245
  });
226
246
  };
@@ -78,7 +78,8 @@ export const seedRuntime = (deps) => Effect.fnUntraced(function* ({ ref, caller,
78
78
  if (placement === undefined)
79
79
  return yield* Effect.die(new Error(`Actor ${ref.actor} is not registered`));
80
80
  const key = routingKey({ ref, placement });
81
- yield* withTenant(ref.tenant)(sql.withTransaction(Effect.gen(function* () {
81
+ yield* sql
82
+ .withTransaction(Effect.gen(function* () {
82
83
  const [inserted] = yield* sql `
83
84
  INSERT INTO actor_generations (routing_key, tenant_id, actor_type, actor_id, created)
84
85
  VALUES (${key}, ${ref.tenant}, ${ref.actor}, ${ref.id}, ${seed.created})
@@ -118,6 +119,7 @@ export const seedRuntime = (deps) => Effect.fnUntraced(function* ({ ref, caller,
118
119
  }, databaseTime);
119
120
  for (const statement of staged.statements)
120
121
  yield* statement;
121
- })));
122
+ }))
123
+ .pipe(withTenant(ref.tenant));
122
124
  yield* deps.wake;
123
125
  }, (effect) => effect.pipe(Effect.catchIf(SqlError.isSqlError, Effect.die), Effect.provideService(FrameworkClock, deps.clock), Effect.provideService(OutboxRuntime, deps.outbox), Effect.provideService(TenantScope, deps.tenantScope), Effect.provideContext(deps.services)));
@@ -17,7 +17,7 @@ export declare const formatPayloadProblem: (problem: PayloadProblem) => string;
17
17
  * Compares declared chains with the versions the database may hold. A
18
18
  * version recorded above a chain's current version is a rollback past a
19
19
  * schema change. A chain that starts above version `v` can't read values of
20
- * `v`: an event version counts as stored until `durable payloads clear` marks
20
+ * `v`: an event version counts as stored until `akter payloads clear` marks
21
21
  * it cleared. Every stored event has a recorded version, because a runtime
22
22
  * records what it writes before it takes a shard and the migration refused
23
23
  * databases that held rows before it. Job versions are read from the
@@ -39,9 +39,9 @@ export declare const recordPayloadVersions: (declarations: readonly PayloadDecla
39
39
  export declare const refreshWriters: (runtimeId: string, windowMs: number, declarations: readonly PayloadDeclaration[]) => Effect.Effect<void, import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
40
40
  /** Drops a stopping runtime's writer rows, so a clear need not wait out its window. */
41
41
  export declare const dropWriters: (runtimeId: string) => Effect.Effect<void, import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
42
- /** `durable payloads check`: the startup check for `actors`, read-only. */
42
+ /** `akter payloads check`: the startup check for `actors`, read-only. */
43
43
  export declare const checkPayloads: (actors: ReadonlyArray<object>) => Effect.Effect<PayloadProblem[], import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
44
- /** What `durable payloads clear` found for one superseded event version. */
44
+ /** What `akter payloads clear` found for one superseded event version. */
45
45
  export interface ClearResult {
46
46
  readonly actorType: string;
47
47
  readonly tag: string;
@@ -54,7 +54,7 @@ export interface ClearResult {
54
54
  readonly outcome: "cleared" | "writer" | "stored";
55
55
  }
56
56
  /**
57
- * `durable payloads clear`: marks each superseded event version of `actors`
57
+ * `akter payloads clear`: marks each superseded event version of `actors`
58
58
  * cleared once its retention horizon has passed, no runtime can still write
59
59
  * it, and no event of it remains. The version row is locked first, the
60
60
  * writer rows are read again after the scan, and each statement of the
@@ -1,5 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { SqlClient } from "effect/sql";
3
+ import { registry } from "../database/coordination.js";
4
+ import { forEachRange, withinRange } from "../database/shards.js";
3
5
  import { descriptorOf } from "../../actor/descriptor.js";
4
6
  import {} from "../../members/payload.js";
5
7
  import { inReadOnlySnapshot } from "../database/snapshot.js";
@@ -14,7 +16,7 @@ const keyOf = (declared) => `${declared.actorType}\u0000${declared.kind}\u0000${
14
16
  * Compares declared chains with the versions the database may hold. A
15
17
  * version recorded above a chain's current version is a rollback past a
16
18
  * schema change. A chain that starts above version `v` can't read values of
17
- * `v`: an event version counts as stored until `durable payloads clear` marks
19
+ * `v`: an event version counts as stored until `akter payloads clear` marks
18
20
  * it cleared. Every stored event has a recorded version, because a runtime
19
21
  * records what it writes before it takes a shard and the migration refused
20
22
  * databases that held rows before it. Job versions are read from the
@@ -60,19 +62,24 @@ export const findPayloadProblems = Effect.fnUntraced(function* (declarations, wr
60
62
  if (declared.kind === "event") {
61
63
  for (const row of versions)
62
64
  if (row.version < first && !row.cleared)
63
- problem(`version ${row.version} may still be stored below this chain's first version ${first}; run durable payloads clear once its events are gone`);
65
+ problem(`version ${row.version} may still be stored below this chain's first version ${first}; run akter payloads clear once its events are gone`);
64
66
  }
65
67
  else {
66
- const [stored] = yield* sql `
68
+ const shards = (yield* forEachRange((range) => sql `
67
69
  SELECT min(v)::int AS version, count(*)::int AS rows FROM (
68
70
  SELECT payload_version AS v FROM actor_outbox
69
71
  WHERE kind = 'job' AND actor_type = ${declared.actorType} AND command = ${declared.tag}
70
- AND payload_version < ${first}
72
+ AND payload_version < ${first} ${withinRange({ sql, range, column: "routing_key" })}
71
73
  UNION ALL
72
74
  SELECT payload_version FROM actor_dead_letters
73
75
  WHERE actor_type = ${declared.actorType} AND job = ${declared.tag}
74
- AND payload_version < ${first}) stored`;
75
- if (stored !== undefined && stored.version !== null)
76
+ AND payload_version < ${first} ${withinRange({ sql, range, column: "routing_key" })}) stored`)).flat();
77
+ const versions = shards.flatMap((row) => (row.version === null ? [] : [row.version]));
78
+ const stored = {
79
+ version: versions.length === 0 ? null : Math.min(...versions),
80
+ rows: shards.reduce((sum, row) => sum + row.rows, 0),
81
+ };
82
+ if (stored.version !== null)
76
83
  problem(`version ${stored.version} stored in ${stored.rows} pending job or dead letter row${stored.rows === 1 ? "" : "s"} below this chain's first version ${first}`);
77
84
  }
78
85
  }
@@ -85,14 +92,15 @@ export const findPayloadProblems = Effect.fnUntraced(function* (declarations, wr
85
92
  .map((row) => row.tag)),
86
93
  ];
87
94
  for (const tag of removed) {
88
- const [pending] = yield* sql `
95
+ const [pending] = (yield* forEachRange((range) => sql `
89
96
  SELECT s.subscriber_type, s.subscription FROM actor_subscriptions s
90
97
  WHERE s.source_type = ${writer.actorType} AND ${tag} = ANY(s.events)
91
98
  AND EXISTS (SELECT 1 FROM actor_events e
92
99
  WHERE e.routing_key = s.routing_key AND e.tenant_id = s.tenant_id
93
100
  AND e.actor_type = s.source_type AND e.actor_id = s.source_id
94
101
  AND e.event = ${tag} AND e.sequence > s.delivered)
95
- LIMIT 1`;
102
+ ${withinRange({ sql, range, column: "s.routing_key" })}
103
+ LIMIT 1`)).flat();
96
104
  if (pending !== undefined)
97
105
  problems.push({
98
106
  actorType: writer.actorType,
@@ -159,7 +167,7 @@ const definitionsOf = (actors) => actors.flatMap((actor) => {
159
167
  const payloads = descriptorOf(actor)?.definitionPayloads;
160
168
  return payloads === undefined ? [] : [payloads];
161
169
  });
162
- /** `durable payloads check`: the startup check for `actors`, read-only. */
170
+ /** `akter payloads check`: the startup check for `actors`, read-only. */
163
171
  export const checkPayloads = (actors) => Effect.suspend(() => {
164
172
  const definitions = definitionsOf(actors);
165
173
  return inReadOnlySnapshot(findPayloadProblems(definitions.flatMap((d) => d.declarations), definitions.flatMap((d) => {
@@ -177,7 +185,7 @@ export const checkPayloads = (actors) => Effect.suspend(() => {
177
185
  })));
178
186
  });
179
187
  /**
180
- * `durable payloads clear`: marks each superseded event version of `actors`
188
+ * `akter payloads clear`: marks each superseded event version of `actors`
181
189
  * cleared once its retention horizon has passed, no runtime can still write
182
190
  * it, and no event of it remains. The version row is locked first, the
183
191
  * writer rows are read again after the scan, and each statement of the
@@ -186,7 +194,8 @@ export const checkPayloads = (actors) => Effect.suspend(() => {
186
194
  * its window and then the longest command timeout have passed.
187
195
  */
188
196
  export const clearPayloads = (actors) => Effect.gen(function* () {
189
- const sql = yield* SqlClient.SqlClient;
197
+ const data = yield* SqlClient.SqlClient;
198
+ const sql = yield* registry;
190
199
  const definitions = definitionsOf(actors);
191
200
  const commandTimeoutMs = Math.max(0, ...definitions.map((d) => d.commandTimeoutMs));
192
201
  const results = [];
@@ -219,11 +228,11 @@ export const clearPayloads = (actors) => Effect.gen(function* () {
219
228
  return undefined;
220
229
  if ((yield* writing).length > 0)
221
230
  return "writer";
222
- const stored = yield* sql `SELECT 1 FROM actor_events
231
+ const stored = yield* forEachRange((range) => data `SELECT 1 FROM actor_events
223
232
  WHERE actor_type = ${declared.actorType} AND event = ${declared.tag}
224
- AND payload_version = ${version}
225
- LIMIT 1`;
226
- if (stored.length > 0)
233
+ AND payload_version = ${version} ${withinRange({ sql: data, range, column: "routing_key" })}
234
+ LIMIT 1`);
235
+ if (stored.some((rows) => rows.length > 0))
227
236
  return "stored";
228
237
  if ((yield* writing).length > 0)
229
238
  return "writer";
@@ -33,9 +33,10 @@ export const committedReads = ({ registrations, queryRegistrations, services, al
33
33
  reason: ActorUnavailable.make({ cause: new Error("Actor not registered") }),
34
34
  });
35
35
  const sql = yield* SqlClient.SqlClient;
36
+ const key = routingKey({ ref, placement: registration.placement });
36
37
  const rows = yield* sql `
37
38
  SELECT 1 FROM actor_generations
38
- WHERE routing_key = ${routingKey({ ref, placement: registration.placement })}
39
+ WHERE routing_key = ${key}
39
40
  AND tenant_id = ${ref.tenant} AND actor_type = ${ref.actor} AND actor_id = ${ref.id}`.pipe(withTenant(ref.tenant));
40
41
  return rows.length > 0;
41
42
  }, Effect.provideContext(services), Effect.catchIf(SqlError.isSqlError, (cause) => Effect.fail(ActorError.make({ reason: ActorUnavailable.make({ cause }) }))));
@@ -151,9 +152,10 @@ export const committedReads = ({ registrations, queryRegistrations, services, al
151
152
  });
152
153
  yield* allow(request);
153
154
  const sql = yield* SqlClient.SqlClient;
155
+ const key = routingKey({ ref: request.ref, placement: registration.placement });
154
156
  const [row] = yield* sql `
155
157
  SELECT status, result FROM actor_workflow_executions
156
- WHERE routing_key = ${routingKey({ ref: request.ref, placement: registration.placement })}
158
+ WHERE routing_key = ${key}
157
159
  AND execution_id = ${request.payload} AND tenant_id = ${request.ref.tenant}
158
160
  AND actor_type = ${request.ref.actor} AND actor_id = ${request.ref.id}
159
161
  AND workflow = ${request.command}`.pipe(withTenant(request.ref.tenant));
@@ -17,6 +17,7 @@ import type { SubscriptionRelay } from "./subscriptions/relay.ts";
17
17
  import { DefectLog } from "./telemetry/defects.ts";
18
18
  import { FrameworkClock } from "./turn/admission.ts";
19
19
  import { OutboxRuntime } from "./turn/outbox.ts";
20
+ import type { LiveRecorder } from "./telemetry/live.ts";
20
21
  /**
21
22
  * Registers actor, query, and job layers with the runtime. Each
22
23
  * registration is refused unless its placement, tables, content, workflows,
@@ -25,13 +26,13 @@ import { OutboxRuntime } from "./turn/outbox.ts";
25
26
  * connection holder, and the sweeps, and forgotten when its layer's scope
26
27
  * closes.
27
28
  */
28
- export declare const actorRegistration: ({ registrations, queryRegistrations, jobRegistrations, residency, diagnostics, owners, heldTypes, heldType, sweepsWorkflows, checked, services, frameworkClock, content, retryWindowMs, tableRole, adoptionRole, enforcedTypes, writerDeclarations, refreshPayloadWriters, subscriptions, transport, authorize, gate, writable, defectLog, outbox, }: {
29
+ export declare const actorRegistration: ({ registrations, queryRegistrations, jobRegistrations, residency, diagnostics, owners, heldTypes, heldType, sweepsWorkflows, checked, services, frameworkClock, content, retryWindowMs, tableRole, adoptionRole, enforcedTypes, writerDeclarations, refreshPayloadWriters, subscriptions, transport, authorize, gate, writable, defectLog, outbox, recorder, }: {
29
30
  readonly registrations: Map<string, Registration>;
30
31
  readonly queryRegistrations: Map<string, QueryRegistration>;
31
32
  readonly jobRegistrations: Map<string, JobRegistration>;
32
33
  readonly residency: Map<string, (entityId: string) => boolean>;
33
34
  /** Each actor type's view of its activations, for a delivery that timed out. */
34
- readonly diagnostics: Map<string, Pick<Effect.Success<ReturnType<typeof registerActor>>, "diagnose" | "restarting">>;
35
+ readonly diagnostics: Map<string, Pick<Effect.Success<ReturnType<typeof registerActor>>, "diagnose" | "restarting" | "resident">>;
35
36
  readonly owners: Map<string, Owner>;
36
37
  readonly heldTypes: Map<string, HeldActorType>;
37
38
  readonly heldType: (registration: Registration) => HeldActorType;
@@ -55,4 +56,6 @@ export declare const actorRegistration: ({ registrations, queryRegistrations, jo
55
56
  readonly writable: Effect.Effect<void, ActorError>;
56
57
  readonly defectLog: DefectLog["Service"];
57
58
  readonly outbox: (typeof OutboxRuntime)["Service"];
59
+ /** The live recorder, once the inspector enabled one. */
60
+ readonly recorder: () => LiveRecorder | undefined;
58
61
  }) => Pick<InternalActors["Service"], "register" | "registerQueries" | "registerJobs">;