@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,142 +0,0 @@
1
- import { Cause, Effect, Exit, Layer, Option, Predicate, Schedule, Stream } from "effect";
2
- import { SqlClient } from "effect/sql";
3
- import { ActorError } from "../../../errors/actor.js";
4
- import { System } from "../../../identity/caller.js";
5
- import { connectionHolder, RESYNC_DEADLINE_MS, } from "../../../runtime/connections/holder.js";
6
- import { ClientMessage, HolderItem } from "../../../runtime/connections/protocol.js";
7
- import { FrameworkClock } from "../../../runtime/turn/admission.js";
8
- import { ActorTest } from "../../actor-test.js";
9
- import { ActorCluster } from "../../cluster.js";
10
- import { Live, Room, connectionsLayer } from "./actors.js";
11
- export const isFrame = (message) => Predicate.isTagged(message, "Frame");
12
- export const frameOf = (message) => isFrame(message) ? message.frame : undefined;
13
- export const cursorOf = (message) => isFrame(message) ? message.cursor : undefined;
14
- /** Reads the next `count` envelopes, control frames included. */
15
- export const next = (connection, count = 1) => connection.messages.pipe(Stream.take(count), Stream.runCollect, Effect.map((chunk) => [...chunk]), Effect.timeoutOrElse({
16
- duration: "20 seconds",
17
- orElse: () => Effect.die(new Error("No connection frame arrived")),
18
- }));
19
- /** Drains envelopes until the session ends and returns how it ended. */
20
- export const endOf = (connection) => connection.messages.pipe(Stream.runDrain, Effect.exit, Effect.timeoutOrElse({
21
- duration: "20 seconds",
22
- orElse: () => Effect.die(new Error("The session did not end")),
23
- }), Effect.map((exit) => Exit.isFailure(exit) ? Option.getOrUndefined(Cause.findErrorOption(exit.cause)) : undefined));
24
- export const reasonOf = (error) => error?.reason;
25
- export const rows = Effect.fnUntraced(function* (ref) {
26
- const sql = yield* SqlClient.SqlClient;
27
- return yield* sql `
28
- SELECT member, frame_seq::text AS frame_seq, session FROM actor_connections
29
- WHERE tenant_id = ${ref.tenant} AND actor_type = ${ref.actor} AND actor_id = ${ref.id}`;
30
- }, Effect.orDie);
31
- export const connect = (id, name = "alice") => Effect.gen(function* () {
32
- const test = yield* ActorTest;
33
- const room = yield* Room.get(id);
34
- const connection = yield* test.connect(room.ref, Live, { name });
35
- return { test, room, connection };
36
- });
37
- /** The `Echo` executor, which every cluster runner builds. */
38
- const connectionsEffects = (fixture) => Room.toJobLayer(Effect.succeed({
39
- LiveEcho: ({ text }) => Effect.suspend(() => fixture.echo).pipe(Effect.as(text)),
40
- }));
41
- /** Reports whether no envelope arrives within one second. */
42
- export const quiet = (connection) => connection.messages.pipe(Stream.take(1), Stream.runCollect, Effect.timeout("1 second"), Effect.option, Effect.map(Option.isNone));
43
- /** Reads envelopes through the next `ResyncReplayed`. */
44
- export const throughReplayed = (connection) => connection.messages.pipe(Stream.takeUntil((message) => Predicate.isTagged(message, "ResyncReplayed")), Stream.runCollect, Effect.map((chunk) => [...chunk]), Effect.timeoutOrElse({
45
- duration: "60 seconds",
46
- orElse: () => Effect.die(new Error("No ResyncReplayed arrived")),
47
- }));
48
- /** Collects envelopes until the session ends and returns them with how it ended. */
49
- export const untilEnd = (connection) => Effect.gen(function* () {
50
- const seen = [];
51
- const exit = yield* connection.messages.pipe(Stream.runForEach((message) => Effect.sync(() => seen.push(message))), Effect.exit, Effect.timeoutOrElse({
52
- duration: "60 seconds",
53
- orElse: () => Effect.die(new Error("The session did not end")),
54
- }));
55
- const failure = Exit.isFailure(exit) ? Cause.findErrorOption(exit.cause) : Option.none();
56
- return { seen, ended: reasonOf(Option.getOrUndefined(failure)) };
57
- });
58
- export const posts = (ref) => ActorTest.use((test) => test.inspect(ref)).pipe(Effect.map(({ state }) => state.posts ?? 0));
59
- const EXPIRATION_SECONDS = 3;
60
- export const withCluster = (environment, fixture, options, body) => environment.run(Effect.gen(function* () {
61
- const database = yield* environment.freshDatabase;
62
- const context = yield* Layer.build(ActorTest.cluster({
63
- database,
64
- runners: options.runners,
65
- holdersOnly: options.holdersOnly,
66
- shardLockExpiration: `${EXPIRATION_SECONDS} seconds`,
67
- actors: connectionsLayer(fixture),
68
- runnerActors: () => connectionsEffects(fixture),
69
- }));
70
- return yield* body.pipe(Effect.provideContext(context));
71
- }));
72
- const OWNER = { generation: "1", owner: "owner", ownerEpoch: "owner-epoch" };
73
- /**
74
- * A holder on its own transport whose owner side is `channel`, for cases that
75
- * need an owner answer the real one never gives. Unless `channel.open` says
76
- * otherwise, opening copies the row of the real connection `template`, so the
77
- * holder's liveness check keeps the session.
78
- */
79
- export const fakeHolder = Effect.fnUntraced(function* (options) {
80
- const sql = yield* SqlClient.SqlClient;
81
- const holderName = `${options.name}-holder`;
82
- const epoch = `${options.name}-epoch`;
83
- const copyRow = (request) => sql `
84
- INSERT INTO actor_connections (
85
- routing_key, connection_id, bucket, tenant_id, actor_type, actor_id, member,
86
- holder, holder_epoch, caller, session, opened_at_ms, opened_through
87
- )
88
- SELECT routing_key, ${request.connectionId}, bucket, tenant_id, actor_type, actor_id,
89
- member, ${request.holder}, ${request.holderEpoch}, caller, NULL, opened_at_ms, 0
90
- FROM actor_connections WHERE connection_id = ${options.template}`.pipe(Effect.orDie, Effect.as({ _tag: "Opened", ...OWNER, baseline: "0" }));
91
- const type = {
92
- deliveryMs: 1_000,
93
- takeoverMs: 5_000,
94
- reauthorizeMs: 60_000,
95
- retryWindowMs: 60_000,
96
- placement: "actor",
97
- hasResync: () => true,
98
- hasMember: () => true,
99
- routingKey: () => 0n,
100
- ...options.type,
101
- channel: {
102
- open: copyRow,
103
- frame: () => Effect.die(new Error("No frame is sent")),
104
- close: () => Effect.void,
105
- resync: () => Effect.never,
106
- ...options.channel?.(copyRow),
107
- },
108
- };
109
- const holder = yield* connectionHolder({
110
- transport: () => ({
111
- holder: holderName,
112
- epoch,
113
- deliver: () => Effect.die(new Error("No owner delivers")),
114
- ping: () => Effect.sync(() => options.alive?.() ?? true),
115
- }),
116
- actorType: () => type,
117
- authorize: () => Effect.succeed(true),
118
- }).pipe(Effect.provideService(FrameworkClock, { offsetMillis: () => options.offset?.() ?? 0 }));
119
- const open = (ref) => holder.open({ ref, member: Live.tag, caller: System.make({ source: "actor" }), params: "{}" });
120
- /** An owner message on the generation's channel carrying `items`. */
121
- const message = (ref, generation, seq, items) => ({ epoch, ...OWNER, ref, generation, seq, through: "0", items });
122
- return { holder, open, message };
123
- });
124
- /** An owner frame to `to`; the fake holder never decodes it. */
125
- export const rawFrame = (to, frame) => HolderItem.cases.Frame.make({ member: Live.tag, to, frame, stamp: true });
126
- /** Collects a held connection's messages until it ends and returns them with how it ended. */
127
- export const heldUntilEnd = (held) => Effect.gen(function* () {
128
- const seen = [];
129
- const exit = yield* held.messages.pipe(Stream.runForEach((message) => Effect.sync(() => seen.push(message))), Effect.exit, Effect.timeoutOrElse({
130
- duration: "20 seconds",
131
- orElse: () => Effect.die(new Error("The held session did not end")),
132
- }));
133
- const failure = Exit.isFailure(exit) ? Cause.findErrorOption(exit.cause) : Option.none();
134
- return { seen, ended: reasonOf(Option.getOrUndefined(failure)) };
135
- });
136
- /** The `Resync` a holder sends when an owner is lost, proving continuity through `after`. */
137
- export const resyncFrom = (after) => ClientMessage.cases.Resync.make({ after, reason: "OwnerLost", deadlineMs: RESYNC_DEADLINE_MS });
138
- /** Polls `check` until it holds. */
139
- export const eventually = (check, what) => check.pipe(Effect.repeat({ schedule: Schedule.spaced("25 millis"), until: (held) => held }), Effect.timeoutOrElse({
140
- duration: "20 seconds",
141
- orElse: () => Effect.die(new Error(`Timed out waiting for ${what}`)),
142
- }), Effect.asVoid);
@@ -1,4 +0,0 @@
1
- import type { ConformanceCase } from "../../conformance.ts";
2
- import { type ConnectionsFixture } from "./actors.ts";
3
- /** Holder-side generation, liveness, redelivery, and close handling of connections. */
4
- export declare const connectionHolderConformance: ReadonlyArray<ConformanceCase<ConnectionsFixture>>;
@@ -1,165 +0,0 @@
1
- import { Cause, Effect, Exit, Option, Predicate, Schema, Stream } from "effect";
2
- import { SqlClient } from "effect/sql";
3
- import { ActorError, SessionEnded, Unauthorized } from "../../../errors/actor.js";
4
- import { MAX_OUTBOUND_BYTES, MAX_OUTBOUND_FRAMES } from "../../../runtime/connections/holder.js";
5
- import { ClientMessage } from "../../../runtime/connections/protocol.js";
6
- import { Hello, Said, Say, receipted } from "./actors.js";
7
- import { connect, eventually, fakeHolder, frameOf, heldUntilEnd, isFrame, next, rawFrame, resyncFrom, untilEnd, } from "./harness.js";
8
- /** Holder-side generation, liveness, redelivery, and close handling of connections. */
9
- export const connectionHolderConformance = [
10
- {
11
- name: "a command and a frame that wake one parked actor together acquire one generation",
12
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
13
- const { test, room, connection } = yield* connect("connections-together");
14
- yield* next(connection);
15
- yield* test.hibernate(room.ref);
16
- const parked = BigInt((yield* test.inspect(room.ref)).generation);
17
- yield* Effect.all([room.Post("together"), connection.send(Say.make({ text: "whoami" }))], { concurrency: "unbounded", discard: true });
18
- const woken = (yield* next(connection, 2)).map(frameOf);
19
- const said = woken.find((frame) => Predicate.isTagged(frame, "Said"));
20
- const hello = woken.find((frame) => Predicate.isTagged(frame, "Hello"));
21
- expect(said).toEqual(Said.make({ text: "together" }));
22
- expect(hello).toEqual(Hello.make({ name: "alice", resumed: true, frames: 1 }));
23
- expect(BigInt((yield* test.inspect(room.ref)).generation)).toBe(parked + 1n);
24
- })),
25
- },
26
- {
27
- name: "a holder whose liveness check fails for a whole reauthorization bound ends its sessions, and later opens are not blamed for it",
28
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
29
- const sql = yield* SqlClient.SqlClient;
30
- const { test, connection } = yield* connect("connections-blind");
31
- yield* next(connection);
32
- yield* sql `ALTER TABLE actor_connections RENAME TO actor_connections_hidden`.pipe(Effect.orDie);
33
- const { ended } = yield* Effect.gen(function* () {
34
- yield* test.advance("11 seconds");
35
- yield* Effect.sleep("500 millis");
36
- yield* test.advance("40 seconds");
37
- yield* Effect.sleep("500 millis");
38
- yield* test.advance("21 seconds");
39
- return yield* untilEnd(connection);
40
- }).pipe(Effect.ensuring(sql `ALTER TABLE actor_connections_hidden RENAME TO actor_connections`.pipe(Effect.orDie)));
41
- expect(Schema.is(SessionEnded)(ended)).toBe(true);
42
- expect(ended).toMatchObject({ cause: "ActorUnavailable", resync: true });
43
- const after = yield* connect("connections-sighted");
44
- yield* next(after.connection);
45
- yield* Effect.sleep("300 millis");
46
- yield* after.connection.send(Say.make({ text: "whoami" }));
47
- const [answer] = yield* next(after.connection);
48
- expect(frameOf(answer)).toEqual(Hello.make({ name: "alice", resumed: false, frames: 1 }));
49
- })),
50
- },
51
- {
52
- name: "a holder applies a redelivered owner message once and counts resync-deferred frames against the outbound limits",
53
- timeoutMs: 30_000,
54
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
55
- const { room, connection } = yield* connect("connections-redelivered");
56
- yield* next(connection);
57
- let alive = true;
58
- const fake = yield* fakeHolder({
59
- name: "redelivered",
60
- template: connection.connectionId,
61
- alive: () => alive,
62
- type: { takeoverMs: 60_000 },
63
- });
64
- const frames = yield* fake.open(room.ref);
65
- const bytes = yield* fake.open(room.ref);
66
- const once = fake.message(room.ref, "1", 1, [rawFrame([frames.connectionId], "once")]);
67
- yield* fake.holder.deliver(once);
68
- expect(yield* fake.holder.deliver(once)).toEqual({ wrongEpoch: false, unknown: [] });
69
- yield* fake.holder.deliver(fake.message(room.ref, "1", 2, [rawFrame([frames.connectionId], "twice")]));
70
- const delivered = yield* frames.messages.pipe(Stream.take(2), Stream.runCollect, Effect.timeout("5 seconds"), Effect.orDie);
71
- expect([...delivered].map((message) => ClientMessage.guards.Frame(message) ? message.frame : undefined)).toEqual(["once", "twice"]);
72
- alive = false;
73
- for (const held of [frames, bytes]) {
74
- const [resync] = yield* held.messages.pipe(Stream.take(1), Stream.runCollect, Effect.timeout("10 seconds"), Effect.orDie);
75
- expect(resync?._tag).toBe("Resync");
76
- }
77
- const half = "x".repeat(MAX_OUTBOUND_BYTES / 2 + 1);
78
- yield* fake.holder.deliver(fake.message(room.ref, "2", 1, [
79
- ...Array.from({ length: MAX_OUTBOUND_FRAMES + 1 }, (_, index) => rawFrame([frames.connectionId], `deferred ${index}`)),
80
- rawFrame([bytes.connectionId], half),
81
- rawFrame([bytes.connectionId], half),
82
- ]));
83
- for (const held of [frames, bytes]) {
84
- const { seen, ended } = yield* heldUntilEnd(held);
85
- expect(seen).toEqual([]);
86
- expect(ended).toMatchObject({ cause: "SlowConsumer", resync: true });
87
- }
88
- })),
89
- },
90
- {
91
- name: "a holder never delivers a broadcast that arrives past the session's authorization bound",
92
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
93
- const { room, connection } = yield* connect("connections-late-broadcast");
94
- yield* next(connection);
95
- let offset = 0;
96
- const fake = yield* fakeHolder({
97
- name: "late-broadcast",
98
- template: connection.connectionId,
99
- offset: () => offset,
100
- });
101
- const held = yield* fake.open(room.ref);
102
- offset = 60_001;
103
- yield* fake.holder.deliver(fake.message(room.ref, "1", 1, [rawFrame([held.connectionId], "late")]));
104
- const { seen, ended } = yield* heldUntilEnd(held);
105
- expect(seen).toEqual([]);
106
- expect(ended).toMatchObject(Unauthorized.make({ code: "reauthorization_unavailable" }));
107
- })),
108
- },
109
- {
110
- name: "a holder closes a connection again when its owner commits the open after the holder gave up",
111
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
112
- const { room, connection } = yield* connect("connections-late-open");
113
- yield* next(connection);
114
- const closes = [];
115
- const fake = yield* fakeHolder({
116
- name: "late-open",
117
- template: connection.connectionId,
118
- type: { deliveryMs: 200 },
119
- channel: (copyRow) => ({
120
- open: (request) => copyRow(request).pipe(Effect.delay("600 millis")),
121
- close: (request) => Effect.sync(() => closes.push(request.cause)),
122
- }),
123
- });
124
- const exit = yield* fake.open(room.ref).pipe(Effect.exit);
125
- const failure = Exit.isFailure(exit) ? Cause.findErrorOption(exit.cause) : Option.none();
126
- const reason = Option.filter(failure, Schema.is(ActorError)).pipe(Option.map((error) => error.reason), Option.getOrUndefined);
127
- expect(reason).toMatchObject({ cause: "ActorUnavailable", resync: true });
128
- yield* eventually(Effect.sync(() => closes.length > 0), "the late open to be closed");
129
- expect(closes).toMatchObject([{ cause: "ActorUnavailable", resync: true }]);
130
- })),
131
- },
132
- {
133
- name: "a resync whose new owner never answers within the takeover bound closes with OwnerLost",
134
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
135
- const { room, connection } = yield* connect("connections-unanswered");
136
- yield* next(connection);
137
- const requests = [];
138
- const fake = yield* fakeHolder({
139
- name: "unanswered",
140
- template: connection.connectionId,
141
- type: { takeoverMs: 300 },
142
- channel: (copyRow) => ({
143
- open: (request) => copyRow(request).pipe(Effect.map((opened) => ({ ...opened, baseline: "4", recovered: true }))),
144
- resync: (request) => Effect.sync(() => requests.push(request.after)).pipe(Effect.andThen(Effect.never)),
145
- }),
146
- });
147
- const held = yield* fake.open(room.ref);
148
- const { seen, ended } = yield* heldUntilEnd(held);
149
- expect(seen).toMatchObject([resyncFrom("4")]);
150
- expect(requests).toEqual(["4"]);
151
- expect(ended).toMatchObject({ cause: "OwnerLost", resync: true });
152
- })),
153
- },
154
- {
155
- name: "sends a server frame shaped like an event entry as the frame itself when its event is no server frame",
156
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
157
- const { connection } = yield* connect("connections-receipted");
158
- yield* next(connection);
159
- yield* connection.send(Say.make({ text: "receipted" }));
160
- const [sent] = yield* next(connection);
161
- expect(frameOf(sent)).toEqual(receipted);
162
- expect(isFrame(sent) ? sent.event : "stamped").toBe(undefined);
163
- })),
164
- },
165
- ];
@@ -1,4 +0,0 @@
1
- import type { ConformanceCase } from "../../conformance.ts";
2
- import { type ConnectionsFixture } from "./actors.ts";
3
- /** Opening, ordered frames, stored sessions, parking, and broadcasts of connections. */
4
- export declare const connectionLifecycleConformance: ReadonlyArray<ConformanceCase<ConnectionsFixture>>;
@@ -1,113 +0,0 @@
1
- import { Cause, Effect, Exit, Option, Schedule } from "effect";
2
- import { CurrentCaller, System, Tenant } from "../../../identity/caller.js";
3
- import { ActorTest } from "../../actor-test.js";
4
- import { Banned, Hello, Live, Room, Said, Say } from "./actors.js";
5
- import { connect, cursorOf, frameOf, isFrame, next, rows } from "./harness.js";
6
- /** Opening, ordered frames, stored sessions, parking, and broadcasts of connections. */
7
- export const connectionLifecycleConformance = [
8
- {
9
- name: "a connection opened after every earlier one to a resident actor closed still receives broadcasts",
10
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
11
- const { test, room, connection } = yield* connect("connections-reopen");
12
- yield* next(connection);
13
- yield* room.Post("first");
14
- expect(frameOf((yield* next(connection))[0])).toEqual(Said.make({ text: "first" }));
15
- yield* connection.close;
16
- const again = yield* test.connect(room.ref, Live, { name: "bob" });
17
- yield* next(again);
18
- yield* room.Post("second");
19
- expect(frameOf((yield* next(again))[0])).toEqual(Said.make({ text: "second" }));
20
- })),
21
- },
22
- {
23
- name: "connection opens, answers frames in order, stores its session, and leaves no row once closed",
24
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
25
- const { room, connection } = yield* connect("connections-open");
26
- const [hello] = yield* next(connection);
27
- expect(frameOf(hello)).toEqual(Hello.make({ name: "alice", resumed: false, frames: 0 }));
28
- yield* connection.send(Say.make({ text: "whoami" }));
29
- yield* connection.send(Say.make({ text: "whoami" }));
30
- const answers = yield* next(connection, 2);
31
- expect(answers.map(frameOf)).toEqual([
32
- Hello.make({ name: "alice", resumed: false, frames: 1 }),
33
- Hello.make({ name: "alice", resumed: false, frames: 2 }),
34
- ]);
35
- const [row] = yield* rows(room.ref);
36
- expect(row).toMatchObject({ member: "Live", frame_seq: "2" });
37
- expect(row.session === null).toBe(false);
38
- yield* connection.close;
39
- yield* rows(room.ref).pipe(Effect.repeat({
40
- schedule: Schedule.spaced("20 millis"),
41
- until: (found) => found.length === 0,
42
- }), Effect.timeout("5 seconds"), Effect.orDie);
43
- })),
44
- },
45
- {
46
- name: "runs a frame handler in the actor's tenant as the caller stored at open",
47
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
48
- const { room, connection } = yield* connect("connections-caller");
49
- yield* next(connection);
50
- yield* connection
51
- .send(Say.make({ text: "caller" }))
52
- .pipe(Effect.provideService(CurrentCaller, System.make({ source: "actor" })), Effect.provideService(Tenant, "elsewhere"));
53
- const [answer] = yield* next(connection);
54
- expect(frameOf(answer)).toEqual(Hello.make({ name: `${room.ref.tenant}/User/alice`, resumed: false, frames: 1 }));
55
- })),
56
- },
57
- {
58
- name: "a declared open failure rejects the connection and stores nothing",
59
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
60
- const test = yield* ActorTest;
61
- const room = yield* Room.get("connections-banned");
62
- const exit = yield* test.connect(room.ref, Live, { name: "mallory" }).pipe(Effect.exit);
63
- expect(Exit.isFailure(exit) && Cause.findErrorOption(exit.cause)).toMatchObject(Option.some(Banned.make({ name: "mallory" })));
64
- expect(yield* rows(room.ref)).toEqual([]);
65
- })),
66
- },
67
- {
68
- name: "a parked connection survives hibernation and its next frame wakes the actor with the stored session",
69
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
70
- const { test, room, connection } = yield* connect("connections-park");
71
- yield* next(connection);
72
- yield* connection.send(Say.make({ text: "whoami" }));
73
- yield* next(connection);
74
- const before = (yield* test.inspect(room.ref)).generation;
75
- yield* test.hibernate(room.ref);
76
- yield* connection.send(Say.make({ text: "whoami" }));
77
- const [woken] = yield* next(connection);
78
- expect(frameOf(woken)).toEqual(Hello.make({ name: "alice", resumed: true, frames: 2 }));
79
- expect(BigInt((yield* test.inspect(room.ref)).generation) > BigInt(before)).toBe(true);
80
- })),
81
- },
82
- {
83
- name: "hibernating before the owner sends anything seals the holder, so the next frame resyncs nothing",
84
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
85
- const { test, room, connection } = yield* connect("connections-park-quiet");
86
- yield* next(connection);
87
- for (let round = 0; round < 3; round++) {
88
- yield* test.hibernate(room.ref);
89
- yield* connection.send(Say.make({ text: "whoami" }));
90
- const [woken] = yield* next(connection);
91
- expect(isFrame(woken)).toBe(true);
92
- }
93
- })),
94
- },
95
- {
96
- name: "a turn's broadcast wakes a parked actor, flushes only after commit, and carries cursor stamps",
97
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
98
- const { test, room, connection } = yield* connect("connections-broadcast");
99
- yield* next(connection);
100
- yield* test.hibernate(room.ref);
101
- const refused = yield* room.Post("refuse").pipe(Effect.exit);
102
- expect(Exit.isFailure(refused)).toBe(true);
103
- yield* room.Post("hello");
104
- const [said] = yield* next(connection);
105
- expect(frameOf(said)).toEqual(Said.make({ text: "hello" }));
106
- expect(cursorOf(said)).toBe(connection.cursor);
107
- yield* room.Post("again");
108
- const [again] = yield* next(connection);
109
- expect(frameOf(again)).toEqual(Said.make({ text: "again" }));
110
- expect(BigInt(cursorOf(again) ?? "0") > BigInt(connection.cursor)).toBe(true);
111
- })),
112
- },
113
- ];
@@ -1,4 +0,0 @@
1
- import type { ConformanceCase } from "../../conformance.ts";
2
- import { type ConnectionsFixture } from "./actors.ts";
3
- /** Session and frame limits, broadcast ordering, and rejected opens of connections. */
4
- export declare const connectionLimitConformance: ReadonlyArray<ConformanceCase<ConnectionsFixture>>;
@@ -1,127 +0,0 @@
1
- import { Cause, Effect, Exit, Fiber, Option, Schema } from "effect";
2
- import { SqlClient } from "effect/sql";
3
- import { ActorUnavailable, SessionEnded, Unauthorized } from "../../../errors/actor.js";
4
- import { MAX_SESSION_BYTES } from "../../../runtime/connections/owner.js";
5
- import { decompress } from "../../../runtime/storage/codec.js";
6
- import { ActorTest } from "../../actor-test.js";
7
- import { Hello, Live, Room, Said, Say, Undeclared, holdNext, sessionJson, storedSession, } from "./actors.js";
8
- import { connect, eventually, frameOf, next, posts, rows, untilEnd } from "./harness.js";
9
- /** Session and frame limits, broadcast ordering, and rejected opens of connections. */
10
- export const connectionLimitConformance = [
11
- {
12
- name: "rejects a session above 16 KiB as a defect, closes the connection, and stores nothing of it",
13
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
14
- const { room, connection } = yield* connect("connections-session-limit");
15
- yield* next(connection);
16
- const fits = MAX_SESSION_BYTES - (yield* sessionJson({ name: "", frames: 1 })).length;
17
- yield* connection.send(Say.make({ text: `grow:${fits}` }));
18
- const [grown] = yield* next(connection);
19
- expect(frameOf(grown)).toEqual(Hello.make({ name: `${fits}`, resumed: false, frames: 1 }));
20
- const [stored] = yield* rows(room.ref);
21
- expect(stored?.frame_seq).toBe("1");
22
- const session = (yield* storedSession(decompress(stored.session))).value;
23
- const json = yield* sessionJson(session);
24
- expect(new TextEncoder().encode(json).byteLength).toBe(MAX_SESSION_BYTES);
25
- yield* connection.send(Say.make({ text: `grow:${fits + 1}` }));
26
- const { seen, ended } = yield* untilEnd(connection);
27
- expect(seen).toEqual([]);
28
- expect(Schema.is(SessionEnded)(ended)).toBe(true);
29
- expect(ended).toMatchObject({ cause: "Defect", resync: false });
30
- expect(yield* rows(room.ref)).toEqual([]);
31
- })),
32
- },
33
- {
34
- name: "sends a broadcast committed while open runs after the open's own frames",
35
- run: ({ expect, environment, fixture }) => environment.run(Effect.gen(function* () {
36
- const test = yield* ActorTest;
37
- const room = yield* Room.get("connections-opening");
38
- const hold = yield* holdNext(fixture);
39
- const opening = yield* test
40
- .connect(room.ref, Live, { name: "held" })
41
- .pipe(Effect.forkChild({ startImmediately: true }));
42
- yield* hold.reached;
43
- yield* room.Post("during open");
44
- yield* hold.release;
45
- const connection = yield* Fiber.join(opening);
46
- const opened = yield* next(connection, 2);
47
- expect(opened.map(frameOf)).toEqual([
48
- Hello.make({ name: "held", resumed: false, frames: 0 }),
49
- Said.make({ text: "during open" }),
50
- ]);
51
- })),
52
- },
53
- {
54
- name: "a failed row delete keeps the connection open and still reached by broadcasts",
55
- run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
56
- const sql = yield* SqlClient.SqlClient;
57
- const { room, connection } = yield* connect("connections-undeletable");
58
- yield* next(connection);
59
- yield* sql `CREATE FUNCTION connections_keep_row() RETURNS trigger LANGUAGE plpgsql
60
- AS $$ BEGIN RAISE EXCEPTION 'row delete refused'; END $$`.pipe(Effect.orDie);
61
- yield* sql `CREATE TRIGGER connections_keep_row BEFORE DELETE ON actor_connections
62
- FOR EACH ROW WHEN (OLD.actor_id = 'connections-undeletable')
63
- EXECUTE FUNCTION connections_keep_row()`.pipe(Effect.orDie);
64
- yield* Effect.gen(function* () {
65
- yield* connection.send(Say.make({ text: "leave" }));
66
- yield* room.Post("still here");
67
- const [still] = yield* next(connection);
68
- expect(frameOf(still)).toEqual(Said.make({ text: "still here" }));
69
- expect((yield* rows(room.ref)).length).toBe(1);
70
- }).pipe(Effect.ensuring(Effect.all([
71
- sql `DROP TRIGGER connections_keep_row ON actor_connections`,
72
- sql `DROP FUNCTION connections_keep_row()`,
73
- ]).pipe(Effect.orDie)));
74
- })),
75
- },
76
- {
77
- name: "a connection queues at most 1,024 inbound frames and never runs the queue it dropped",
78
- run: ({ expect, environment, fixture }) => environment.run(Effect.gen(function* () {
79
- const { room, connection } = yield* connect("connections-inbound");
80
- yield* next(connection);
81
- const hold = yield* holdNext(fixture);
82
- yield* connection.send(Say.make({ text: "hold" }));
83
- yield* hold.reached;
84
- for (let index = 0; index <= 1_024; index++)
85
- yield* connection.send(Say.make({ text: "queued" })).pipe(Effect.ignore);
86
- const { ended } = yield* untilEnd(connection);
87
- expect(Schema.is(SessionEnded)(ended)).toBe(true);
88
- expect(ended).toMatchObject({ cause: "SlowConsumer", resync: true });
89
- yield* hold.release;
90
- yield* Effect.sleep("500 millis");
91
- expect(yield* posts(room.ref)).toBe(0);
92
- })),
93
- },
94
- {
95
- name: "a revoked connection drops its unread outbound frames and its queued inbound frames",
96
- run: ({ expect, environment, fixture, access }) => environment.run(Effect.gen(function* () {
97
- const { test, room, connection } = yield* connect("connections-revoked-queue");
98
- yield* next(connection);
99
- yield* room.Post("unread");
100
- const hold = yield* holdNext(fixture);
101
- yield* connection.send(Say.make({ text: "hold" }));
102
- yield* hold.reached;
103
- yield* connection.send(Say.make({ text: "queued" }));
104
- access.allowed = false;
105
- const { seen, ended } = yield* test.advance("55 seconds").pipe(Effect.andThen(eventually(Effect.map(rows(room.ref), (found) => found.length === 0), "the revoked row to go")), Effect.andThen(untilEnd(connection)), Effect.ensuring(Effect.sync(() => (access.allowed = true))));
106
- expect(ended).toMatchObject(Unauthorized.make({ code: "access_denied" }));
107
- expect(seen).toEqual([]);
108
- yield* hold.release;
109
- yield* Effect.sleep("500 millis");
110
- expect(yield* posts(room.ref)).toBe(1);
111
- })),
112
- },
113
- {
114
- name: "rejects an open for a member the actor does not declare before authorizing it",
115
- run: ({ expect, environment, access }) => environment.run(Effect.gen(function* () {
116
- const test = yield* ActorTest;
117
- const room = yield* Room.get("connections-undeclared");
118
- access.allowed = false;
119
- const exit = yield* test
120
- .connect(room.ref, Undeclared, {})
121
- .pipe(Effect.exit, Effect.ensuring(Effect.sync(() => (access.allowed = true))));
122
- const failure = Exit.isFailure(exit) ? Cause.findErrorOption(exit.cause) : Option.none();
123
- expect(Schema.is(ActorUnavailable)(Option.getOrUndefined(failure)?.reason)).toBe(true);
124
- expect(yield* rows(room.ref)).toEqual([]);
125
- })),
126
- },
127
- ];
@@ -1,4 +0,0 @@
1
- import type { ConformanceCase } from "../../conformance.ts";
2
- import { type ConnectionsFixture } from "./actors.ts";
3
- /** Reauthorization, holder liveness checks, and server-side closes of connections. */
4
- export declare const connectionReauthorizationConformance: ReadonlyArray<ConformanceCase<ConnectionsFixture>>;