@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,664 +0,0 @@
|
|
|
1
|
-
import { eq, inArray, Param, SQL, sql as drizzleSql, StringChunk } from "drizzle-orm";
|
|
2
|
-
import { index, integer, pgTable, text } from "drizzle-orm/pg-core";
|
|
3
|
-
import { Cause, DateTime, Deferred, Effect, Exit, Fiber, Layer, Option, Schema } from "effect";
|
|
4
|
-
import { SqlClient } from "effect/sql";
|
|
5
|
-
import { retryPoolRefusal } from "../../runtime/database/bounded.js";
|
|
6
|
-
import { Actor } from "../../index.js";
|
|
7
|
-
import { ActorTest } from "../actor-test.js";
|
|
8
|
-
/** An owned table with a column-level key, a per-actor unique column, and an index. */
|
|
9
|
-
export const notes = Actor.table(pgTable("conformance_notes", {
|
|
10
|
-
id: text("id").primaryKey(),
|
|
11
|
-
body: text("body").notNull().unique(),
|
|
12
|
-
rank: integer("rank").notNull().default(0),
|
|
13
|
-
}, (table) => [index("conformance_notes_rank").on(table.rank)]));
|
|
14
|
-
/** A second actor type's table in the same tenant placement group. */
|
|
15
|
-
export const labels = Actor.table(pgTable("conformance_labels", {
|
|
16
|
-
id: text("id").primaryKey(),
|
|
17
|
-
noteId: text("note_id").notNull(),
|
|
18
|
-
label: text("label").notNull(),
|
|
19
|
-
}));
|
|
20
|
-
const unowned = pgTable("conformance_unowned", { id: text("id").primaryKey() });
|
|
21
|
-
const receipts = Actor.table(pgTable("actor_receipts", {
|
|
22
|
-
command_id: text("command_id").primaryKey(),
|
|
23
|
-
command: text("command"),
|
|
24
|
-
}));
|
|
25
|
-
/** What drizzle-kit generates for the tables above; `owned.test.ts` checks it stays so. */
|
|
26
|
-
export const tablesDdl = [
|
|
27
|
-
`CREATE TABLE "conformance_notes" (
|
|
28
|
-
"routing_key" bigint,
|
|
29
|
-
"tenant_id" text,
|
|
30
|
-
"actor_id" text,
|
|
31
|
-
"id" text,
|
|
32
|
-
"body" text NOT NULL,
|
|
33
|
-
"rank" integer DEFAULT 0 NOT NULL,
|
|
34
|
-
CONSTRAINT "conformance_notes_pkey" PRIMARY KEY("routing_key","tenant_id","actor_id","id"),
|
|
35
|
-
CONSTRAINT "conformance_notes_routing_key_tenant_id_actor_id_body_unique" UNIQUE("routing_key","tenant_id","actor_id","body")
|
|
36
|
-
);
|
|
37
|
-
`,
|
|
38
|
-
`ALTER TABLE "conformance_notes" ENABLE ROW LEVEL SECURITY;`,
|
|
39
|
-
`CREATE TABLE "conformance_labels" (
|
|
40
|
-
"routing_key" bigint,
|
|
41
|
-
"tenant_id" text,
|
|
42
|
-
"actor_id" text,
|
|
43
|
-
"id" text,
|
|
44
|
-
"note_id" text NOT NULL,
|
|
45
|
-
"label" text NOT NULL,
|
|
46
|
-
CONSTRAINT "conformance_labels_pkey" PRIMARY KEY("routing_key","tenant_id","actor_id","id")
|
|
47
|
-
);
|
|
48
|
-
`,
|
|
49
|
-
`ALTER TABLE "conformance_labels" ENABLE ROW LEVEL SECURITY;`,
|
|
50
|
-
`CREATE INDEX "conformance_notes_rank" ON "conformance_notes" ("routing_key","tenant_id","actor_id","rank");`,
|
|
51
|
-
`CREATE POLICY "durable_tenant" ON "conformance_notes" AS PERMISSIVE FOR ALL TO public USING (tenant_id = current_setting('durable.tenant', true)) WITH CHECK (tenant_id = current_setting('durable.tenant', true));`,
|
|
52
|
-
`CREATE POLICY "durable_tenant" ON "conformance_labels" AS PERMISSIVE FOR ALL TO public USING (tenant_id = current_setting('durable.tenant', true)) WITH CHECK (tenant_id = current_setting('durable.tenant', true));`,
|
|
53
|
-
];
|
|
54
|
-
export const tablesFixture = () => ({ escaped: Effect.void, hold: Effect.void });
|
|
55
|
-
class NotebookRejected extends Schema.TaggedError()("NotebookRejected", {}) {
|
|
56
|
-
}
|
|
57
|
-
const Note = Schema.Struct({ id: Schema.String, body: Schema.String, rank: Schema.Int });
|
|
58
|
-
export const Misuse = Schema.Literals([
|
|
59
|
-
"ownerInsert",
|
|
60
|
-
"ownerFilter",
|
|
61
|
-
"ownerSet",
|
|
62
|
-
"ownerUpsert",
|
|
63
|
-
"rawValue",
|
|
64
|
-
"rawFilter",
|
|
65
|
-
"unknownColumn",
|
|
66
|
-
"undeclaredTable",
|
|
67
|
-
"wrappedFilter",
|
|
68
|
-
"wrappedValue",
|
|
69
|
-
]);
|
|
70
|
-
const GroupMisuse = Schema.Literals([
|
|
71
|
-
"raw",
|
|
72
|
-
"rightJoin",
|
|
73
|
-
"lock",
|
|
74
|
-
"unowned",
|
|
75
|
-
"subquery",
|
|
76
|
-
"parentheses",
|
|
77
|
-
"param",
|
|
78
|
-
"wrapper",
|
|
79
|
-
"ownership",
|
|
80
|
-
"unlisted",
|
|
81
|
-
]);
|
|
82
|
-
const Write = Actor.command("Write", {
|
|
83
|
-
payload: Schema.Struct({ id: Schema.String, body: Schema.String }),
|
|
84
|
-
success: Schema.Int,
|
|
85
|
-
});
|
|
86
|
-
const Save = Actor.command("Save", { payload: Note });
|
|
87
|
-
const Rename = Actor.command("Rename", {
|
|
88
|
-
payload: Schema.Struct({ id: Schema.String, body: Schema.String }),
|
|
89
|
-
});
|
|
90
|
-
const Promote = Actor.command("Promote", {
|
|
91
|
-
payload: Schema.Struct({ atLeast: Schema.Int, rank: Schema.Int }),
|
|
92
|
-
});
|
|
93
|
-
const Remove = Actor.command("Remove", { payload: Schema.String });
|
|
94
|
-
const Clear = Actor.command("Clear");
|
|
95
|
-
const WriteThenReject = Actor.command("WriteThenReject", {
|
|
96
|
-
payload: Schema.String,
|
|
97
|
-
error: NotebookRejected,
|
|
98
|
-
});
|
|
99
|
-
const WriteThenMisuse = Actor.command("WriteThenMisuse", { payload: Misuse });
|
|
100
|
-
const WriteThenHold = Actor.command("WriteThenHold", { payload: Schema.String });
|
|
101
|
-
const Capture = Actor.command("Capture");
|
|
102
|
-
const CaptureGroup = Actor.command("CaptureGroup");
|
|
103
|
-
const CaptureUnbound = Actor.command("CaptureUnbound");
|
|
104
|
-
const Replay = Actor.command("Replay");
|
|
105
|
-
const List = Actor.query("List", { success: Schema.Array(Note) });
|
|
106
|
-
const Get = Actor.query("Get", { payload: Schema.String, success: Schema.Option(Note) });
|
|
107
|
-
const Ranked = Actor.query("Ranked", {
|
|
108
|
-
payload: Schema.Struct({ limit: Schema.Int, offset: Schema.Int }),
|
|
109
|
-
success: Schema.Array(Schema.String),
|
|
110
|
-
});
|
|
111
|
-
const Count = Actor.query("Count", { success: Schema.Int });
|
|
112
|
-
const QueryWrite = Actor.query("QueryWrite");
|
|
113
|
-
const Joined = Schema.Struct({
|
|
114
|
-
note: Schema.String,
|
|
115
|
-
body: Schema.String,
|
|
116
|
-
label: Schema.NullOr(Schema.String),
|
|
117
|
-
});
|
|
118
|
-
const Catalog = Actor.query("Catalog", { payload: Schema.Boolean, success: Schema.Array(Joined) });
|
|
119
|
-
const Misgroup = Actor.query("Misgroup", { payload: GroupMisuse });
|
|
120
|
-
/** Queries that try to smuggle SQL past validation; any rows they return must stay in scope. */
|
|
121
|
-
const Smuggle = Schema.Literals([
|
|
122
|
-
"dateFilter",
|
|
123
|
-
"dateGroup",
|
|
124
|
-
"hiddenWhere",
|
|
125
|
-
"hiddenSelection",
|
|
126
|
-
"shiftingText",
|
|
127
|
-
"shiftingParam",
|
|
128
|
-
"shiftingChunks",
|
|
129
|
-
]);
|
|
130
|
-
const Smuggled = Actor.query("Smuggled", {
|
|
131
|
-
payload: Smuggle,
|
|
132
|
-
success: Schema.Array(Schema.String),
|
|
133
|
-
});
|
|
134
|
-
const Everything = Actor.query("Everything", { success: Schema.Array(Schema.String) });
|
|
135
|
-
/** @internal */
|
|
136
|
-
export const Notebook = Actor.make("Notebook", {
|
|
137
|
-
key: Schema.String,
|
|
138
|
-
tables: [notes],
|
|
139
|
-
api: {
|
|
140
|
-
Write,
|
|
141
|
-
Save,
|
|
142
|
-
Rename,
|
|
143
|
-
Promote,
|
|
144
|
-
Remove,
|
|
145
|
-
Clear,
|
|
146
|
-
WriteThenReject,
|
|
147
|
-
WriteThenMisuse,
|
|
148
|
-
WriteThenHold,
|
|
149
|
-
Capture,
|
|
150
|
-
CaptureGroup,
|
|
151
|
-
CaptureUnbound,
|
|
152
|
-
Replay,
|
|
153
|
-
List,
|
|
154
|
-
Get,
|
|
155
|
-
Ranked,
|
|
156
|
-
Count,
|
|
157
|
-
QueryWrite,
|
|
158
|
-
Catalog,
|
|
159
|
-
Misgroup,
|
|
160
|
-
Everything,
|
|
161
|
-
Smuggled,
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
const Label = Actor.command("Label", {
|
|
165
|
-
payload: Schema.Struct({ id: Schema.String, noteId: Schema.String, label: Schema.String }),
|
|
166
|
-
});
|
|
167
|
-
const Shelf = Actor.make("Shelf", { key: Schema.String, tables: [labels], api: { Label } });
|
|
168
|
-
const sqlLookalike = { getSQL: () => drizzleSql.raw("'zzz')) or ((true") };
|
|
169
|
-
const escape = "'zzz')) or ((true";
|
|
170
|
-
const sqlDate = (text) => Object.assign(DateTime.toDateUtc(DateTime.makeUnsafe(0)), {
|
|
171
|
-
getSQL: () => drizzleSql.raw(text),
|
|
172
|
-
});
|
|
173
|
-
const hiddenSql = (text) => Object.defineProperty({}, "getSQL", { value: () => drizzleSql.raw(text) });
|
|
174
|
-
const shifting = (safe, evil, safeReads) => {
|
|
175
|
-
let reads = 0;
|
|
176
|
-
return () => {
|
|
177
|
-
reads += 1;
|
|
178
|
-
return reads <= safeReads ? safe : evil;
|
|
179
|
-
};
|
|
180
|
-
};
|
|
181
|
-
const misuse = (loose, kind) => {
|
|
182
|
-
switch (kind) {
|
|
183
|
-
case "ownerInsert":
|
|
184
|
-
return loose.insert({ id: "forged", body: "forged", actor_id: "victim" });
|
|
185
|
-
case "ownerFilter":
|
|
186
|
-
return loose.delete().where({ actor_id: { eq: "victim" } });
|
|
187
|
-
case "ownerSet":
|
|
188
|
-
return loose.update({ tenant_id: "elsewhere" }).where({});
|
|
189
|
-
case "ownerUpsert":
|
|
190
|
-
return loose.upsert({ id: "forged", body: "forged", routing_key: 0n });
|
|
191
|
-
case "rawValue":
|
|
192
|
-
return loose.insert({ id: "raw", body: drizzleSql `(SELECT body FROM conformance_notes)` });
|
|
193
|
-
case "rawFilter":
|
|
194
|
-
return loose.delete().where({ RAW: { eq: drizzleSql `true` } });
|
|
195
|
-
case "unknownColumn":
|
|
196
|
-
return loose.insert({ id: "unknown", body: "unknown", secret: 1 });
|
|
197
|
-
case "wrappedFilter":
|
|
198
|
-
return loose.delete().where({ id: { eq: sqlLookalike } });
|
|
199
|
-
case "wrappedValue":
|
|
200
|
-
return loose.insert({ id: "wrapped", body: sqlLookalike });
|
|
201
|
-
case "undeclaredTable":
|
|
202
|
-
return loose.all();
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
const NotebookLive = (fixture) => Notebook.toLayer(Effect.succeed({
|
|
206
|
-
Write: Effect.fnUntraced(function* ({ id, body }) {
|
|
207
|
-
const turn = yield* Notebook.Turn;
|
|
208
|
-
yield* turn.rows(notes).insert({ id, body });
|
|
209
|
-
return yield* turn.rows(notes).count();
|
|
210
|
-
}),
|
|
211
|
-
Save: Effect.fnUntraced(function* (note) {
|
|
212
|
-
yield* (yield* Notebook.Turn).rows(notes).upsert(note);
|
|
213
|
-
}),
|
|
214
|
-
Rename: Effect.fnUntraced(function* ({ id, body }) {
|
|
215
|
-
yield* (yield* Notebook.Turn).rows(notes).update({ body }).where({ id });
|
|
216
|
-
}),
|
|
217
|
-
Promote: Effect.fnUntraced(function* ({ atLeast, rank }) {
|
|
218
|
-
yield* (yield* Notebook.Turn)
|
|
219
|
-
.rows(notes)
|
|
220
|
-
.update({ rank })
|
|
221
|
-
.where({ rank: { gte: atLeast } });
|
|
222
|
-
}),
|
|
223
|
-
Remove: Effect.fnUntraced(function* (id) {
|
|
224
|
-
yield* (yield* Notebook.Turn).rows(notes).delete().where({ id });
|
|
225
|
-
}),
|
|
226
|
-
Clear: Effect.fnUntraced(function* () {
|
|
227
|
-
yield* (yield* Notebook.Turn).rows(notes).delete().where({});
|
|
228
|
-
}),
|
|
229
|
-
WriteThenReject: Effect.fnUntraced(function* (id) {
|
|
230
|
-
const rows = (yield* Notebook.Turn).rows(notes);
|
|
231
|
-
yield* rows.insert({ id, body: id });
|
|
232
|
-
yield* rows.update({ rank: 3 }).where({});
|
|
233
|
-
yield* rows.upsert({ id: "kept", body: "upserted", rank: 9 });
|
|
234
|
-
yield* rows.delete().where({ id });
|
|
235
|
-
return yield* NotebookRejected.make({});
|
|
236
|
-
}),
|
|
237
|
-
WriteThenMisuse: Effect.fnUntraced(function* (kind) {
|
|
238
|
-
const turn = yield* Notebook.Turn;
|
|
239
|
-
yield* turn.rows(notes).insert({ id: "before-misuse", body: "before-misuse" });
|
|
240
|
-
const loose = turn.rows;
|
|
241
|
-
yield* misuse(loose(kind === "undeclaredTable" ? labels : notes), kind);
|
|
242
|
-
}),
|
|
243
|
-
WriteThenHold: Effect.fnUntraced(function* (id) {
|
|
244
|
-
const rows = (yield* Notebook.Turn).rows(notes);
|
|
245
|
-
fixture.escaped = rows.insert({ id: `${id}-stolen`, body: `${id}-stolen` });
|
|
246
|
-
yield* rows.insert({ id, body: id });
|
|
247
|
-
yield* fixture.hold;
|
|
248
|
-
}),
|
|
249
|
-
Capture: Effect.fnUntraced(function* () {
|
|
250
|
-
const rows = (yield* Notebook.Turn).rows(notes);
|
|
251
|
-
fixture.escaped = rows.insert({ id: "escaped", body: "escaped" });
|
|
252
|
-
yield* rows.insert({ id: "captured", body: "captured" });
|
|
253
|
-
}),
|
|
254
|
-
CaptureGroup: Effect.fnUntraced(function* () {
|
|
255
|
-
fixture.escaped = (yield* Notebook.Turn).group((db) => db.select({ id: notes.id }).from(notes));
|
|
256
|
-
}),
|
|
257
|
-
CaptureUnbound: Effect.fnUntraced(function* () {
|
|
258
|
-
const turn = yield* Notebook.Turn;
|
|
259
|
-
fixture.escaped = Effect.suspend(() => turn.rows(notes).insert({ id: "unbound", body: "unbound" }));
|
|
260
|
-
}),
|
|
261
|
-
Replay: () => Effect.suspend(() => fixture.escaped).pipe(Effect.asVoid),
|
|
262
|
-
}));
|
|
263
|
-
const NotebookReads = Notebook.toQueryLayer(Effect.succeed({
|
|
264
|
-
List: Effect.fnUntraced(function* () {
|
|
265
|
-
return yield* (yield* Notebook.Read).rows(notes).all({ orderBy: { id: "asc" } });
|
|
266
|
-
}),
|
|
267
|
-
Get: Effect.fnUntraced(function* (id) {
|
|
268
|
-
return yield* (yield* Notebook.Read).rows(notes).one({ where: { id } });
|
|
269
|
-
}),
|
|
270
|
-
Ranked: Effect.fnUntraced(function* ({ limit, offset }) {
|
|
271
|
-
const found = yield* (yield* Notebook.Read)
|
|
272
|
-
.rows(notes)
|
|
273
|
-
.all({ orderBy: { rank: "desc", id: "asc" }, limit, offset });
|
|
274
|
-
return found.map((note) => note.id);
|
|
275
|
-
}),
|
|
276
|
-
Count: Effect.fnUntraced(function* () {
|
|
277
|
-
return yield* (yield* Notebook.Read).rows(notes).count();
|
|
278
|
-
}),
|
|
279
|
-
QueryWrite: Effect.fnUntraced(function* () {
|
|
280
|
-
const rows = (yield* Notebook.Read).rows(notes);
|
|
281
|
-
const attempted = rows.insert({ id: "from-query", body: "from-query" });
|
|
282
|
-
yield* attempted;
|
|
283
|
-
}),
|
|
284
|
-
Catalog: Effect.fnUntraced(function* (left) {
|
|
285
|
-
return yield* (yield* Notebook.Read).group((db) => {
|
|
286
|
-
const query = db
|
|
287
|
-
.select({ note: notes.id, body: notes.body, label: labels.label })
|
|
288
|
-
.from(notes)
|
|
289
|
-
.$dynamic();
|
|
290
|
-
return (left
|
|
291
|
-
? query.leftJoin(labels, eq(labels.noteId, notes.id))
|
|
292
|
-
: query.innerJoin(labels, eq(labels.noteId, notes.id)))
|
|
293
|
-
.where(inArray(notes.id, ["a", "b", "c"]))
|
|
294
|
-
.orderBy(notes.id, labels.label);
|
|
295
|
-
});
|
|
296
|
-
}),
|
|
297
|
-
Everything: Effect.fnUntraced(function* () {
|
|
298
|
-
const found = yield* (yield* Notebook.Read).group((db) => db
|
|
299
|
-
.select({ body: notes.body })
|
|
300
|
-
.from(notes)
|
|
301
|
-
.where(drizzleSql `true or true`));
|
|
302
|
-
return found.map((note) => note.body);
|
|
303
|
-
}),
|
|
304
|
-
Smuggled: Effect.fnUntraced(function* (kind) {
|
|
305
|
-
const read = yield* Notebook.Read;
|
|
306
|
-
if (kind === "dateFilter") {
|
|
307
|
-
const found = yield* read.rows(notes).all({ where: { id: { eq: sqlDate(escape) } } });
|
|
308
|
-
return found.map((note) => note.body);
|
|
309
|
-
}
|
|
310
|
-
const found = yield* read.group((db) => {
|
|
311
|
-
const base = db.select({ body: notes.body }).from(notes);
|
|
312
|
-
switch (kind) {
|
|
313
|
-
case "dateGroup":
|
|
314
|
-
return base.where(drizzleSql `${sqlDate("true)) or ((true")}`);
|
|
315
|
-
case "hiddenWhere":
|
|
316
|
-
return base.where(drizzleSql `${hiddenSql("true)) or ((true")}`);
|
|
317
|
-
case "hiddenSelection":
|
|
318
|
-
return db
|
|
319
|
-
.select({
|
|
320
|
-
body: drizzleSql `${hiddenSql("(select string_agg(body, ',') from conformance_notes)")}`.as("body"),
|
|
321
|
-
})
|
|
322
|
-
.from(notes);
|
|
323
|
-
case "shiftingText": {
|
|
324
|
-
const chunk = new StringChunk("true");
|
|
325
|
-
const value = shifting("true", "true)) or ((true", 2);
|
|
326
|
-
Object.defineProperty(chunk, "value", { get: value });
|
|
327
|
-
return base.where(new SQL([chunk]));
|
|
328
|
-
}
|
|
329
|
-
case "shiftingParam": {
|
|
330
|
-
const param = new Param(true);
|
|
331
|
-
const value = shifting(true, drizzleSql.raw("true)) or ((true"), 1);
|
|
332
|
-
Object.defineProperty(param, "value", { get: value });
|
|
333
|
-
return base.where(drizzleSql `${param}`);
|
|
334
|
-
}
|
|
335
|
-
case "shiftingChunks": {
|
|
336
|
-
const expression = new SQL([new StringChunk("true")]);
|
|
337
|
-
const chunks = shifting([new StringChunk("true")], [new StringChunk("true)) or ((true")], 1);
|
|
338
|
-
Object.defineProperty(expression, "queryChunks", { get: chunks });
|
|
339
|
-
return base.where(expression);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
return found.map((note) => note.body);
|
|
344
|
-
}),
|
|
345
|
-
Misgroup: Effect.fnUntraced(function* (kind) {
|
|
346
|
-
yield* (yield* Notebook.Read).group((db) => {
|
|
347
|
-
switch (kind) {
|
|
348
|
-
case "raw":
|
|
349
|
-
return db
|
|
350
|
-
.select({ id: notes.id })
|
|
351
|
-
.from(notes)
|
|
352
|
-
.where(drizzleSql `exists (select 1 from conformance_labels)`);
|
|
353
|
-
case "subquery":
|
|
354
|
-
return db
|
|
355
|
-
.select({ id: notes.id })
|
|
356
|
-
.from(notes)
|
|
357
|
-
.where(drizzleSql `${notes.id} in ${db.select({ id: labels.noteId }).from(labels)}`);
|
|
358
|
-
case "rightJoin":
|
|
359
|
-
return db
|
|
360
|
-
.select({ id: notes.id })
|
|
361
|
-
.from(notes)
|
|
362
|
-
.rightJoin(labels, eq(labels.noteId, notes.id));
|
|
363
|
-
case "lock":
|
|
364
|
-
return db.select({ id: notes.id }).from(notes).for("update");
|
|
365
|
-
case "unowned":
|
|
366
|
-
return db.select({ id: unowned.id }).from(unowned);
|
|
367
|
-
case "parentheses":
|
|
368
|
-
return db
|
|
369
|
-
.select({ id: notes.id })
|
|
370
|
-
.from(notes)
|
|
371
|
-
.where(drizzleSql `true)) or ((true`);
|
|
372
|
-
case "param":
|
|
373
|
-
return db
|
|
374
|
-
.select({ id: notes.id })
|
|
375
|
-
.from(notes)
|
|
376
|
-
.where(drizzleSql `${new Param(drizzleSql.raw("true"))}`);
|
|
377
|
-
case "wrapper":
|
|
378
|
-
return db
|
|
379
|
-
.select({ id: notes.id })
|
|
380
|
-
.from(notes)
|
|
381
|
-
.where(drizzleSql `${sqlLookalike}`);
|
|
382
|
-
case "unlisted":
|
|
383
|
-
return db.select({ id: receipts.command }).from(receipts);
|
|
384
|
-
case "ownership":
|
|
385
|
-
return db.select({ id: notes.tenant_id }).from(notes);
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
}),
|
|
389
|
-
}));
|
|
390
|
-
const ShelfLive = Shelf.toLayer(Effect.succeed({
|
|
391
|
-
Label: Effect.fnUntraced(function* (label) {
|
|
392
|
-
yield* (yield* Shelf.Turn).rows(labels).insert(label);
|
|
393
|
-
}),
|
|
394
|
-
}));
|
|
395
|
-
/** Applies the drizzle-kit DDL once, then registers the table-owning actors. */
|
|
396
|
-
export const tablesLayer = (fixture) => Layer.unwrap(Effect.gen(function* () {
|
|
397
|
-
const sql = yield* SqlClient.SqlClient;
|
|
398
|
-
const existing = yield* retryPoolRefusal(sql `
|
|
399
|
-
SELECT relname FROM pg_class WHERE relname = 'conformance_notes'`);
|
|
400
|
-
if (existing.length === 0)
|
|
401
|
-
for (const statement of tablesDdl)
|
|
402
|
-
yield* retryPoolRefusal(sql.unsafe(statement));
|
|
403
|
-
return Layer.mergeAll(NotebookLive(fixture), NotebookReads, ShelfLive);
|
|
404
|
-
}).pipe(Effect.orDie));
|
|
405
|
-
const defect = (exit) => Exit.isFailure(exit) ? Cause.pretty(exit.cause) : "succeeded";
|
|
406
|
-
const rowsOf = (count) => ({ rows: { conformance_notes: count } });
|
|
407
|
-
/** Owned-table cases: rows scope by tenant and actor for every operation, and constraint violations are defects without a receipt. */
|
|
408
|
-
export const tablesConformance = [
|
|
409
|
-
{
|
|
410
|
-
name: "scopes owned rows by tenant and actor for every supported operation",
|
|
411
|
-
run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
|
|
412
|
-
const test = yield* ActorTest;
|
|
413
|
-
const home = yield* Notebook.get("shared-id");
|
|
414
|
-
const neighbor = yield* Notebook.get("neighbor");
|
|
415
|
-
const abroad = yield* Notebook.get("shared-id").pipe(Actor.tenant(`${test.tenant}-b`));
|
|
416
|
-
const all = [home, neighbor, abroad];
|
|
417
|
-
for (const notebook of all)
|
|
418
|
-
expect(yield* notebook.Write({ id: "n1", body: "one" })).toBe(1);
|
|
419
|
-
for (const notebook of all)
|
|
420
|
-
expect(yield* notebook.Write({ id: "n2", body: "two" })).toBe(2);
|
|
421
|
-
yield* home.Save({ id: "n1", body: "uno", rank: 5 });
|
|
422
|
-
yield* neighbor.Save({ id: "n3", body: "three", rank: 1 });
|
|
423
|
-
yield* abroad.Rename({ id: "n2", body: "dos" });
|
|
424
|
-
yield* neighbor.Promote({ atLeast: 0, rank: 7 });
|
|
425
|
-
yield* abroad.Remove("n1");
|
|
426
|
-
expect(yield* home.List()).toEqual([
|
|
427
|
-
{ id: "n1", body: "uno", rank: 5 },
|
|
428
|
-
{ id: "n2", body: "two", rank: 0 },
|
|
429
|
-
]);
|
|
430
|
-
expect(yield* neighbor.List()).toEqual([
|
|
431
|
-
{ id: "n1", body: "one", rank: 7 },
|
|
432
|
-
{ id: "n2", body: "two", rank: 7 },
|
|
433
|
-
{ id: "n3", body: "three", rank: 7 },
|
|
434
|
-
]);
|
|
435
|
-
expect(yield* abroad.List()).toEqual([{ id: "n2", body: "dos", rank: 0 }]);
|
|
436
|
-
expect(yield* home.Get("n1")).toEqual(Option.some({ id: "n1", body: "uno", rank: 5 }));
|
|
437
|
-
expect(yield* abroad.Get("n1")).toEqual(Option.none());
|
|
438
|
-
expect(yield* neighbor.Ranked({ limit: 2, offset: 1 })).toEqual(["n2", "n3"]);
|
|
439
|
-
expect(yield* home.Count()).toBe(2);
|
|
440
|
-
yield* home.Clear();
|
|
441
|
-
expect(yield* test.inspect(home.ref)).toMatchObject(rowsOf(0));
|
|
442
|
-
expect(yield* test.inspect(neighbor.ref)).toMatchObject(rowsOf(3));
|
|
443
|
-
expect(yield* test.inspect(abroad.ref)).toMatchObject(rowsOf(1));
|
|
444
|
-
})),
|
|
445
|
-
},
|
|
446
|
-
{
|
|
447
|
-
name: "keeps unique constraints per actor and rejects a duplicate key as a defect without a receipt",
|
|
448
|
-
run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
|
|
449
|
-
const test = yield* ActorTest;
|
|
450
|
-
const first = yield* Notebook.get("unique-a");
|
|
451
|
-
const second = yield* Notebook.get("unique-b");
|
|
452
|
-
expect(yield* first.Write({ id: "k", body: "same" })).toBe(1);
|
|
453
|
-
expect(yield* second.Write({ id: "k", body: "same" })).toBe(1);
|
|
454
|
-
const duplicate = yield* first.Write({ id: "k2", body: "same" }).pipe(Effect.exit);
|
|
455
|
-
expect(defect(duplicate)).toContain("conformance_notes");
|
|
456
|
-
expect(yield* test.inspect(first.ref)).toMatchObject({ ...rowsOf(1), receipts: 1 });
|
|
457
|
-
expect(yield* test.inspect(second.ref)).toMatchObject({ ...rowsOf(1), receipts: 1 });
|
|
458
|
-
})),
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
name: "cannot write another actor's rows, even with an explicit actor_id",
|
|
462
|
-
run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
|
|
463
|
-
const test = yield* ActorTest;
|
|
464
|
-
const victim = yield* Notebook.get("victim");
|
|
465
|
-
const attacker = yield* Notebook.get("attacker");
|
|
466
|
-
expect(yield* victim.Write({ id: "v", body: "victim" })).toBe(1);
|
|
467
|
-
const rejections = [
|
|
468
|
-
["ownerInsert", "Ownership column actor_id"],
|
|
469
|
-
["ownerFilter", "Ownership column actor_id"],
|
|
470
|
-
["ownerSet", "Ownership column tenant_id"],
|
|
471
|
-
["ownerUpsert", "Ownership column routing_key"],
|
|
472
|
-
["rawValue", "plain data"],
|
|
473
|
-
["rawFilter", "RAW filters"],
|
|
474
|
-
["unknownColumn", "Unknown column secret"],
|
|
475
|
-
["undeclaredTable", "is not an owned table of Notebook"],
|
|
476
|
-
["wrappedFilter", "plain data"],
|
|
477
|
-
["wrappedValue", "plain data"],
|
|
478
|
-
];
|
|
479
|
-
for (const [kind, message] of rejections)
|
|
480
|
-
expect(defect(yield* attacker.WriteThenMisuse(kind).pipe(Effect.exit))).toContain(message);
|
|
481
|
-
expect(yield* test.inspect(attacker.ref)).toMatchObject({ ...rowsOf(0), receipts: 0 });
|
|
482
|
-
expect(yield* victim.List()).toEqual([{ id: "v", body: "victim", rank: 0 }]);
|
|
483
|
-
})),
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
name: "rolls back every owned-row write with a declared failure and keeps its receipt",
|
|
487
|
-
run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
|
|
488
|
-
const test = yield* ActorTest;
|
|
489
|
-
const notebook = yield* Notebook.get("rejected");
|
|
490
|
-
expect(yield* notebook.Write({ id: "kept", body: "kept" })).toBe(1);
|
|
491
|
-
const rejected = notebook.WriteThenReject("dropped");
|
|
492
|
-
expect(yield* rejected.pipe(Effect.flip)).toEqual(NotebookRejected.make({}));
|
|
493
|
-
expect(yield* rejected.pipe(Effect.flip)).toEqual(NotebookRejected.make({}));
|
|
494
|
-
expect(yield* notebook.List()).toEqual([{ id: "kept", body: "kept", rank: 0 }]);
|
|
495
|
-
expect(yield* test.inspect(notebook.ref)).toMatchObject({ ...rowsOf(1), receipts: 2 });
|
|
496
|
-
})),
|
|
497
|
-
},
|
|
498
|
-
{
|
|
499
|
-
name: "retries a crash before commit to exactly one owned row",
|
|
500
|
-
run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
|
|
501
|
-
const test = yield* ActorTest;
|
|
502
|
-
const notebook = yield* Notebook.get("crash");
|
|
503
|
-
yield* test.crashNext("beforeCommit");
|
|
504
|
-
expect(yield* notebook.Write({ id: "once", body: "once" })).toBe(1);
|
|
505
|
-
expect(yield* test.inspect(notebook.ref)).toMatchObject({ ...rowsOf(1), receipts: 1 });
|
|
506
|
-
})),
|
|
507
|
-
},
|
|
508
|
-
{
|
|
509
|
-
name: "gives queries read-only rows and rejects escaped row capabilities",
|
|
510
|
-
run: ({ expect, environment, fixture }) => environment.run(Effect.gen(function* () {
|
|
511
|
-
const test = yield* ActorTest;
|
|
512
|
-
const notebook = yield* Notebook.get("escape");
|
|
513
|
-
expect(defect(yield* notebook.QueryWrite().pipe(Effect.exit))).toContain("insert");
|
|
514
|
-
yield* notebook.Capture();
|
|
515
|
-
expect(defect(yield* fixture.escaped.pipe(Effect.exit))).toContain("Table capability escaped its turn");
|
|
516
|
-
expect(defect(yield* notebook.Replay().pipe(Effect.exit))).toContain("Table capability escaped its turn");
|
|
517
|
-
yield* notebook.CaptureGroup();
|
|
518
|
-
expect(defect(yield* fixture.escaped.pipe(Effect.exit))).toContain("Table capability escaped its turn");
|
|
519
|
-
yield* notebook.CaptureUnbound();
|
|
520
|
-
expect(defect(yield* fixture.escaped.pipe(Effect.exit))).toContain("Table capability escaped its turn");
|
|
521
|
-
expect(defect(yield* notebook.Replay().pipe(Effect.exit))).toContain("Table capability escaped its turn");
|
|
522
|
-
expect(yield* notebook.List()).toEqual([{ id: "captured", body: "captured", rank: 0 }]);
|
|
523
|
-
expect(yield* test.inspect(notebook.ref)).toMatchObject({ ...rowsOf(1), receipts: 3 });
|
|
524
|
-
})),
|
|
525
|
-
},
|
|
526
|
-
{
|
|
527
|
-
name: "joins owned tables across the placement group and never beyond it",
|
|
528
|
-
run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
|
|
529
|
-
const test = yield* ActorTest;
|
|
530
|
-
const other = `${test.tenant}-group`;
|
|
531
|
-
for (const tenant of [test.tenant, other]) {
|
|
532
|
-
const scoped = Actor.tenant(tenant);
|
|
533
|
-
yield* (yield* Notebook.get("g1").pipe(scoped)).Write({ id: "a", body: `a-${tenant}` });
|
|
534
|
-
yield* (yield* Notebook.get("g2").pipe(scoped)).Write({ id: "b", body: `b-${tenant}` });
|
|
535
|
-
yield* (yield* Notebook.get("g2").pipe(scoped)).Write({ id: "c", body: `c-${tenant}` });
|
|
536
|
-
const shelf = yield* Shelf.get("s").pipe(scoped);
|
|
537
|
-
yield* shelf.Label({ id: "l1", noteId: "a", label: `x-${tenant}` });
|
|
538
|
-
yield* shelf.Label({ id: "l2", noteId: "b", label: `y-${tenant}` });
|
|
539
|
-
}
|
|
540
|
-
const reader = yield* Notebook.get("g1");
|
|
541
|
-
const t = test.tenant;
|
|
542
|
-
expect(yield* reader.Catalog(false)).toEqual([
|
|
543
|
-
{ note: "a", body: `a-${t}`, label: `x-${t}` },
|
|
544
|
-
{ note: "b", body: `b-${t}`, label: `y-${t}` },
|
|
545
|
-
]);
|
|
546
|
-
const leaks = {};
|
|
547
|
-
for (const kind of Smuggle.literals) {
|
|
548
|
-
const exit = yield* reader.Smuggled(kind).pipe(Effect.exit);
|
|
549
|
-
leaks[kind] = Exit.isSuccess(exit)
|
|
550
|
-
? exit.value.filter((body) => body.includes(other))
|
|
551
|
-
: [];
|
|
552
|
-
}
|
|
553
|
-
expect(leaks).toEqual(Object.fromEntries(Smuggle.literals.map((kind) => [kind, []])));
|
|
554
|
-
const everything = yield* reader.Everything();
|
|
555
|
-
expect(everything).toContain(`a-${t}`);
|
|
556
|
-
expect(everything.filter((body) => body.includes(other))).toEqual([]);
|
|
557
|
-
expect(yield* reader.Catalog(true)).toEqual([
|
|
558
|
-
{ note: "a", body: `a-${t}`, label: `x-${t}` },
|
|
559
|
-
{ note: "b", body: `b-${t}`, label: `y-${t}` },
|
|
560
|
-
{ note: "c", body: `c-${t}`, label: null },
|
|
561
|
-
]);
|
|
562
|
-
for (const [kind, message] of [
|
|
563
|
-
["raw", "not raw SQL"],
|
|
564
|
-
["subquery", "cannot reference tables, subqueries"],
|
|
565
|
-
["rightJoin", "inner and left joins"],
|
|
566
|
-
["lock", "read-only"],
|
|
567
|
-
["unowned", "registered by an actor type"],
|
|
568
|
-
["unlisted", "registered by an actor type"],
|
|
569
|
-
["parentheses", "balance their parentheses"],
|
|
570
|
-
["param", "plain data"],
|
|
571
|
-
["wrapper", "plain data"],
|
|
572
|
-
["ownership", "ownership columns"],
|
|
573
|
-
])
|
|
574
|
-
expect(defect(yield* reader.Misgroup(kind).pipe(Effect.exit))).toContain(message);
|
|
575
|
-
})),
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
name: "keeps uncommitted owned rows invisible to a second connection",
|
|
579
|
-
requiresIndependentConnections: true,
|
|
580
|
-
run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
|
|
581
|
-
const test = yield* ActorTest;
|
|
582
|
-
const notebook = yield* Notebook.get("invisible");
|
|
583
|
-
const pause = yield* test.pauseNext("beforeCommit");
|
|
584
|
-
const writer = yield* notebook
|
|
585
|
-
.Write({ id: "pending", body: "pending" })
|
|
586
|
-
.pipe(Effect.forkChild);
|
|
587
|
-
yield* pause.reached;
|
|
588
|
-
expect(yield* notebook.Count()).toBe(0);
|
|
589
|
-
expect(yield* test.inspect(notebook.ref)).toMatchObject({ ...rowsOf(0), receipts: 0 });
|
|
590
|
-
yield* pause.release;
|
|
591
|
-
expect(yield* Fiber.join(writer)).toBe(1);
|
|
592
|
-
expect(yield* notebook.Count()).toBe(1);
|
|
593
|
-
})),
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
name: "commits equal business keys of two actors concurrently without blocking",
|
|
597
|
-
requiresIndependentConnections: true,
|
|
598
|
-
run: ({ expect, environment, fixture }) => environment.run(Effect.gen(function* () {
|
|
599
|
-
const test = yield* ActorTest;
|
|
600
|
-
const first = yield* Notebook.get("contended-a");
|
|
601
|
-
const second = yield* Notebook.get("contended-b");
|
|
602
|
-
const reached = yield* Deferred.make();
|
|
603
|
-
const release = yield* Deferred.make();
|
|
604
|
-
fixture.hold = Deferred.succeed(reached, undefined).pipe(Effect.andThen(Deferred.await(release)));
|
|
605
|
-
const holder = yield* first.WriteThenHold("same").pipe(Effect.forkChild);
|
|
606
|
-
yield* Deferred.await(reached);
|
|
607
|
-
fixture.hold = Effect.void;
|
|
608
|
-
expect(yield* second.Write({ id: "same", body: "same" }).pipe(Effect.timeout("5 seconds"))).toBe(1);
|
|
609
|
-
yield* Deferred.succeed(release, undefined);
|
|
610
|
-
yield* Fiber.join(holder);
|
|
611
|
-
expect(yield* test.inspect(first.ref)).toMatchObject({ ...rowsOf(1), receipts: 1 });
|
|
612
|
-
expect(yield* test.inspect(second.ref)).toMatchObject({ ...rowsOf(1), receipts: 1 });
|
|
613
|
-
})),
|
|
614
|
-
},
|
|
615
|
-
{
|
|
616
|
-
name: "rejects an owned-row capability used from another still-active turn",
|
|
617
|
-
requiresIndependentConnections: true,
|
|
618
|
-
run: ({ expect, environment, fixture }) => environment.run(Effect.gen(function* () {
|
|
619
|
-
const test = yield* ActorTest;
|
|
620
|
-
const owner = yield* Notebook.get("row-owner");
|
|
621
|
-
const thief = yield* Notebook.get("row-thief");
|
|
622
|
-
const reached = yield* Deferred.make();
|
|
623
|
-
const release = yield* Deferred.make();
|
|
624
|
-
fixture.hold = Deferred.succeed(reached, undefined).pipe(Effect.andThen(Deferred.await(release)));
|
|
625
|
-
const holder = yield* owner.WriteThenHold("held").pipe(Effect.forkChild);
|
|
626
|
-
yield* Deferred.await(reached);
|
|
627
|
-
fixture.hold = Effect.void;
|
|
628
|
-
const stolen = yield* thief
|
|
629
|
-
.Replay()
|
|
630
|
-
.pipe(Effect.exit, Effect.ensuring(Deferred.succeed(release, undefined)));
|
|
631
|
-
expect(defect(stolen)).toContain("Table capability escaped its turn");
|
|
632
|
-
yield* Fiber.join(holder);
|
|
633
|
-
expect(yield* owner.List()).toEqual([{ id: "held", body: "held", rank: 0 }]);
|
|
634
|
-
expect(yield* test.inspect(thief.ref)).toMatchObject({ ...rowsOf(0), receipts: 0 });
|
|
635
|
-
})),
|
|
636
|
-
},
|
|
637
|
-
{
|
|
638
|
-
name: "retries an owned-row write that times out on a real lock",
|
|
639
|
-
requiresIndependentConnections: true,
|
|
640
|
-
timeoutMs: 15_000,
|
|
641
|
-
run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
|
|
642
|
-
const test = yield* ActorTest;
|
|
643
|
-
const notebook = yield* Notebook.get("row-lock");
|
|
644
|
-
const connect = environment.connect;
|
|
645
|
-
if (connect === undefined)
|
|
646
|
-
return yield* Effect.die(new Error("backend lacks independent connections"));
|
|
647
|
-
const lock = yield* connect;
|
|
648
|
-
yield* lock.query("BEGIN");
|
|
649
|
-
yield* lock.query("LOCK TABLE conformance_notes IN EXCLUSIVE MODE");
|
|
650
|
-
const writer = yield* notebook
|
|
651
|
-
.Write({ id: "locked", body: "locked" })
|
|
652
|
-
.pipe(Effect.forkChild);
|
|
653
|
-
yield* Effect.sleep("3 seconds");
|
|
654
|
-
yield* lock.query("ROLLBACK");
|
|
655
|
-
expect(yield* Fiber.join(writer)).toBe(1);
|
|
656
|
-
expect(yield* test.inspect(notebook.ref)).toMatchObject({ ...rowsOf(1), receipts: 1 });
|
|
657
|
-
})),
|
|
658
|
-
},
|
|
659
|
-
];
|
|
660
|
-
/** The owned-table actors. */
|
|
661
|
-
export const tablesSuite = {
|
|
662
|
-
fixture: tablesFixture,
|
|
663
|
-
layer: tablesLayer,
|
|
664
|
-
};
|