@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,8 +1,71 @@
1
- import { Effect, Option } from "effect";
1
+ import { Effect, Option, Schedule, Schema } from "effect";
2
2
  import { PgClient } from "@effect/sql-pg";
3
- import { Migrator, SqlClient } from "effect/sql";
4
- import { NekiTurnSessions } from "./neki/session.js";
5
- import { MigrationResuming, nekiMigrator, withMigrationCoordination } from "./neki/migrations.js";
3
+ import { Migrator, SqlClient, SqlError } from "effect/sql";
4
+ import { withMigrationCoordination } from "./coordination.js";
5
+ /** Transactional migrations never resume a partially committed body. */
6
+ const MigrationResuming = Effect.succeed(false);
7
+ /** How long a migration's table DDL waits for its lock before giving way. */
8
+ const LOCK_TIMEOUT = "2s";
9
+ /** How many times a migration's table DDL that gave way is tried again. */
10
+ const LOCK_ATTEMPTS = 30;
11
+ const isLockBusy = Schema.is(Schema.Struct({ code: Schema.Literal("55P03") }));
12
+ /**
13
+ * Runs `ddl`, whose table lock would otherwise wait behind any long
14
+ * transaction on the table and hold every turn queued behind it, waiting at
15
+ * most `LOCK_TIMEOUT` for the lock. On Postgres each attempt runs in a
16
+ * savepoint of the migration's transaction, so one that gave way is rolled
17
+ * back and tried again. The previous `lock_timeout` is restored afterwards.
18
+ */
19
+ const briefLocks = (ddl) => Effect.gen(function* () {
20
+ const sql = yield* SqlClient.SqlClient;
21
+ const [previous] = yield* sql `SELECT current_setting('lock_timeout') AS value`;
22
+ const limit = sql `SELECT set_config('lock_timeout', ${LOCK_TIMEOUT}, true)`;
23
+ const restore = sql `SELECT set_config('lock_timeout', ${previous.value}, true)`.pipe(Effect.ignore);
24
+ return yield* sql.withTransaction(Effect.andThen(limit, ddl)).pipe(Effect.retry({
25
+ while: (error) => SqlError.isSqlError(error) && isLockBusy(error.reason.cause),
26
+ times: LOCK_ATTEMPTS,
27
+ schedule: Schedule.spaced("500 millis"),
28
+ }), Effect.ensuring(restore));
29
+ });
30
+ /** The first set of inspection views and the version the catalog lists for each. */
31
+ const FIRST_VIEWS = [
32
+ ["actors", 1],
33
+ ["state", 1],
34
+ ["receipts", 2],
35
+ ["events", 1],
36
+ ["outbox", 1],
37
+ ["timers", 1],
38
+ ["jobs", 1],
39
+ ["dead_letters", 2],
40
+ ["workflows", 1],
41
+ ["workflow_steps", 1],
42
+ ["contents", 1],
43
+ ["content_refs", 1],
44
+ ["operator_audit", 1],
45
+ ];
46
+ /** The single-table views `0031_routable_views` creates. */
47
+ export const ROUTABLE_VIEWS = [
48
+ "actors_v2",
49
+ "state_v2",
50
+ "receipts_v2",
51
+ "events_v2",
52
+ "outbox_v2",
53
+ "timers_v2",
54
+ "jobs_v2",
55
+ "dead_letters_v2",
56
+ "workflows_v2",
57
+ "workflow_steps_v2",
58
+ "contents_v2",
59
+ "content_sweeps_v2",
60
+ "content_refs_v2",
61
+ "operator_audit_v2",
62
+ "placements_v2",
63
+ ];
64
+ /**
65
+ * Applied migration bodies retain their original calls; every supported database
66
+ * creates the joined inspection views.
67
+ */
68
+ const joinedViews = (_tables, views) => views;
6
69
  /** Every framework migration by id, applied in order above the latest applied id. */
7
70
  export const migrations = {
8
71
  "0001_foundation": Effect.gen(function* () {
@@ -317,74 +380,85 @@ export const migrations = {
317
380
  "0013_inspection_views": Effect.gen(function* () {
318
381
  const sql = yield* SqlClient.SqlClient;
319
382
  yield* sql `CREATE SCHEMA durable`;
320
- yield* sql `CREATE VIEW durable.actors AS
321
- SELECT g.tenant_id, g.actor_type, g.actor_id, g.routing_key, p.placement,
322
- g.generation, g.created, g.event_sequence AS last_event_sequence
323
- FROM actor_generations g
324
- LEFT JOIN actor_placements p ON p.actor_type = g.actor_type`;
325
- yield* sql `CREATE VIEW durable.state AS
326
- SELECT s.tenant_id, s.actor_type, s.actor_id, s.routing_key, p.placement,
327
- s.key, s.value, octet_length(s.value) AS value_bytes
328
- FROM actor_state s
329
- LEFT JOIN actor_placements p ON p.actor_type = s.actor_type`;
330
- yield* sql `CREATE VIEW durable.receipts AS
331
- SELECT r.tenant_id, r.actor_type, r.actor_id, r.routing_key, p.placement,
332
- r.command_id, r.command, r.caller_key,
333
- r.outcome::jsonb ->> '_tag' AS outcome_tag, r.outcome,
334
- r.expires_at_ms, to_timestamp(r.expires_at_ms::float8 / 1000) AS expires_at
335
- FROM actor_receipts r
336
- LEFT JOIN actor_placements p ON p.actor_type = r.actor_type`;
337
- yield* sql `CREATE VIEW durable.events AS
338
- SELECT e.tenant_id, e.actor_type, e.actor_id, e.routing_key, p.placement,
339
- e.sequence, e.event, e.command_id, e.value, octet_length(e.value) AS value_bytes,
340
- e.emitted_at_ms, to_timestamp(e.emitted_at_ms::float8 / 1000) AS emitted_at
341
- FROM actor_events e
342
- LEFT JOIN actor_placements p ON p.actor_type = e.actor_type`;
343
- yield* sql `CREATE VIEW durable.outbox AS
344
- SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key, p.placement,
345
- o.intent_id, o.timer_key, o.target_type, o.target_id, o.command, o.payload, o.caller,
346
- o.attempts, o.last_error, o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at
347
- FROM actor_outbox o
348
- LEFT JOIN actor_placements p ON p.actor_type = o.actor_type
349
- WHERE o.kind = 'intent'`;
350
- yield* sql `CREATE VIEW durable.timers AS
351
- SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key, p.placement,
352
- o.timer_key, o.intent_id, o.target_type, o.target_id, o.command, o.payload, o.caller,
353
- o.attempts, o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at
354
- FROM actor_outbox o
355
- LEFT JOIN actor_placements p ON p.actor_type = o.actor_type
356
- WHERE o.kind = 'intent' AND o.timer_key IS NOT NULL`;
357
- yield* sql `CREATE VIEW durable.effects AS
358
- SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key, p.placement,
359
- o.intent_id AS effect_id, o.command AS effect, o.payload, o.caller,
360
- o.attempts, o.last_error, o.ambiguous,
361
- o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at
362
- FROM actor_outbox o
363
- LEFT JOIN actor_placements p ON p.actor_type = o.actor_type
364
- WHERE o.kind = 'effect'`;
365
- yield* sql `CREATE VIEW durable.dead_letters AS
366
- SELECT d.tenant_id, d.actor_type, d.actor_id, d.routing_key, p.placement,
367
- d.effect_id, d.effect, d.payload, d.attempts, d.cause, d.ambiguous,
368
- d.dead_at_ms, to_timestamp(d.dead_at_ms::float8 / 1000) AS dead_at
369
- FROM actor_dead_letters d
370
- LEFT JOIN actor_placements p ON p.actor_type = d.actor_type`;
371
- yield* sql `CREATE VIEW durable.workflows AS
372
- SELECT w.tenant_id, w.actor_type, w.actor_id, w.routing_key, p.placement,
373
- w.execution_id, w.workflow, w.workflow_key, w.manifest_hash, w.status, w.interrupt,
374
- w.caller, w.payload, octet_length(w.payload) AS payload_bytes,
375
- w.result, octet_length(w.result) AS result_bytes,
376
- w.started_at_ms, to_timestamp(w.started_at_ms::float8 / 1000) AS started_at,
377
- w.finished_at_ms, to_timestamp(w.finished_at_ms::float8 / 1000) AS finished_at
378
- FROM actor_workflow_executions w
379
- LEFT JOIN actor_placements p ON p.actor_type = w.actor_type`;
380
- yield* sql `CREATE VIEW durable.workflow_steps AS
381
- SELECT s.tenant_id, s.actor_type, s.actor_id, s.routing_key, p.placement,
382
- s.execution_id, s.step, s.attempt, s.kind, s.exit, s.wait_event, s.version,
383
- s.due_at_ms, to_timestamp(s.due_at_ms::float8 / 1000) AS due_at,
384
- s.started_at_ms, to_timestamp(s.started_at_ms::float8 / 1000) AS started_at,
385
- s.settled_at_ms, to_timestamp(s.settled_at_ms::float8 / 1000) AS settled_at
386
- FROM actor_workflow_step s
387
- LEFT JOIN actor_placements p ON p.actor_type = s.actor_type`;
383
+ yield* joinedViews([
384
+ "actor_generations",
385
+ "actor_state",
386
+ "actor_receipts",
387
+ "actor_events",
388
+ "actor_outbox",
389
+ "actor_dead_letters",
390
+ "actor_workflow_executions",
391
+ "actor_workflow_step",
392
+ ], Effect.gen(function* () {
393
+ yield* sql `CREATE VIEW durable.actors AS
394
+ SELECT g.tenant_id, g.actor_type, g.actor_id, g.routing_key, p.placement,
395
+ g.generation, g.created, g.event_sequence AS last_event_sequence
396
+ FROM actor_generations g
397
+ LEFT JOIN actor_placements p ON p.actor_type = g.actor_type`;
398
+ yield* sql `CREATE VIEW durable.state AS
399
+ SELECT s.tenant_id, s.actor_type, s.actor_id, s.routing_key, p.placement,
400
+ s.key, s.value, octet_length(s.value) AS value_bytes
401
+ FROM actor_state s
402
+ LEFT JOIN actor_placements p ON p.actor_type = s.actor_type`;
403
+ yield* sql `CREATE VIEW durable.receipts AS
404
+ SELECT r.tenant_id, r.actor_type, r.actor_id, r.routing_key, p.placement,
405
+ r.command_id, r.command, r.caller_key,
406
+ r.outcome::jsonb ->> '_tag' AS outcome_tag, r.outcome,
407
+ r.expires_at_ms, to_timestamp(r.expires_at_ms::float8 / 1000) AS expires_at
408
+ FROM actor_receipts r
409
+ LEFT JOIN actor_placements p ON p.actor_type = r.actor_type`;
410
+ yield* sql `CREATE VIEW durable.events AS
411
+ SELECT e.tenant_id, e.actor_type, e.actor_id, e.routing_key, p.placement,
412
+ e.sequence, e.event, e.command_id, e.value, octet_length(e.value) AS value_bytes,
413
+ e.emitted_at_ms, to_timestamp(e.emitted_at_ms::float8 / 1000) AS emitted_at
414
+ FROM actor_events e
415
+ LEFT JOIN actor_placements p ON p.actor_type = e.actor_type`;
416
+ yield* sql `CREATE VIEW durable.outbox AS
417
+ SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key, p.placement,
418
+ o.intent_id, o.timer_key, o.target_type, o.target_id, o.command, o.payload, o.caller,
419
+ o.attempts, o.last_error, o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at
420
+ FROM actor_outbox o
421
+ LEFT JOIN actor_placements p ON p.actor_type = o.actor_type
422
+ WHERE o.kind = 'intent'`;
423
+ yield* sql `CREATE VIEW durable.timers AS
424
+ SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key, p.placement,
425
+ o.timer_key, o.intent_id, o.target_type, o.target_id, o.command, o.payload, o.caller,
426
+ o.attempts, o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at
427
+ FROM actor_outbox o
428
+ LEFT JOIN actor_placements p ON p.actor_type = o.actor_type
429
+ WHERE o.kind = 'intent' AND o.timer_key IS NOT NULL`;
430
+ yield* sql `CREATE VIEW durable.effects AS
431
+ SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key, p.placement,
432
+ o.intent_id AS effect_id, o.command AS effect, o.payload, o.caller,
433
+ o.attempts, o.last_error, o.ambiguous,
434
+ o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at
435
+ FROM actor_outbox o
436
+ LEFT JOIN actor_placements p ON p.actor_type = o.actor_type
437
+ WHERE o.kind = 'effect'`;
438
+ yield* sql `CREATE VIEW durable.dead_letters AS
439
+ SELECT d.tenant_id, d.actor_type, d.actor_id, d.routing_key, p.placement,
440
+ d.effect_id, d.effect, d.payload, d.attempts, d.cause, d.ambiguous,
441
+ d.dead_at_ms, to_timestamp(d.dead_at_ms::float8 / 1000) AS dead_at
442
+ FROM actor_dead_letters d
443
+ LEFT JOIN actor_placements p ON p.actor_type = d.actor_type`;
444
+ yield* sql `CREATE VIEW durable.workflows AS
445
+ SELECT w.tenant_id, w.actor_type, w.actor_id, w.routing_key, p.placement,
446
+ w.execution_id, w.workflow, w.workflow_key, w.manifest_hash, w.status, w.interrupt,
447
+ w.caller, w.payload, octet_length(w.payload) AS payload_bytes,
448
+ w.result, octet_length(w.result) AS result_bytes,
449
+ w.started_at_ms, to_timestamp(w.started_at_ms::float8 / 1000) AS started_at,
450
+ w.finished_at_ms, to_timestamp(w.finished_at_ms::float8 / 1000) AS finished_at
451
+ FROM actor_workflow_executions w
452
+ LEFT JOIN actor_placements p ON p.actor_type = w.actor_type`;
453
+ yield* sql `CREATE VIEW durable.workflow_steps AS
454
+ SELECT s.tenant_id, s.actor_type, s.actor_id, s.routing_key, p.placement,
455
+ s.execution_id, s.step, s.attempt, s.kind, s.exit, s.wait_event, s.version,
456
+ s.due_at_ms, to_timestamp(s.due_at_ms::float8 / 1000) AS due_at,
457
+ s.started_at_ms, to_timestamp(s.started_at_ms::float8 / 1000) AS started_at,
458
+ s.settled_at_ms, to_timestamp(s.settled_at_ms::float8 / 1000) AS settled_at
459
+ FROM actor_workflow_step s
460
+ LEFT JOIN actor_placements p ON p.actor_type = s.actor_type`;
461
+ }));
388
462
  yield* sql `CREATE VIEW durable.views AS
389
463
  SELECT view_name, version FROM (VALUES
390
464
  ('actors', 1), ('state', 1), ('receipts', 1), ('events', 1), ('outbox', 1),
@@ -618,17 +692,19 @@ export const migrations = {
618
692
  actor_type text PRIMARY KEY,
619
693
  turn_ms bigint NOT NULL CHECK (turn_ms > 0)
620
694
  )`;
621
- yield* sql `CREATE VIEW durable.contents AS
622
- SELECT c.tenant_id, c.routing_key, c.hash, c.size,
623
- c.granted_until_ms, to_timestamp(c.granted_until_ms::float8 / 1000) AS granted_until,
624
- s.swept_at_ms, to_timestamp(s.swept_at_ms::float8 / 1000) AS swept_at
625
- FROM tenant_contents c
626
- LEFT JOIN tenant_content_sweeps s ON s.routing_key = c.routing_key AND s.tenant_id = c.tenant_id`;
627
- yield* sql `CREATE VIEW durable.content_refs AS
628
- SELECT r.tenant_id, r.actor_type, r.actor_id, r.routing_key, p.placement,
629
- r.blob, r.name, r.hash, r.size
630
- FROM actor_content_refs r
631
- LEFT JOIN actor_placements p ON p.actor_type = r.actor_type`;
695
+ yield* joinedViews(["tenant_contents", "tenant_content_sweeps", "actor_content_refs"], Effect.gen(function* () {
696
+ yield* sql `CREATE VIEW durable.contents AS
697
+ SELECT c.tenant_id, c.routing_key, c.hash, c.size,
698
+ c.granted_until_ms, to_timestamp(c.granted_until_ms::float8 / 1000) AS granted_until,
699
+ s.swept_at_ms, to_timestamp(s.swept_at_ms::float8 / 1000) AS swept_at
700
+ FROM tenant_contents c
701
+ LEFT JOIN tenant_content_sweeps s ON s.routing_key = c.routing_key AND s.tenant_id = c.tenant_id`;
702
+ yield* sql `CREATE VIEW durable.content_refs AS
703
+ SELECT r.tenant_id, r.actor_type, r.actor_id, r.routing_key, p.placement,
704
+ r.blob, r.name, r.hash, r.size
705
+ FROM actor_content_refs r
706
+ LEFT JOIN actor_placements p ON p.actor_type = r.actor_type`;
707
+ }));
632
708
  yield* sql `CREATE OR REPLACE VIEW durable.views AS
633
709
  SELECT view_name, version FROM (VALUES
634
710
  ('actors', 1), ('state', 1), ('receipts', 1), ('events', 1), ('outbox', 1),
@@ -685,29 +761,31 @@ export const migrations = {
685
761
  )`;
686
762
  yield* sql `CREATE INDEX actor_payload_writers_version
687
763
  ON actor_payload_writers (actor_type, kind, tag, version, refreshed_at_ms)`;
688
- yield* sql `CREATE OR REPLACE VIEW durable.events AS
689
- SELECT e.tenant_id, e.actor_type, e.actor_id, e.routing_key, p.placement,
690
- e.sequence, e.event, e.command_id, e.value, octet_length(e.value) AS value_bytes,
691
- e.emitted_at_ms, to_timestamp(e.emitted_at_ms::float8 / 1000) AS emitted_at,
692
- e.payload_version
693
- FROM actor_events e
694
- LEFT JOIN actor_placements p ON p.actor_type = e.actor_type`;
695
- yield* sql `CREATE OR REPLACE VIEW durable.effects AS
696
- SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key, p.placement,
697
- o.intent_id AS effect_id, o.command AS effect, o.payload, o.caller,
698
- o.attempts, o.last_error, o.ambiguous,
699
- o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at,
700
- o.payload_version
701
- FROM actor_outbox o
702
- LEFT JOIN actor_placements p ON p.actor_type = o.actor_type
703
- WHERE o.kind = 'effect'`;
704
- yield* sql `CREATE OR REPLACE VIEW durable.dead_letters AS
705
- SELECT d.tenant_id, d.actor_type, d.actor_id, d.routing_key, p.placement,
706
- d.effect_id, d.effect, d.payload, d.attempts, d.cause, d.ambiguous,
707
- d.dead_at_ms, to_timestamp(d.dead_at_ms::float8 / 1000) AS dead_at,
708
- d.payload_version
709
- FROM actor_dead_letters d
710
- LEFT JOIN actor_placements p ON p.actor_type = d.actor_type`;
764
+ yield* joinedViews(["actor_events", "actor_outbox", "actor_dead_letters"], Effect.gen(function* () {
765
+ yield* sql `CREATE OR REPLACE VIEW durable.events AS
766
+ SELECT e.tenant_id, e.actor_type, e.actor_id, e.routing_key, p.placement,
767
+ e.sequence, e.event, e.command_id, e.value, octet_length(e.value) AS value_bytes,
768
+ e.emitted_at_ms, to_timestamp(e.emitted_at_ms::float8 / 1000) AS emitted_at,
769
+ e.payload_version
770
+ FROM actor_events e
771
+ LEFT JOIN actor_placements p ON p.actor_type = e.actor_type`;
772
+ yield* sql `CREATE OR REPLACE VIEW durable.effects AS
773
+ SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key, p.placement,
774
+ o.intent_id AS effect_id, o.command AS effect, o.payload, o.caller,
775
+ o.attempts, o.last_error, o.ambiguous,
776
+ o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at,
777
+ o.payload_version
778
+ FROM actor_outbox o
779
+ LEFT JOIN actor_placements p ON p.actor_type = o.actor_type
780
+ WHERE o.kind = 'effect'`;
781
+ yield* sql `CREATE OR REPLACE VIEW durable.dead_letters AS
782
+ SELECT d.tenant_id, d.actor_type, d.actor_id, d.routing_key, p.placement,
783
+ d.effect_id, d.effect, d.payload, d.attempts, d.cause, d.ambiguous,
784
+ d.dead_at_ms, to_timestamp(d.dead_at_ms::float8 / 1000) AS dead_at,
785
+ d.payload_version
786
+ FROM actor_dead_letters d
787
+ LEFT JOIN actor_placements p ON p.actor_type = d.actor_type`;
788
+ }));
711
789
  }),
712
790
  /**
713
791
  * A parent-placed type routes through its parent type's placement, so the parent is part of
@@ -752,12 +830,12 @@ export const migrations = {
752
830
  yield* sql `CREATE POLICY durable_tenant ON actor_operator_audit
753
831
  USING (tenant_id = current_setting('durable.tenant', true))
754
832
  WITH CHECK (tenant_id = current_setting('durable.tenant', true))`;
755
- yield* sql `CREATE VIEW durable.operator_audit AS
756
- SELECT a.tenant_id, a.actor_type, a.actor_id, a.routing_key, p.placement, a.audit_id,
757
- a.operator, a.action, a.target, a.capability, a.reason, a.outcome,
758
- a.at_ms, to_timestamp(a.at_ms::float8 / 1000) AS at
759
- FROM actor_operator_audit a
760
- LEFT JOIN actor_placements p ON p.actor_type = a.actor_type`;
833
+ yield* joinedViews(["actor_operator_audit"], sql `CREATE VIEW durable.operator_audit AS
834
+ SELECT a.tenant_id, a.actor_type, a.actor_id, a.routing_key, p.placement, a.audit_id,
835
+ a.operator, a.action, a.target, a.capability, a.reason, a.outcome,
836
+ a.at_ms, to_timestamp(a.at_ms::float8 / 1000) AS at
837
+ FROM actor_operator_audit a
838
+ LEFT JOIN actor_placements p ON p.actor_type = a.actor_type`);
761
839
  yield* sql `CREATE OR REPLACE VIEW durable.views AS
762
840
  SELECT view_name, version FROM (VALUES
763
841
  ('actors', 1), ('state', 1), ('receipts', 1), ('events', 1), ('outbox', 1),
@@ -929,24 +1007,26 @@ export const migrations = {
929
1007
  }
930
1008
  yield* sql `ALTER TABLE actor_subscription_tags DROP CONSTRAINT actor_subscription_tags_rows_check,
931
1009
  ADD CONSTRAINT actor_subscription_tags_rows_check CHECK (rows >= 0)`;
932
- yield* sql `DROP VIEW durable.effects`;
933
- yield* sql `DROP VIEW durable.dead_letters`;
934
- yield* sql `CREATE VIEW durable.jobs AS
935
- SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key, p.placement,
936
- o.intent_id AS job_id, o.command AS job, o.payload, o.caller,
937
- o.attempts, o.last_error, o.ambiguous,
938
- o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at,
939
- o.payload_version
940
- FROM actor_outbox o
941
- LEFT JOIN actor_placements p ON p.actor_type = o.actor_type
942
- WHERE o.kind = 'job'`;
943
- yield* sql `CREATE VIEW durable.dead_letters AS
944
- SELECT d.tenant_id, d.actor_type, d.actor_id, d.routing_key, p.placement,
945
- d.job_id, d.job, d.payload, d.attempts, d.cause, d.ambiguous,
946
- d.dead_at_ms, to_timestamp(d.dead_at_ms::float8 / 1000) AS dead_at,
947
- d.payload_version
948
- FROM actor_dead_letters d
949
- LEFT JOIN actor_placements p ON p.actor_type = d.actor_type`;
1010
+ yield* joinedViews(["actor_outbox", "actor_dead_letters"], Effect.gen(function* () {
1011
+ yield* sql `DROP VIEW durable.effects`;
1012
+ yield* sql `DROP VIEW durable.dead_letters`;
1013
+ yield* sql `CREATE VIEW durable.jobs AS
1014
+ SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key, p.placement,
1015
+ o.intent_id AS job_id, o.command AS job, o.payload, o.caller,
1016
+ o.attempts, o.last_error, o.ambiguous,
1017
+ o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at,
1018
+ o.payload_version
1019
+ FROM actor_outbox o
1020
+ LEFT JOIN actor_placements p ON p.actor_type = o.actor_type
1021
+ WHERE o.kind = 'job'`;
1022
+ yield* sql `CREATE VIEW durable.dead_letters AS
1023
+ SELECT d.tenant_id, d.actor_type, d.actor_id, d.routing_key, p.placement,
1024
+ d.job_id, d.job, d.payload, d.attempts, d.cause, d.ambiguous,
1025
+ d.dead_at_ms, to_timestamp(d.dead_at_ms::float8 / 1000) AS dead_at,
1026
+ d.payload_version
1027
+ FROM actor_dead_letters d
1028
+ LEFT JOIN actor_placements p ON p.actor_type = d.actor_type`;
1029
+ }));
950
1030
  yield* sql `CREATE OR REPLACE VIEW durable.views AS
951
1031
  SELECT view_name, version FROM (VALUES
952
1032
  ('actors', 1), ('state', 1), ('receipts', 1), ('events', 1), ('outbox', 1),
@@ -970,6 +1050,169 @@ export const migrations = {
970
1050
  runner_shards BETWEEN 1 AND 65536)
971
1051
  )`;
972
1052
  }),
1053
+ /**
1054
+ * A receipt's start is the clock its batch's fenced read selected, and its commit time the
1055
+ * clock when the commit flight writes the row, so both are the database's own clock and a
1056
+ * receipt binds nothing for the second. The default is set after the column exists, so rows
1057
+ * written before this migration keep `null` instead of the migration's own time.
1058
+ */
1059
+ "0030_receipt_timing": Effect.gen(function* () {
1060
+ const sql = yield* SqlClient.SqlClient;
1061
+ yield* briefLocks(Effect.gen(function* () {
1062
+ yield* sql `ALTER TABLE actor_receipts ADD COLUMN started_at_ms bigint,
1063
+ ADD COLUMN committed_at_ms bigint`;
1064
+ yield* sql `ALTER TABLE actor_receipts ALTER COLUMN committed_at_ms
1065
+ SET DEFAULT floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint`;
1066
+ }));
1067
+ yield* joinedViews(["actor_receipts"], sql `CREATE OR REPLACE VIEW durable.receipts AS
1068
+ SELECT r.tenant_id, r.actor_type, r.actor_id, r.routing_key, p.placement,
1069
+ r.command_id, r.command, r.caller_key,
1070
+ r.outcome::jsonb ->> '_tag' AS outcome_tag, r.outcome,
1071
+ r.expires_at_ms, to_timestamp(r.expires_at_ms::float8 / 1000) AS expires_at,
1072
+ r.started_at_ms, r.committed_at_ms,
1073
+ to_timestamp(r.committed_at_ms::float8 / 1000) AS committed_at,
1074
+ r.committed_at_ms - r.started_at_ms AS duration_ms
1075
+ FROM actor_receipts r
1076
+ LEFT JOIN actor_placements p ON p.actor_type = r.actor_type`);
1077
+ yield* sql `CREATE OR REPLACE VIEW durable.views AS
1078
+ SELECT view_name, version FROM (VALUES
1079
+ ('actors', 1), ('state', 1), ('receipts', 2), ('events', 1), ('outbox', 1),
1080
+ ('timers', 1), ('jobs', 1), ('dead_letters', 2), ('workflows', 1),
1081
+ ('workflow_steps', 1), ('views', 1), ('contents', 1), ('content_refs', 1),
1082
+ ('operator_audit', 1)
1083
+ ) AS v(view_name, version)`;
1084
+ }),
1085
+ /**
1086
+ * A second set of inspection views, each reading exactly one table, with the actor type's
1087
+ * placement in a view of its own (`placements_v2`) that a tool joins by `actor_type`. A Neki
1088
+ * router serves a view on a group routed by `routing_key` only when it is a projection or
1089
+ * filter of one table; the first set joins `actor_placements` to every view, and a view over
1090
+ * several tables of one group is refused too.
1091
+ *
1092
+ * Every column keeps its name, meaning and type apart from `placement`, which moves. A single
1093
+ * table view is automatically updatable, so the migration takes every write privilege on
1094
+ * each of them from its owner; the first set's joins kept it read-only structurally.
1095
+ *
1096
+ * The catalog lists the first set only where it exists, because a routed layout never
1097
+ * created it.
1098
+ */
1099
+ "0031_routable_views": Effect.gen(function* () {
1100
+ const sql = yield* SqlClient.SqlClient;
1101
+ yield* sql `CREATE VIEW durable.actors_v2 AS
1102
+ SELECT g.tenant_id, g.actor_type, g.actor_id, g.routing_key,
1103
+ g.generation, g.created, g.event_sequence AS last_event_sequence
1104
+ FROM actor_generations g`;
1105
+ yield* sql `CREATE VIEW durable.state_v2 AS
1106
+ SELECT s.tenant_id, s.actor_type, s.actor_id, s.routing_key,
1107
+ s.key, s.value, octet_length(s.value) AS value_bytes
1108
+ FROM actor_state s`;
1109
+ yield* sql `CREATE VIEW durable.receipts_v2 AS
1110
+ SELECT r.tenant_id, r.actor_type, r.actor_id, r.routing_key,
1111
+ r.command_id, r.command, r.caller_key,
1112
+ r.outcome::jsonb ->> '_tag' AS outcome_tag, r.outcome,
1113
+ r.expires_at_ms, to_timestamp(r.expires_at_ms::float8 / 1000) AS expires_at,
1114
+ r.started_at_ms, r.committed_at_ms,
1115
+ to_timestamp(r.committed_at_ms::float8 / 1000) AS committed_at,
1116
+ r.committed_at_ms - r.started_at_ms AS duration_ms
1117
+ FROM actor_receipts r`;
1118
+ yield* sql `CREATE VIEW durable.events_v2 AS
1119
+ SELECT e.tenant_id, e.actor_type, e.actor_id, e.routing_key,
1120
+ e.sequence, e.event, e.command_id, e.value, octet_length(e.value) AS value_bytes,
1121
+ e.emitted_at_ms, to_timestamp(e.emitted_at_ms::float8 / 1000) AS emitted_at,
1122
+ e.payload_version
1123
+ FROM actor_events e`;
1124
+ yield* sql `CREATE VIEW durable.outbox_v2 AS
1125
+ SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key,
1126
+ o.intent_id, o.timer_key, o.target_type, o.target_id, o.command, o.payload, o.caller,
1127
+ o.attempts, o.last_error, o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at
1128
+ FROM actor_outbox o
1129
+ WHERE o.kind = 'intent'`;
1130
+ yield* sql `CREATE VIEW durable.timers_v2 AS
1131
+ SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key,
1132
+ o.timer_key, o.intent_id, o.target_type, o.target_id, o.command, o.payload, o.caller,
1133
+ o.attempts, o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at
1134
+ FROM actor_outbox o
1135
+ WHERE o.kind = 'intent' AND o.timer_key IS NOT NULL`;
1136
+ yield* sql `CREATE VIEW durable.jobs_v2 AS
1137
+ SELECT o.tenant_id, o.actor_type, o.actor_id, o.routing_key,
1138
+ o.intent_id AS job_id, o.command AS job, o.payload, o.caller,
1139
+ o.attempts, o.last_error, o.ambiguous,
1140
+ o.due_at_ms, to_timestamp(o.due_at_ms::float8 / 1000) AS due_at,
1141
+ o.payload_version
1142
+ FROM actor_outbox o
1143
+ WHERE o.kind = 'job'`;
1144
+ yield* sql `CREATE VIEW durable.dead_letters_v2 AS
1145
+ SELECT d.tenant_id, d.actor_type, d.actor_id, d.routing_key,
1146
+ d.job_id, d.job, d.payload, d.attempts, d.cause, d.ambiguous,
1147
+ d.dead_at_ms, to_timestamp(d.dead_at_ms::float8 / 1000) AS dead_at,
1148
+ d.payload_version
1149
+ FROM actor_dead_letters d`;
1150
+ yield* sql `CREATE VIEW durable.workflows_v2 AS
1151
+ SELECT w.tenant_id, w.actor_type, w.actor_id, w.routing_key,
1152
+ w.execution_id, w.workflow, w.workflow_key, w.manifest_hash, w.status, w.interrupt,
1153
+ w.caller, w.payload, octet_length(w.payload) AS payload_bytes,
1154
+ w.result, octet_length(w.result) AS result_bytes,
1155
+ w.started_at_ms, to_timestamp(w.started_at_ms::float8 / 1000) AS started_at,
1156
+ w.finished_at_ms, to_timestamp(w.finished_at_ms::float8 / 1000) AS finished_at
1157
+ FROM actor_workflow_executions w`;
1158
+ yield* sql `CREATE VIEW durable.workflow_steps_v2 AS
1159
+ SELECT s.tenant_id, s.actor_type, s.actor_id, s.routing_key,
1160
+ s.execution_id, s.step, s.attempt, s.kind, s.exit, s.wait_event, s.version,
1161
+ s.due_at_ms, to_timestamp(s.due_at_ms::float8 / 1000) AS due_at,
1162
+ s.started_at_ms, to_timestamp(s.started_at_ms::float8 / 1000) AS started_at,
1163
+ s.settled_at_ms, to_timestamp(s.settled_at_ms::float8 / 1000) AS settled_at
1164
+ FROM actor_workflow_step s`;
1165
+ yield* sql `CREATE VIEW durable.contents_v2 AS
1166
+ SELECT c.tenant_id, c.routing_key, c.hash, c.size,
1167
+ c.granted_until_ms, to_timestamp(c.granted_until_ms::float8 / 1000) AS granted_until
1168
+ FROM tenant_contents c`;
1169
+ yield* sql `CREATE VIEW durable.content_sweeps_v2 AS
1170
+ SELECT s.tenant_id, s.routing_key,
1171
+ s.swept_at_ms, to_timestamp(s.swept_at_ms::float8 / 1000) AS swept_at
1172
+ FROM tenant_content_sweeps s`;
1173
+ yield* sql `CREATE VIEW durable.content_refs_v2 AS
1174
+ SELECT r.tenant_id, r.actor_type, r.actor_id, r.routing_key,
1175
+ r.blob, r.name, r.hash, r.size
1176
+ FROM actor_content_refs r`;
1177
+ yield* sql `CREATE VIEW durable.operator_audit_v2 AS
1178
+ SELECT a.tenant_id, a.actor_type, a.actor_id, a.routing_key, a.audit_id,
1179
+ a.operator, a.action, a.target, a.capability, a.reason, a.outcome,
1180
+ a.at_ms, to_timestamp(a.at_ms::float8 / 1000) AS at
1181
+ FROM actor_operator_audit a`;
1182
+ yield* sql `CREATE VIEW durable.placements_v2 AS
1183
+ SELECT p.actor_type, p.placement, p.parent_type
1184
+ FROM actor_placements p`;
1185
+ yield* sql.unsafe(`REVOKE INSERT, UPDATE, DELETE, TRUNCATE ON ${ROUTABLE_VIEWS.map((view) => `durable.${view}`).join(", ")} FROM CURRENT_USER`);
1186
+ const present = yield* sql `
1187
+ SELECT relname AS view FROM pg_class
1188
+ WHERE relnamespace = 'durable'::regnamespace AND relkind = 'v'`;
1189
+ const first = FIRST_VIEWS.filter(([view]) => present.some((row) => row.view === view));
1190
+ const rows = [...first, ...ROUTABLE_VIEWS.map((view) => [view, 1]), ["views", 1]];
1191
+ yield* sql.unsafe(`CREATE OR REPLACE VIEW durable.views AS
1192
+ SELECT view_name, version FROM (VALUES
1193
+ ${rows.map(([view, version]) => `('${view}', ${version})`).join(", ")}
1194
+ ) AS v(view_name, version)`);
1195
+ }),
1196
+ /**
1197
+ * An authority-placed type keeps every routing key in the one bucket a split topology leaves
1198
+ * on the authoritative shard, so its turns reach the tables the database does not route on
1199
+ * the shard that holds them.
1200
+ */
1201
+ "0032_authority_placement": Effect.gen(function* () {
1202
+ const sql = yield* SqlClient.SqlClient;
1203
+ yield* sql `ALTER TABLE actor_placements DROP CONSTRAINT actor_placements_placement_check,
1204
+ ADD CONSTRAINT actor_placements_placement_check
1205
+ CHECK (placement IN ('tenant', 'actor', 'parent', 'authority'))`;
1206
+ }),
1207
+ /** Retires the single-table inspection variants without rewriting the applied migration that created them. */
1208
+ "0033_joined_inspection": Effect.gen(function* () {
1209
+ const sql = yield* SqlClient.SqlClient;
1210
+ yield* sql.unsafe(`DROP VIEW ${ROUTABLE_VIEWS.map((view) => `durable.${view}`).join(", ")}`);
1211
+ yield* sql.unsafe(`CREATE OR REPLACE VIEW durable.views AS
1212
+ SELECT view_name, version FROM (VALUES
1213
+ ${[...FIRST_VIEWS, ["views", 1]].map(([view, version]) => `('${view}', ${version})`).join(", ")}
1214
+ ) AS v(view_name, version)`);
1215
+ }),
973
1216
  };
974
1217
  /**
975
1218
  * Runs `record` like `Migrator`, but first refuses a database where a
@@ -1002,16 +1245,9 @@ export const migrator = (record) => {
1002
1245
  });
1003
1246
  const transactional = refuseSkipped.pipe(Effect.andThen(run), Effect.tap(() => refuseSkipped));
1004
1247
  return Effect.gen(function* () {
1005
- const neki = yield* NekiTurnSessions;
1006
- const selected = neki
1007
- ? nekiMigrator({
1008
- record: Object.fromEntries(Object.entries(record).map(([key, effect]) => [key, Effect.orDie(effect)])),
1009
- refuseSkipped,
1010
- })
1248
+ return yield* Option.isSome(yield* Effect.serviceOption(PgClient.PgClient))
1249
+ ? withMigrationCoordination(transactional)
1011
1250
  : transactional;
1012
- return yield* neki || Option.isSome(yield* Effect.serviceOption(PgClient.PgClient))
1013
- ? withMigrationCoordination(selected)
1014
- : selected;
1015
1251
  });
1016
1252
  };
1017
1253
  /** Applies every framework migration not yet recorded in `actor_migrations`, refusing a database that skipped one. */
@@ -28,7 +28,10 @@ export declare const replicaLayer: (options: PgClient.PgPoolConfig | undefined)
28
28
  * is built on that client.
29
29
  */
30
30
  export declare const QueryPool: Context.Reference<SqlClient.SqlClient | undefined>;
31
- /** Provides `QueryPool` as a bounded, first-come, first-served pool for `options`. */
31
+ /**
32
+ * Provides `QueryPool` as a bounded, first-come, first-served pool for
33
+ * `options`, independent of the off-turn client's checkouts.
34
+ */
32
35
  export declare const queryPoolLayer: (options: PgClient.PgPoolConfig) => Layer.Layer<never, import("effect/sql/SqlError").SqlError, never>;
33
36
  /**
34
37
  * Whether the replica has replayed WAL through `version`. A server not in
@@ -34,7 +34,10 @@ export const replicaLayer = (options) => Layer.effect(ReadReplica, Effect.gen(fu
34
34
  * is built on that client.
35
35
  */
36
36
  export const QueryPool = Context.Reference("@rikalabs/akter/runtime/database/replica/QueryPool", { defaultValue: () => undefined });
37
- /** Provides `QueryPool` as a bounded, first-come, first-served pool for `options`. */
37
+ /**
38
+ * Provides `QueryPool` as a bounded, first-come, first-served pool for
39
+ * `options`, independent of the off-turn client's checkouts.
40
+ */
38
41
  export const queryPoolLayer = (options) => Layer.effect(QueryPool, boundedPool(options)).pipe(Layer.provide(Reactivity.layer));
39
42
  /**
40
43
  * Whether the replica has replayed WAL through `version`. A server not in
@@ -0,0 +1,13 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ import type { SqlError } from "effect/sql";
4
+ /**
5
+ * Runs an application's own schema setup on the ambient `SqlClient` while
6
+ * holding advisory lock `lock`, so processes starting together apply it one
7
+ * at a time. The lock and every statement share one transaction, so a failed
8
+ * setup rolls back together and releases its lock.
9
+ */
10
+ export declare const schemaChange: {
11
+ (lock: number): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | SqlError.SqlError, R | SqlClient.SqlClient>;
12
+ <A, E, R>(effect: Effect.Effect<A, E, R>, lock: number): Effect.Effect<A, E | SqlError.SqlError, R | SqlClient.SqlClient>;
13
+ };