@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.
- package/CHANGELOG.md +46 -3
- package/README.md +2 -2
- package/dist/actor/definition.d.ts +13 -9
- package/dist/actor/descriptor.d.ts +2 -2
- package/dist/actor/descriptor.js +4 -4
- package/dist/client/calls.js +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/errors/actor.d.ts +12 -42
- package/dist/errors/actor.js +13 -42
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/members/payload.d.ts +1 -1
- package/dist/protocol/inspection.d.ts +221 -1
- package/dist/protocol/inspection.js +151 -2
- package/dist/protocol/wire.d.ts +6 -49
- package/dist/protocol/wire.js +1 -10
- package/dist/runtime/actors.d.ts +3 -0
- package/dist/runtime/adoption/backfill.js +1 -1
- package/dist/runtime/adoption/enforce.js +2 -2
- package/dist/runtime/adoption/observe.js +1 -1
- package/dist/runtime/adoption/plan.d.ts +1 -1
- package/dist/runtime/adoption/startup.js +1 -1
- package/dist/runtime/adoption/status.d.ts +1 -1
- package/dist/runtime/app.d.ts +65 -0
- package/dist/runtime/app.js +38 -0
- package/dist/runtime/connections/holder.d.ts +11 -1
- package/dist/runtime/connections/holder.js +8 -4
- package/dist/runtime/connections/streams.d.ts +4 -4
- package/dist/runtime/content/store.js +15 -11
- package/dist/runtime/database/bounded.d.ts +4 -18
- package/dist/runtime/database/bounded.js +36 -22
- package/dist/runtime/database/coordination.d.ts +12 -3
- package/dist/runtime/database/coordination.js +29 -36
- package/dist/runtime/database/migrations.d.ts +61 -29
- package/dist/runtime/database/migrations.js +375 -139
- package/dist/runtime/database/replica.d.ts +4 -1
- package/dist/runtime/database/replica.js +4 -1
- package/dist/runtime/database/schema.d.ts +13 -0
- package/dist/runtime/database/schema.js +13 -0
- package/dist/runtime/database/shards.d.ts +31 -13
- package/dist/runtime/database/shards.js +38 -26
- package/dist/runtime/entity/register.d.ts +8 -1
- package/dist/runtime/entity/register.js +58 -7
- package/dist/runtime/fleet/checks.js +4 -4
- package/dist/runtime/fleet/maintainer.d.ts +1 -1
- package/dist/runtime/fleet/maintainer.js +1 -1
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/inspector/layer.d.ts +20 -0
- package/dist/runtime/inspector/layer.js +224 -5
- package/dist/runtime/inspector/queries.d.ts +54 -8
- package/dist/runtime/inspector/queries.js +84 -15
- package/dist/runtime/layer.d.ts +48 -39
- package/dist/runtime/layer.js +124 -56
- package/dist/runtime/operators/audit.d.ts +3 -0
- package/dist/runtime/operators/audit.js +6 -2
- package/dist/runtime/operators/repair.js +30 -10
- package/dist/runtime/operators/seed.js +4 -2
- package/dist/runtime/payloads/versions.d.ts +4 -4
- package/dist/runtime/payloads/versions.js +24 -15
- package/dist/runtime/queries.js +4 -2
- package/dist/runtime/registration.d.ts +5 -2
- package/dist/runtime/registration.js +3 -3
- package/dist/runtime/runner.d.ts +12 -0
- package/dist/runtime/runner.js +12 -0
- package/dist/runtime/storage/codec.d.ts +19 -7
- package/dist/runtime/storage/codec.js +23 -7
- package/dist/runtime/storage/placements.d.ts +6 -1
- package/dist/runtime/storage/placements.js +98 -6
- package/dist/runtime/storage/retention.d.ts +6 -0
- package/dist/runtime/storage/retention.js +25 -13
- package/dist/runtime/subscriptions/relay.js +47 -44
- package/dist/runtime/subscriptions/storage.d.ts +3 -2
- package/dist/runtime/subscriptions/storage.js +9 -6
- package/dist/runtime/telemetry/live.d.ts +255 -0
- package/dist/runtime/telemetry/live.js +540 -0
- package/dist/runtime/telemetry/metrics.d.ts +2 -0
- package/dist/runtime/telemetry/metrics.js +4 -1
- package/dist/runtime/telemetry/sampler.js +33 -13
- package/dist/runtime/telemetry/spans.d.ts +1 -1
- package/dist/runtime/telemetry/spans.js +1 -1
- package/dist/runtime/topology/locks.d.ts +6 -3
- package/dist/runtime/topology/locks.js +27 -4
- package/dist/runtime/turn/execute.d.ts +2 -0
- package/dist/runtime/turn/execute.js +5 -2
- package/dist/runtime/turn/pipeline.d.ts +5 -12
- package/dist/runtime/turn/pipeline.js +11 -11
- package/dist/runtime/turn/relay.d.ts +13 -3
- package/dist/runtime/turn/relay.js +27 -14
- package/dist/runtime/turn/rows.js +4 -4
- package/dist/runtime/workflows/compatibility.d.ts +1 -1
- package/dist/runtime/workflows/compatibility.js +85 -31
- package/dist/runtime/workflows/engine.js +20 -17
- package/dist/serve/layer.js +3 -2
- package/dist/tables/owned.d.ts +1 -1
- package/dist/tables/owned.js +1 -1
- package/dist/testing/actor-test.d.ts +2 -24
- package/dist/testing/actor-test.js +1 -27
- package/dist/testing/index.d.ts +0 -12
- package/dist/testing/index.js +0 -6
- package/package.json +1 -1
- package/dist/runtime/database/neki/migrations.d.ts +0 -31
- package/dist/runtime/database/neki/migrations.js +0 -276
- package/dist/runtime/database/neki/session.d.ts +0 -23
- package/dist/runtime/database/neki/session.js +0 -31
- package/dist/testing/cluster.d.ts +0 -93
- package/dist/testing/cluster.js +0 -296
- package/dist/testing/conformance/access.d.ts +0 -5
- package/dist/testing/conformance/access.js +0 -531
- package/dist/testing/conformance/admission.d.ts +0 -11
- package/dist/testing/conformance/admission.js +0 -182
- package/dist/testing/conformance/adoption.d.ts +0 -2
- package/dist/testing/conformance/adoption.js +0 -1019
- package/dist/testing/conformance/assertions.d.ts +0 -202
- package/dist/testing/conformance/assertions.js +0 -849
- package/dist/testing/conformance/batches.d.ts +0 -41
- package/dist/testing/conformance/batches.js +0 -388
- package/dist/testing/conformance/blobs.d.ts +0 -14
- package/dist/testing/conformance/blobs.js +0 -405
- package/dist/testing/conformance/capacity.d.ts +0 -8
- package/dist/testing/conformance/capacity.js +0 -433
- package/dist/testing/conformance/client.d.ts +0 -5
- package/dist/testing/conformance/client.js +0 -719
- package/dist/testing/conformance/cold-serve.d.ts +0 -16
- package/dist/testing/conformance/cold-serve.js +0 -254
- package/dist/testing/conformance/connections/actors.d.ts +0 -1143
- package/dist/testing/conformance/connections/actors.js +0 -180
- package/dist/testing/conformance/connections/harness.d.ts +0 -1754
- package/dist/testing/conformance/connections/harness.js +0 -142
- package/dist/testing/conformance/connections/holders.d.ts +0 -4
- package/dist/testing/conformance/connections/holders.js +0 -165
- package/dist/testing/conformance/connections/lifecycle.d.ts +0 -4
- package/dist/testing/conformance/connections/lifecycle.js +0 -113
- package/dist/testing/conformance/connections/limits.d.ts +0 -4
- package/dist/testing/conformance/connections/limits.js +0 -127
- package/dist/testing/conformance/connections/reauthorization.d.ts +0 -4
- package/dist/testing/conformance/connections/reauthorization.js +0 -170
- package/dist/testing/conformance/connections/resync.d.ts +0 -4
- package/dist/testing/conformance/connections/resync.js +0 -360
- package/dist/testing/conformance/connections/takeover.d.ts +0 -4
- package/dist/testing/conformance/connections/takeover.js +0 -287
- package/dist/testing/conformance/connections.d.ts +0 -4
- package/dist/testing/conformance/connections.js +0 -15
- package/dist/testing/conformance/content-blobs.d.ts +0 -16
- package/dist/testing/conformance/content-blobs.js +0 -534
- package/dist/testing/conformance/counter.d.ts +0 -16
- package/dist/testing/conformance/counter.js +0 -813
- package/dist/testing/conformance/cron.d.ts +0 -5
- package/dist/testing/conformance/cron.js +0 -665
- package/dist/testing/conformance/drain.d.ts +0 -26
- package/dist/testing/conformance/drain.js +0 -540
- package/dist/testing/conformance/effect-control.d.ts +0 -38
- package/dist/testing/conformance/effect-control.js +0 -1059
- package/dist/testing/conformance/effects.d.ts +0 -23
- package/dist/testing/conformance/effects.js +0 -432
- package/dist/testing/conformance/events.d.ts +0 -28
- package/dist/testing/conformance/events.js +0 -335
- package/dist/testing/conformance/export.d.ts +0 -2
- package/dist/testing/conformance/export.js +0 -368
- package/dist/testing/conformance/fleet.d.ts +0 -444
- package/dist/testing/conformance/fleet.js +0 -1009
- package/dist/testing/conformance/groups.d.ts +0 -7
- package/dist/testing/conformance/groups.js +0 -353
- package/dist/testing/conformance/heap.d.ts +0 -3
- package/dist/testing/conformance/heap.js +0 -149
- package/dist/testing/conformance/http.d.ts +0 -2809
- package/dist/testing/conformance/http.js +0 -1234
- package/dist/testing/conformance/inspection-views.d.ts +0 -8
- package/dist/testing/conformance/inspection-views.js +0 -272
- package/dist/testing/conformance/inspector.d.ts +0 -7
- package/dist/testing/conformance/inspector.js +0 -650
- package/dist/testing/conformance/mint.d.ts +0 -58
- package/dist/testing/conformance/mint.js +0 -692
- package/dist/testing/conformance/multi-runner.d.ts +0 -8
- package/dist/testing/conformance/multi-runner.js +0 -403
- package/dist/testing/conformance/neki/plan.d.ts +0 -19
- package/dist/testing/conformance/neki/plan.js +0 -30
- package/dist/testing/conformance/observability.d.ts +0 -3
- package/dist/testing/conformance/observability.js +0 -343
- package/dist/testing/conformance/offline.d.ts +0 -4
- package/dist/testing/conformance/offline.js +0 -474
- package/dist/testing/conformance/operator-harness.d.ts +0 -57
- package/dist/testing/conformance/operator-harness.js +0 -78
- package/dist/testing/conformance/operator.d.ts +0 -3
- package/dist/testing/conformance/operator.js +0 -379
- package/dist/testing/conformance/outbox-cross-shard.d.ts +0 -14
- package/dist/testing/conformance/outbox-cross-shard.js +0 -160
- package/dist/testing/conformance/outbox.d.ts +0 -36
- package/dist/testing/conformance/outbox.js +0 -437
- package/dist/testing/conformance/payload-migrations.d.ts +0 -3
- package/dist/testing/conformance/payload-migrations.js +0 -612
- package/dist/testing/conformance/pipeline.d.ts +0 -3
- package/dist/testing/conformance/pipeline.js +0 -1040
- package/dist/testing/conformance/placement.d.ts +0 -22
- package/dist/testing/conformance/placement.js +0 -539
- package/dist/testing/conformance/platform.d.ts +0 -40
- package/dist/testing/conformance/platform.js +0 -101
- package/dist/testing/conformance/progress.d.ts +0 -39
- package/dist/testing/conformance/progress.js +0 -666
- package/dist/testing/conformance/properties.d.ts +0 -6
- package/dist/testing/conformance/properties.js +0 -295
- package/dist/testing/conformance/protocols.d.ts +0 -4
- package/dist/testing/conformance/protocols.js +0 -618
- package/dist/testing/conformance/provider/advertise.d.ts +0 -21
- package/dist/testing/conformance/provider/advertise.js +0 -44
- package/dist/testing/conformance/read-your-writes.d.ts +0 -20
- package/dist/testing/conformance/read-your-writes.js +0 -192
- package/dist/testing/conformance/reducers.d.ts +0 -8
- package/dist/testing/conformance/reducers.js +0 -258
- package/dist/testing/conformance/relay.d.ts +0 -42
- package/dist/testing/conformance/relay.js +0 -1115
- package/dist/testing/conformance/restore.d.ts +0 -43
- package/dist/testing/conformance/restore.js +0 -341
- package/dist/testing/conformance/retention.d.ts +0 -14
- package/dist/testing/conformance/retention.js +0 -463
- package/dist/testing/conformance/rls.d.ts +0 -24
- package/dist/testing/conformance/rls.js +0 -576
- package/dist/testing/conformance/simulation.d.ts +0 -31
- package/dist/testing/conformance/simulation.js +0 -167
- package/dist/testing/conformance/single-shard.d.ts +0 -2
- package/dist/testing/conformance/single-shard.js +0 -125
- package/dist/testing/conformance/singleton.d.ts +0 -3
- package/dist/testing/conformance/singleton.js +0 -293
- package/dist/testing/conformance/statements.d.ts +0 -60
- package/dist/testing/conformance/statements.js +0 -108
- package/dist/testing/conformance/streams.d.ts +0 -7
- package/dist/testing/conformance/streams.js +0 -280
- package/dist/testing/conformance/subscriptions/access.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/access.js +0 -98
- package/dist/testing/conformance/subscriptions/actors.d.ts +0 -4910
- package/dist/testing/conformance/subscriptions/actors.js +0 -372
- package/dist/testing/conformance/subscriptions/cluster.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/cluster.js +0 -89
- package/dist/testing/conformance/subscriptions/delivery.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/delivery.js +0 -253
- package/dist/testing/conformance/subscriptions/deploys.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/deploys.js +0 -162
- package/dist/testing/conformance/subscriptions/epochs.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/epochs.js +0 -169
- package/dist/testing/conformance/subscriptions/failures.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/failures.js +0 -108
- package/dist/testing/conformance/subscriptions/feeds.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/feeds.js +0 -218
- package/dist/testing/conformance/subscriptions/harness.d.ts +0 -124
- package/dist/testing/conformance/subscriptions/harness.js +0 -143
- package/dist/testing/conformance/subscriptions/operator.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/operator.js +0 -128
- package/dist/testing/conformance/subscriptions/retention.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/retention.js +0 -288
- package/dist/testing/conformance/subscriptions/routing.d.ts +0 -4
- package/dist/testing/conformance/subscriptions/routing.js +0 -179
- package/dist/testing/conformance/subscriptions.d.ts +0 -4
- package/dist/testing/conformance/subscriptions.js +0 -19
- package/dist/testing/conformance/tables.d.ts +0 -125
- package/dist/testing/conformance/tables.js +0 -664
- package/dist/testing/conformance/transports/actors.d.ts +0 -1907
- package/dist/testing/conformance/transports/actors.js +0 -163
- package/dist/testing/conformance/transports/client.d.ts +0 -3
- package/dist/testing/conformance/transports/client.js +0 -237
- package/dist/testing/conformance/transports/feeds.d.ts +0 -3
- package/dist/testing/conformance/transports/feeds.js +0 -232
- package/dist/testing/conformance/transports/harness.d.ts +0 -93
- package/dist/testing/conformance/transports/harness.js +0 -84
- package/dist/testing/conformance/transports/streams.d.ts +0 -3
- package/dist/testing/conformance/transports/streams.js +0 -96
- package/dist/testing/conformance/transports/websocket.d.ts +0 -3
- package/dist/testing/conformance/transports/websocket.js +0 -384
- package/dist/testing/conformance/transports/wire.d.ts +0 -141
- package/dist/testing/conformance/transports/wire.js +0 -204
- package/dist/testing/conformance/transports.d.ts +0 -3
- package/dist/testing/conformance/transports.js +0 -11
- package/dist/testing/conformance/watch.d.ts +0 -59
- package/dist/testing/conformance/watch.js +0 -879
- package/dist/testing/conformance/workflow-engine.d.ts +0 -97
- package/dist/testing/conformance/workflow-engine.js +0 -253
- package/dist/testing/conformance/workflow-versions.d.ts +0 -3
- package/dist/testing/conformance/workflow-versions.js +0 -545
- package/dist/testing/conformance/workflows/access.d.ts +0 -4
- package/dist/testing/conformance/workflows/access.js +0 -147
- package/dist/testing/conformance/workflows/actors.d.ts +0 -2317
- package/dist/testing/conformance/workflows/actors.js +0 -305
- package/dist/testing/conformance/workflows/basics.d.ts +0 -4
- package/dist/testing/conformance/workflows/basics.js +0 -144
- package/dist/testing/conformance/workflows/engine.d.ts +0 -4
- package/dist/testing/conformance/workflows/engine.js +0 -80
- package/dist/testing/conformance/workflows/guards.d.ts +0 -4
- package/dist/testing/conformance/workflows/guards.js +0 -172
- package/dist/testing/conformance/workflows/harness.d.ts +0 -14
- package/dist/testing/conformance/workflows/harness.js +0 -44
- package/dist/testing/conformance/workflows/recovery.d.ts +0 -4
- package/dist/testing/conformance/workflows/recovery.js +0 -181
- package/dist/testing/conformance/workflows/starts.d.ts +0 -4
- package/dist/testing/conformance/workflows/starts.js +0 -57
- package/dist/testing/conformance/workflows/suspension.d.ts +0 -4
- package/dist/testing/conformance/workflows/suspension.js +0 -106
- package/dist/testing/conformance/workflows/waits.d.ts +0 -4
- package/dist/testing/conformance/workflows/waits.js +0 -284
- package/dist/testing/conformance/workflows.d.ts +0 -4
- package/dist/testing/conformance/workflows.js +0 -19
- package/dist/testing/conformance.d.ts +0 -330
- package/dist/testing/conformance.js +0 -364
- package/dist/testing/foundation.d.ts +0 -41
- package/dist/testing/foundation.js +0 -517
- package/dist/testing/simulate-cluster.d.ts +0 -112
- package/dist/testing/simulate-cluster.js +0 -257
- package/dist/testing/simulate.d.ts +0 -90
- 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>>;
|