@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,8 +0,0 @@
|
|
|
1
|
-
import { Schema } from "effect";
|
|
2
|
-
import type { ConformanceCase } from "../conformance.ts";
|
|
3
|
-
/** A commutative reducer: calls already waiting on the owner merge into one turn. */
|
|
4
|
-
export declare const Tick: import("../../index.ts").Reducer<"Tick", {
|
|
5
|
-
readonly count: Schema.withDecodingDefault<Schema.Finite, never>;
|
|
6
|
-
}, Schema.Finite, Schema.Void, Schema.Never>;
|
|
7
|
-
/** Multi-runner cases on three runners: placement on exactly one runner, merging of commutative calls into one turn, and retry on the next owner after a kill. */
|
|
8
|
-
export declare const multiRunnerConformance: ReadonlyArray<ConformanceCase>;
|
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
import { Effect, Fiber, Layer, Result, Schedule, Schema } from "effect";
|
|
2
|
-
import { SqlClient } from "effect/sql";
|
|
3
|
-
import { Actor, Actors, System, User } from "../../index.js";
|
|
4
|
-
import { InternalActors } from "../../runtime/actors.js";
|
|
5
|
-
import { ActorTest } from "../actor-test.js";
|
|
6
|
-
import { ActorCluster } from "../cluster.js";
|
|
7
|
-
const Tallied = Actor.event("Tallied", { amount: Schema.Finite });
|
|
8
|
-
const Add = Actor.command("Add", { payload: Schema.Finite, success: Schema.Finite });
|
|
9
|
-
const Whoami = Actor.command("Whoami", { success: Schema.String });
|
|
10
|
-
const Log = Actor.query("Log", {
|
|
11
|
-
success: Schema.Array(Schema.Struct({ cursor: Schema.String, commandId: Schema.String })),
|
|
12
|
-
});
|
|
13
|
-
const TallyState = Actor.state({
|
|
14
|
-
count: Schema.Finite.pipe(Schema.withDecodingDefault(Effect.succeed(0))),
|
|
15
|
-
});
|
|
16
|
-
const tickReductions = { count: 0 };
|
|
17
|
-
/** Runs of the `Add` handler, committed or not, across every runner. */
|
|
18
|
-
const addRuns = { count: 0 };
|
|
19
|
-
/** A commutative reducer: calls already waiting on the owner merge into one turn. */
|
|
20
|
-
export const Tick = Actor.reducer("Tick", {
|
|
21
|
-
state: TallyState,
|
|
22
|
-
payload: Schema.Finite,
|
|
23
|
-
reduce: (state, amount) => {
|
|
24
|
-
tickReductions.count += 1;
|
|
25
|
-
return Result.succeed({ count: state.count + amount });
|
|
26
|
-
},
|
|
27
|
-
batch: { combine: (first, second) => first + second },
|
|
28
|
-
});
|
|
29
|
-
const Tally = Actor.make("Tally", {
|
|
30
|
-
key: Schema.String,
|
|
31
|
-
state: TallyState,
|
|
32
|
-
events: [Tallied],
|
|
33
|
-
api: { Add, Whoami, Log, Tick },
|
|
34
|
-
});
|
|
35
|
-
const TallyLive = Layer.mergeAll(Tally.toLayer(Effect.succeed({
|
|
36
|
-
Add: Effect.fnUntraced(function* (amount) {
|
|
37
|
-
addRuns.count += 1;
|
|
38
|
-
const turn = yield* Tally.Turn;
|
|
39
|
-
yield* turn.state.set({ count: turn.state.count + amount });
|
|
40
|
-
yield* turn.emit(Tallied.make({ amount }));
|
|
41
|
-
return turn.state.count;
|
|
42
|
-
}),
|
|
43
|
-
Whoami: Effect.fnUntraced(function* () {
|
|
44
|
-
const turn = yield* Tally.Turn;
|
|
45
|
-
return Schema.is(User)(turn.caller) ? turn.caller.subject : turn.caller._tag;
|
|
46
|
-
}),
|
|
47
|
-
})), Tally.toQueryLayer(Effect.succeed({
|
|
48
|
-
Log: Effect.fnUntraced(function* () {
|
|
49
|
-
const entries = yield* (yield* Tally.Read).events(Tallied);
|
|
50
|
-
return entries.map(({ cursor, commandId }) => ({ cursor, commandId }));
|
|
51
|
-
}, Effect.orDie),
|
|
52
|
-
})));
|
|
53
|
-
const Dispatch = Actor.command("Dispatch", { payload: Schema.String });
|
|
54
|
-
const Hear = Actor.command("Hear");
|
|
55
|
-
const Heard = Actor.query("Heard", { success: Schema.String });
|
|
56
|
-
/** Hears the principal a relay-delivered intent carries: the sender's caller as `onBehalfOf`. */
|
|
57
|
-
const Envoy = Actor.make("Envoy", {
|
|
58
|
-
key: Schema.String,
|
|
59
|
-
state: Actor.state({
|
|
60
|
-
heard: Schema.String.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
|
|
61
|
-
}),
|
|
62
|
-
api: { Dispatch, Heard },
|
|
63
|
-
internal: { Hear },
|
|
64
|
-
});
|
|
65
|
-
const EnvoyLive = Layer.mergeAll(Envoy.toLayer(Effect.succeed({
|
|
66
|
-
Dispatch: Effect.fnUntraced(function* (target) {
|
|
67
|
-
yield* Envoy.Turn;
|
|
68
|
-
yield* (yield* Envoy.intents(target)).Hear();
|
|
69
|
-
}),
|
|
70
|
-
Hear: Effect.fnUntraced(function* () {
|
|
71
|
-
const turn = yield* Envoy.Turn;
|
|
72
|
-
yield* turn.state.set({
|
|
73
|
-
heard: Schema.is(System)(turn.caller) ? (turn.caller.onBehalfOf?.subject ?? "") : "",
|
|
74
|
-
});
|
|
75
|
-
}),
|
|
76
|
-
})), Envoy.toQueryLayer(Effect.succeed({
|
|
77
|
-
Heard: Effect.fnUntraced(function* () {
|
|
78
|
-
return (yield* Envoy.Read).state.heard;
|
|
79
|
-
}),
|
|
80
|
-
})));
|
|
81
|
-
const EXPIRATION_SECONDS = 3;
|
|
82
|
-
/** Builds a fresh database and a cluster of `runners` on it for one case. */
|
|
83
|
-
const withCluster = (environment, runners, body) => environment.run(Effect.gen(function* () {
|
|
84
|
-
const database = yield* environment.freshDatabase;
|
|
85
|
-
const context = yield* Layer.build(ActorTest.cluster({
|
|
86
|
-
database,
|
|
87
|
-
runners,
|
|
88
|
-
shardLockExpiration: `${EXPIRATION_SECONDS} seconds`,
|
|
89
|
-
actors: Layer.merge(TallyLive, EnvoyLive),
|
|
90
|
-
as: User.make({ subject: "alice" }),
|
|
91
|
-
authorize: () => Effect.succeed(true),
|
|
92
|
-
}));
|
|
93
|
-
return yield* body.pipe(Effect.provideContext(context));
|
|
94
|
-
}));
|
|
95
|
-
const add = (runner, id, amount) => Effect.gen(function* () {
|
|
96
|
-
const cluster = yield* ActorCluster;
|
|
97
|
-
return yield* cluster.on(runner)(Tally.get(id).pipe(Effect.flatMap((tally) => tally.Add(amount))));
|
|
98
|
-
});
|
|
99
|
-
const refOf = (id) => Effect.gen(function* () {
|
|
100
|
-
const cluster = yield* ActorCluster;
|
|
101
|
-
return (yield* cluster.on(0)(Tally.get(id))).ref;
|
|
102
|
-
});
|
|
103
|
-
const inspect = (runner, ref) => Effect.gen(function* () {
|
|
104
|
-
const cluster = yield* ActorCluster;
|
|
105
|
-
return yield* cluster.on(runner)(ActorTest.use((test) => test.inspect(ref)));
|
|
106
|
-
});
|
|
107
|
-
/** Polls until `ref`'s shard lock is held by a runner that `accept`s it. */
|
|
108
|
-
const awaitOwner = (ref, accept) => Effect.gen(function* () {
|
|
109
|
-
const cluster = yield* ActorCluster;
|
|
110
|
-
return yield* cluster.owner(ref).pipe(Effect.repeat({ schedule: Schedule.spaced("25 millis"), until: accept }), Effect.timeoutOrElse({
|
|
111
|
-
duration: "30 seconds",
|
|
112
|
-
orElse: () => Effect.die(new Error("The actor's shard did not move")),
|
|
113
|
-
}));
|
|
114
|
-
});
|
|
115
|
-
/** A shard lock row with database-clock times in epoch milliseconds. */
|
|
116
|
-
const lockOf = (runner, ref) => Effect.gen(function* () {
|
|
117
|
-
const cluster = yield* ActorCluster;
|
|
118
|
-
return yield* cluster.on(runner)(Effect.gen(function* () {
|
|
119
|
-
const sql = yield* SqlClient.SqlClient;
|
|
120
|
-
const shard = yield* (yield* InternalActors).shardId(ref);
|
|
121
|
-
const [row] = yield* sql `
|
|
122
|
-
SELECT address,
|
|
123
|
-
(EXTRACT(EPOCH FROM acquired_at) * 1000)::float8 AS acquired,
|
|
124
|
-
(EXTRACT(EPOCH FROM LOCALTIMESTAMP) * 1000)::float8 AS now
|
|
125
|
-
FROM cluster_locks WHERE shard_id = ${shard}`.pipe(Effect.orDie);
|
|
126
|
-
return row;
|
|
127
|
-
}));
|
|
128
|
-
});
|
|
129
|
-
/**
|
|
130
|
-
* Kills an actor's owner while a command from another runner is paused at
|
|
131
|
-
* `point` on it, then checks that no survivor took the shard before the dead
|
|
132
|
-
* runner's lock expired and that the caller's in-flight command, under its
|
|
133
|
-
* original id, committed exactly once.
|
|
134
|
-
*/
|
|
135
|
-
const killDuringTurn = (expect, point) => Effect.gen(function* () {
|
|
136
|
-
const cluster = yield* ActorCluster;
|
|
137
|
-
const id = `killed-${point}`;
|
|
138
|
-
const ref = yield* refOf(id);
|
|
139
|
-
const owner = (yield* cluster.owner(ref));
|
|
140
|
-
const caller = (owner + 1) % 3;
|
|
141
|
-
expect(yield* add(caller, id, 1)).toBe(1);
|
|
142
|
-
const commandId = yield* cluster.on(caller)(Effect.gen(function* () {
|
|
143
|
-
return yield* (yield* Actors).mintCommandId;
|
|
144
|
-
}));
|
|
145
|
-
const pause = yield* cluster.on(owner)(ActorTest.use((test) => test.pauseNext(point)));
|
|
146
|
-
const retried = yield* cluster
|
|
147
|
-
.on(caller)(Tally.get(id).pipe(Effect.flatMap((tally) => tally.Add(2).pipe(Actor.commandId(commandId)))))
|
|
148
|
-
.pipe(Effect.forkChild);
|
|
149
|
-
yield* pause.reached;
|
|
150
|
-
const killed = (yield* lockOf(caller, ref)).now;
|
|
151
|
-
yield* cluster.kill(owner);
|
|
152
|
-
const held = yield* lockOf(caller, ref);
|
|
153
|
-
yield* pause.release;
|
|
154
|
-
expect(yield* inspect(caller, ref)).toMatchObject(point === "beforeCommit"
|
|
155
|
-
? { state: { count: 1 }, receipts: 1, events: 1 }
|
|
156
|
-
: { state: { count: 3 }, receipts: 2, events: 2 });
|
|
157
|
-
const taken = yield* lockOf(caller, ref).pipe(Effect.repeat({
|
|
158
|
-
schedule: Schedule.spaced("10 millis"),
|
|
159
|
-
until: (lock) => lock.address !== held.address,
|
|
160
|
-
}), Effect.timeoutOrElse({
|
|
161
|
-
duration: "30 seconds",
|
|
162
|
-
orElse: () => Effect.die(new Error("No survivor took the dead runner's shard")),
|
|
163
|
-
}));
|
|
164
|
-
const next = (yield* cluster.owner(ref));
|
|
165
|
-
expect(next === owner).toBe(false);
|
|
166
|
-
expect(taken.acquired - held.acquired >= EXPIRATION_SECONDS * 1000).toBe(true);
|
|
167
|
-
expect(yield* Fiber.join(retried)).toBe(3);
|
|
168
|
-
const resumed = (yield* lockOf(next, ref)).now;
|
|
169
|
-
expect(resumed >= taken.acquired && taken.acquired >= killed).toBe(true);
|
|
170
|
-
expect(yield* inspect(next, ref)).toMatchObject({
|
|
171
|
-
state: { count: 3 },
|
|
172
|
-
receipts: 2,
|
|
173
|
-
events: 2,
|
|
174
|
-
});
|
|
175
|
-
const log = yield* cluster.on(next)(Tally.get(id).pipe(Effect.flatMap((tally) => tally.Log())));
|
|
176
|
-
expect(log.map((entry) => entry.commandId).filter((entry) => entry === commandId).length).toBe(1);
|
|
177
|
-
});
|
|
178
|
-
/** Multi-runner cases on three runners: placement on exactly one runner, merging of commutative calls into one turn, and retry on the next owner after a kill. */
|
|
179
|
-
export const multiRunnerConformance = [
|
|
180
|
-
{
|
|
181
|
-
name: "merges commutative calls from three runners on the owner into one turn with one receipt per command id",
|
|
182
|
-
requiresIndependentConnections: true,
|
|
183
|
-
timeoutMs: 60_000,
|
|
184
|
-
run: ({ expect, environment }) => withCluster(environment, 3, Effect.gen(function* () {
|
|
185
|
-
const cluster = yield* ActorCluster;
|
|
186
|
-
const id = "merged-ticks";
|
|
187
|
-
const ref = yield* refOf(id);
|
|
188
|
-
expect(yield* add(0, id, 1)).toBe(1);
|
|
189
|
-
const owner = (yield* cluster.owner(ref));
|
|
190
|
-
const onOwner = cluster.on(owner);
|
|
191
|
-
const held = yield* onOwner(ActorTest.use((test) => test.pauseNext("beforeCommit")));
|
|
192
|
-
const first = yield* add((owner + 1) % 3, id, 1).pipe(Effect.forkChild);
|
|
193
|
-
yield* held.reached;
|
|
194
|
-
const calls = [];
|
|
195
|
-
const ticks = [];
|
|
196
|
-
for (let runner = 0; runner < 3; runner++)
|
|
197
|
-
for (let call = 0; call < 4; call++) {
|
|
198
|
-
const commandId = yield* cluster.on(runner)(Effect.gen(function* () {
|
|
199
|
-
return yield* (yield* Actors).mintCommandId;
|
|
200
|
-
}));
|
|
201
|
-
const queued = yield* onOwner(ActorTest.use((test) => test.pauseNext("queued")));
|
|
202
|
-
ticks.push(yield* cluster
|
|
203
|
-
.on(runner)(Tally.get(id).pipe(Effect.flatMap((tally) => tally.Tick(1).pipe(Actor.commandId(commandId)))))
|
|
204
|
-
.pipe(Effect.forkChild));
|
|
205
|
-
yield* queued.reached;
|
|
206
|
-
yield* queued.release;
|
|
207
|
-
yield* Effect.yieldNow;
|
|
208
|
-
calls.push({ runner, commandId });
|
|
209
|
-
}
|
|
210
|
-
const before = tickReductions.count;
|
|
211
|
-
yield* held.release;
|
|
212
|
-
expect(yield* Fiber.join(first)).toBe(2);
|
|
213
|
-
expect(yield* Effect.forEach(ticks, Fiber.join)).toEqual(calls.map(() => undefined));
|
|
214
|
-
expect(tickReductions.count - before).toBe(1);
|
|
215
|
-
const receipts = yield* cluster.on(owner)(Effect.gen(function* () {
|
|
216
|
-
const sql = yield* SqlClient.SqlClient;
|
|
217
|
-
return yield* sql `
|
|
218
|
-
SELECT command_id, xmin::text AS tx FROM actor_receipts
|
|
219
|
-
WHERE tenant_id = ${ref.tenant} AND actor_type = 'Tally' AND actor_id = ${ref.id}
|
|
220
|
-
AND command = 'Tick'`;
|
|
221
|
-
}).pipe(Effect.orDie));
|
|
222
|
-
expect(receipts.map((row) => row.command_id).sort()).toEqual(calls.map((call) => call.commandId).sort());
|
|
223
|
-
expect(new Set(receipts.map((row) => row.tx)).size).toBe(1);
|
|
224
|
-
expect(yield* inspect((owner + 2) % 3, ref)).toMatchObject({
|
|
225
|
-
state: { count: 14 },
|
|
226
|
-
receipts: 14,
|
|
227
|
-
});
|
|
228
|
-
const retried = calls[5];
|
|
229
|
-
expect(yield* cluster.on((retried.runner + 1) % 3)(Tally.get(id).pipe(Effect.flatMap((tally) => tally.Tick(1).pipe(Actor.commandId(retried.commandId)))))).toBe(undefined);
|
|
230
|
-
expect(tickReductions.count - before).toBe(1);
|
|
231
|
-
})),
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
name: "places each actor on exactly one of three runners, reachable through every runner",
|
|
235
|
-
requiresIndependentConnections: true,
|
|
236
|
-
timeoutMs: 60_000,
|
|
237
|
-
run: ({ expect, environment }) => withCluster(environment, 3, Effect.gen(function* () {
|
|
238
|
-
const cluster = yield* ActorCluster;
|
|
239
|
-
const ids = Array.from({ length: 24 }, (_, index) => `spread-${index}`);
|
|
240
|
-
const owners = new Set();
|
|
241
|
-
for (const id of ids) {
|
|
242
|
-
yield* add(0, id, 1);
|
|
243
|
-
const activated = (yield* inspect(0, yield* refOf(id))).generation;
|
|
244
|
-
for (let runner = 1; runner < 3; runner++)
|
|
245
|
-
yield* add(runner, id, 1);
|
|
246
|
-
expect((yield* inspect(0, yield* refOf(id))).generation).toBe(activated);
|
|
247
|
-
const owner = yield* cluster.owner(yield* refOf(id));
|
|
248
|
-
expect(owner === undefined).toBe(false);
|
|
249
|
-
owners.add(owner);
|
|
250
|
-
}
|
|
251
|
-
expect(owners.size > 1).toBe(true);
|
|
252
|
-
for (const id of ids)
|
|
253
|
-
expect(yield* inspect(1, yield* refOf(id))).toMatchObject({
|
|
254
|
-
state: { count: 3 },
|
|
255
|
-
receipts: 3,
|
|
256
|
-
events: 3,
|
|
257
|
-
});
|
|
258
|
-
})),
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: "rolls back a killed runner's paused turn and retries it once on the next owner after lock expiry",
|
|
262
|
-
requiresIndependentConnections: true,
|
|
263
|
-
timeoutMs: 60_000,
|
|
264
|
-
run: ({ expect, environment }) => withCluster(environment, 3, killDuringTurn(expect, "beforeCommit")),
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
name: "replays a killed runner's committed turn on the next owner instead of running it again",
|
|
268
|
-
requiresIndependentConnections: true,
|
|
269
|
-
timeoutMs: 60_000,
|
|
270
|
-
run: ({ expect, environment }) => withCluster(environment, 3, killDuringTurn(expect, "afterCommit")),
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
name: "fails a stale activation's fence once another runner commits for the actor, then reloads",
|
|
274
|
-
requiresIndependentConnections: true,
|
|
275
|
-
timeoutMs: 60_000,
|
|
276
|
-
run: ({ expect, environment }) => withCluster(environment, 2, Effect.gen(function* () {
|
|
277
|
-
const cluster = yield* ActorCluster;
|
|
278
|
-
const ref = yield* refOf("stale");
|
|
279
|
-
const stale = (yield* cluster.owner(ref));
|
|
280
|
-
const rival = 1 - stale;
|
|
281
|
-
expect(yield* add(stale, "stale", 1)).toBe(1);
|
|
282
|
-
const first = Number((yield* inspect(rival, ref)).generation);
|
|
283
|
-
const heartbeat = yield* cluster.pauseHeartbeat(stale);
|
|
284
|
-
yield* awaitOwner(ref, (current) => current === rival);
|
|
285
|
-
expect(yield* add(rival, "stale", 10)).toBe(11);
|
|
286
|
-
expect(yield* add(stale, "stale", 100)).toBe(111);
|
|
287
|
-
yield* heartbeat.resume;
|
|
288
|
-
const inspection = yield* inspect(rival, ref);
|
|
289
|
-
expect(inspection).toMatchObject({ state: { count: 111 }, receipts: 3, events: 3 });
|
|
290
|
-
expect(Number(inspection.generation) >= first + 2).toBe(true);
|
|
291
|
-
})),
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
name: "runs a brand-new actor's first command once when two runners race it under one command id",
|
|
295
|
-
requiresIndependentConnections: true,
|
|
296
|
-
timeoutMs: 60_000,
|
|
297
|
-
run: ({ expect, environment }) => withCluster(environment, 2, Effect.gen(function* () {
|
|
298
|
-
const cluster = yield* ActorCluster;
|
|
299
|
-
const ref = yield* refOf("brand-new");
|
|
300
|
-
const paused = (yield* cluster.owner(ref));
|
|
301
|
-
const rival = 1 - paused;
|
|
302
|
-
expect((yield* inspect(rival, ref)).generation).toBe(undefined);
|
|
303
|
-
const heartbeat = yield* cluster.pauseHeartbeat(paused);
|
|
304
|
-
yield* awaitOwner(ref, (current) => current === rival);
|
|
305
|
-
const id = yield* cluster.on(rival)(Effect.gen(function* () {
|
|
306
|
-
return yield* (yield* Actors).mintCommandId;
|
|
307
|
-
}));
|
|
308
|
-
const before = addRuns.count;
|
|
309
|
-
const replies = yield* Effect.forEach([paused, rival], (runner) => cluster.on(runner)(Tally.get("brand-new").pipe(Effect.flatMap((tally) => tally.Add(5).pipe(Actor.commandId(id))))), { concurrency: 2 });
|
|
310
|
-
yield* heartbeat.resume;
|
|
311
|
-
expect(replies).toEqual([5, 5]);
|
|
312
|
-
expect(addRuns.count - before).toBe(1);
|
|
313
|
-
expect(yield* inspect(rival, ref)).toMatchObject({
|
|
314
|
-
state: { count: 5 },
|
|
315
|
-
receipts: 1,
|
|
316
|
-
events: 1,
|
|
317
|
-
});
|
|
318
|
-
})),
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
name: "orders events gap-free when two real runners race for one actor",
|
|
322
|
-
requiresIndependentConnections: true,
|
|
323
|
-
timeoutMs: 90_000,
|
|
324
|
-
run: ({ expect, environment }) => withCluster(environment, 2, Effect.gen(function* () {
|
|
325
|
-
const cluster = yield* ActorCluster;
|
|
326
|
-
const ref = yield* refOf("raced");
|
|
327
|
-
const paused = (yield* cluster.owner(ref));
|
|
328
|
-
const rival = 1 - paused;
|
|
329
|
-
yield* add(paused, "raced", 1);
|
|
330
|
-
const first = Number((yield* inspect(rival, ref)).generation);
|
|
331
|
-
const heartbeat = yield* cluster.pauseHeartbeat(paused);
|
|
332
|
-
yield* awaitOwner(ref, (current) => current === rival);
|
|
333
|
-
yield* Effect.forEach(Array.from({ length: 24 }, (_, index) => index), (index) => add(index % 2 === 0 ? paused : rival, "raced", 1), { concurrency: 8, discard: true });
|
|
334
|
-
yield* heartbeat.resume;
|
|
335
|
-
const log = yield* cluster.on(rival)(Tally.get("raced").pipe(Effect.flatMap((tally) => tally.Log())));
|
|
336
|
-
expect(log.map(({ cursor }) => cursor)).toEqual(Array.from({ length: 25 }, (_, index) => String(index + 1)));
|
|
337
|
-
expect(new Set(log.map(({ commandId }) => commandId)).size).toBe(25);
|
|
338
|
-
const inspection = yield* inspect(rival, ref);
|
|
339
|
-
expect(inspection).toMatchObject({ state: { count: 25 }, receipts: 25, events: 25 });
|
|
340
|
-
expect(Number(inspection.generation) >= first + 2).toBe(true);
|
|
341
|
-
})),
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
name: "restarts a killed runner, which serves its shards again",
|
|
345
|
-
requiresIndependentConnections: true,
|
|
346
|
-
timeoutMs: 60_000,
|
|
347
|
-
run: ({ expect, environment }) => withCluster(environment, 2, Effect.gen(function* () {
|
|
348
|
-
const cluster = yield* ActorCluster;
|
|
349
|
-
const ref = yield* refOf("restart");
|
|
350
|
-
const owner = (yield* cluster.owner(ref));
|
|
351
|
-
const other = 1 - owner;
|
|
352
|
-
expect(yield* add(other, "restart", 1)).toBe(1);
|
|
353
|
-
yield* cluster.kill(owner);
|
|
354
|
-
expect(yield* add(other, "restart", 1)).toBe(2);
|
|
355
|
-
expect(yield* cluster.owner(ref)).toBe(other);
|
|
356
|
-
yield* cluster.restart(owner);
|
|
357
|
-
yield* cluster.ready;
|
|
358
|
-
expect((yield* cluster.owner(ref)) === undefined).toBe(false);
|
|
359
|
-
expect(yield* add(owner, "restart", 1)).toBe(3);
|
|
360
|
-
expect(yield* add(other, "restart", 1)).toBe(4);
|
|
361
|
-
expect(yield* inspect(owner, ref)).toMatchObject({ state: { count: 4 }, receipts: 4 });
|
|
362
|
-
})),
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
name: "serializes a 512-byte subject through a cross-runner command",
|
|
366
|
-
requiresIndependentConnections: true,
|
|
367
|
-
timeoutMs: 60_000,
|
|
368
|
-
run: ({ expect, environment }) => withCluster(environment, 2, Effect.gen(function* () {
|
|
369
|
-
const cluster = yield* ActorCluster;
|
|
370
|
-
const subject = "é".repeat(256);
|
|
371
|
-
const ids = Array.from({ length: 16 }, (_, index) => `principal-${index}`);
|
|
372
|
-
for (const id of ids) {
|
|
373
|
-
yield* add(0, id, 1);
|
|
374
|
-
const owner = yield* cluster.owner(yield* refOf(id));
|
|
375
|
-
const caller = owner === 0 ? 1 : 0;
|
|
376
|
-
const echoed = yield* cluster.on(caller)(Tally.get(id).pipe(Effect.flatMap((tally) => tally.Whoami()), Actor.as(User.make({ subject }))));
|
|
377
|
-
expect(owner === 0 || owner === 1).toBe(true);
|
|
378
|
-
expect(echoed).toBe(subject);
|
|
379
|
-
}
|
|
380
|
-
})),
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
name: "carries the largest supported principal as onBehalfOf through a relay-delivered intent between runners",
|
|
384
|
-
requiresIndependentConnections: true,
|
|
385
|
-
timeoutMs: 90_000,
|
|
386
|
-
run: ({ expect, environment }) => withCluster(environment, 2, Effect.gen(function* () {
|
|
387
|
-
const cluster = yield* ActorCluster;
|
|
388
|
-
const subject = "é".repeat(256);
|
|
389
|
-
const receivers = Array.from({ length: 12 }, (_, index) => `receiver-${index}`);
|
|
390
|
-
for (const receiver of receivers)
|
|
391
|
-
yield* cluster.on(0)(Envoy.get("sender").pipe(Effect.flatMap((envoy) => envoy.Dispatch(receiver)), Actor.as(User.make({ subject }))));
|
|
392
|
-
const owners = new Set();
|
|
393
|
-
for (const receiver of receivers) {
|
|
394
|
-
owners.add(yield* cluster.owner((yield* cluster.on(0)(Envoy.get(receiver))).ref));
|
|
395
|
-
const heard = yield* cluster
|
|
396
|
-
.on(1)(Envoy.get(receiver).pipe(Effect.flatMap((envoy) => envoy.Heard()), Effect.filterOrFail((value) => value !== "")))
|
|
397
|
-
.pipe(Effect.retry({ times: 60, schedule: Schedule.spaced("500 millis") }));
|
|
398
|
-
expect(heard).toBe(subject);
|
|
399
|
-
}
|
|
400
|
-
expect(owners.size).toBe(2);
|
|
401
|
-
})),
|
|
402
|
-
},
|
|
403
|
-
];
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/** One node of a Neki plan printed with `FORMAT TEXT`. */
|
|
2
|
-
export interface NekiPlanNode {
|
|
3
|
-
/** `Route`, `Collapse`, `Aggregate`, `Limit`, and any other router operator. */
|
|
4
|
-
readonly operator: string;
|
|
5
|
-
/** The bracketed routing choice, e.g. `EqualUnique`, `IN` or `Scatter`. */
|
|
6
|
-
readonly kind: string | undefined;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* The router operators of a plan printed by `EXPLAIN (NEKI_PLAN, FORMAT TEXT)`,
|
|
10
|
-
* in print order. Tree glyphs and indentation are dropped.
|
|
11
|
-
*/
|
|
12
|
-
export declare const nekiPlanNodes: (plan: string) => ReadonlyArray<NekiPlanNode>;
|
|
13
|
-
/**
|
|
14
|
-
* Whether a plan is one route to one shard and nothing else. A router
|
|
15
|
-
* operator above the route, a second route, a scatter, an `IN` list, and a
|
|
16
|
-
* routing choice not known to be single-shard all fail: an unfamiliar plan is
|
|
17
|
-
* reported, never assumed safe.
|
|
18
|
-
*/
|
|
19
|
-
export declare const touchesOneShard: (plan: string) => boolean;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A node line is an operator name with an optional bracketed kind. Attribute
|
|
3
|
-
* lines such as `Query:` and `ShardGroup:` carry a colon and are not nodes.
|
|
4
|
-
*/
|
|
5
|
-
const NODE = /^([A-Za-z][A-Za-z ]*?)(?: \[([^\]]+)\])?$/;
|
|
6
|
-
/**
|
|
7
|
-
* The router operators of a plan printed by `EXPLAIN (NEKI_PLAN, FORMAT TEXT)`,
|
|
8
|
-
* in print order. Tree glyphs and indentation are dropped.
|
|
9
|
-
*/
|
|
10
|
-
export const nekiPlanNodes = (plan) => plan
|
|
11
|
-
.split("\n")
|
|
12
|
-
.map((line) => line.replace(/^[│├└─\s]+/u, "").trim())
|
|
13
|
-
.flatMap((line) => {
|
|
14
|
-
const match = NODE.exec(line);
|
|
15
|
-
return match === null || line.includes(":") ? [] : [{ operator: match[1], kind: match[2] }];
|
|
16
|
-
});
|
|
17
|
-
/** The routing choices that Neki documents as reaching exactly one shard. */
|
|
18
|
-
const SINGLE_SHARD_ROUTES = new Set(["EqualUnique"]);
|
|
19
|
-
/**
|
|
20
|
-
* Whether a plan is one route to one shard and nothing else. A router
|
|
21
|
-
* operator above the route, a second route, a scatter, an `IN` list, and a
|
|
22
|
-
* routing choice not known to be single-shard all fail: an unfamiliar plan is
|
|
23
|
-
* reported, never assumed safe.
|
|
24
|
-
*/
|
|
25
|
-
export const touchesOneShard = (plan) => {
|
|
26
|
-
const nodes = nekiPlanNodes(plan);
|
|
27
|
-
return (nodes.length === 1 &&
|
|
28
|
-
nodes[0].operator === "Route" &&
|
|
29
|
-
SINGLE_SHARD_ROUTES.has(nodes[0].kind ?? ""));
|
|
30
|
-
};
|