@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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type Crypto, Layer, Schema } from "effect";
|
|
2
|
+
import type { SqlClient } from "effect/sql";
|
|
3
|
+
import type { layer } from "./layer.ts";
|
|
4
|
+
export declare const TypeId: unique symbol;
|
|
5
|
+
/** What `Actors.layer` provides: the actor runtime every handler layer registers with. */
|
|
6
|
+
type RuntimeServices = Layer.Success<ReturnType<typeof layer>>;
|
|
7
|
+
/**
|
|
8
|
+
* Every service a hosted app's layer may require. The host builds
|
|
9
|
+
* `Actors.layer` over its own database and provides it with the SQL client and
|
|
10
|
+
* `Crypto`, so an app never constructs a runtime and a requirement outside
|
|
11
|
+
* this union is a compile error at `App.make`.
|
|
12
|
+
*/
|
|
13
|
+
export type AppServices = RuntimeServices | SqlClient.SqlClient | Crypto.Crypto;
|
|
14
|
+
declare const AppLayerFailed_base: Schema.Class<AppLayerFailed, Schema.TaggedStruct<"AppLayerFailed", {
|
|
15
|
+
readonly cause: Schema.Defect;
|
|
16
|
+
}>, import("effect/Cause").YieldableError>;
|
|
17
|
+
/** Building the app's layer failed; `cause` is what the layer failed with. */
|
|
18
|
+
export declare class AppLayerFailed extends AppLayerFailed_base {
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* An actor definition as an app declares it: its name, which `Actors.serve`
|
|
22
|
+
* routes by, and its declared members, which `checkWorkflows` compares with
|
|
23
|
+
* stored executions before a rollout.
|
|
24
|
+
*/
|
|
25
|
+
export interface AppActor {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly api: object;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A hosted app: the actor definitions to serve and one layer that registers
|
|
31
|
+
* their handlers and builds any services they need from `AppServices` alone.
|
|
32
|
+
*/
|
|
33
|
+
export interface App {
|
|
34
|
+
readonly [TypeId]: typeof TypeId;
|
|
35
|
+
readonly actors: ReadonlyArray<AppActor>;
|
|
36
|
+
readonly layer: Layer.Layer<never, AppLayerFailed, AppServices>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Declares a hosted app, the default export of `src/app.ts`. Any handler
|
|
40
|
+
* requirement the layer leaves unprovided fails to compile here, which is why
|
|
41
|
+
* the host accepts only values this constructor branded: a layer's
|
|
42
|
+
* requirements cannot be checked at run time. A failure building the layer
|
|
43
|
+
* becomes `AppLayerFailed`, so a host that loads the app knows its error type.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* export default App.make({ actors: [Counter], layer: CounterLive })
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare const make: <E = never>(app: {
|
|
51
|
+
readonly actors: ReadonlyArray<AppActor>;
|
|
52
|
+
readonly layer: Layer.Layer<never, E, AppServices>;
|
|
53
|
+
}) => App;
|
|
54
|
+
/**
|
|
55
|
+
* Whether `value` is an app `App.make` built: branded, with actors that each
|
|
56
|
+
* have a name and an `api` object, and a layer. A loader uses it on a module's default export before providing the
|
|
57
|
+
* layer.
|
|
58
|
+
*/
|
|
59
|
+
declare const is: (value: unknown) => value is App;
|
|
60
|
+
/** Hosted app declaration and the loader's check of a module's default export. */
|
|
61
|
+
export declare const App: {
|
|
62
|
+
make: typeof make;
|
|
63
|
+
is: typeof is;
|
|
64
|
+
};
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Effect, Layer, Predicate, Schema } from "effect";
|
|
2
|
+
export const TypeId = Symbol.for("@rikalabs/akter/App");
|
|
3
|
+
/** Building the app's layer failed; `cause` is what the layer failed with. */
|
|
4
|
+
export class AppLayerFailed extends Schema.TaggedError()("AppLayerFailed", {
|
|
5
|
+
cause: Schema.Defect(),
|
|
6
|
+
}) {
|
|
7
|
+
}
|
|
8
|
+
const DeclaredActors = Schema.Array(Schema.Struct({ name: Schema.String, api: Schema.ObjectKeyword }));
|
|
9
|
+
/**
|
|
10
|
+
* Declares a hosted app, the default export of `src/app.ts`. Any handler
|
|
11
|
+
* requirement the layer leaves unprovided fails to compile here, which is why
|
|
12
|
+
* the host accepts only values this constructor branded: a layer's
|
|
13
|
+
* requirements cannot be checked at run time. A failure building the layer
|
|
14
|
+
* becomes `AppLayerFailed`, so a host that loads the app knows its error type.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* export default App.make({ actors: [Counter], layer: CounterLive })
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
const make = (app) => ({
|
|
22
|
+
[TypeId]: TypeId,
|
|
23
|
+
actors: [...app.actors],
|
|
24
|
+
layer: Layer.catch(app.layer, (cause) => Layer.effectDiscard(Effect.fail(AppLayerFailed.make({ cause })))),
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Whether `value` is an app `App.make` built: branded, with actors that each
|
|
28
|
+
* have a name and an `api` object, and a layer. A loader uses it on a module's default export before providing the
|
|
29
|
+
* layer.
|
|
30
|
+
*/
|
|
31
|
+
const is = (value) => Predicate.hasProperty(value, TypeId) &&
|
|
32
|
+
value[TypeId] === TypeId &&
|
|
33
|
+
Predicate.hasProperty(value, "actors") &&
|
|
34
|
+
Schema.is(DeclaredActors)(value.actors) &&
|
|
35
|
+
Predicate.hasProperty(value, "layer") &&
|
|
36
|
+
Layer.isLayer(value.layer);
|
|
37
|
+
/** Hosted app declaration and the loader's check of a module's default export. */
|
|
38
|
+
export const App = { make, is };
|
|
@@ -213,9 +213,19 @@ export declare const connectionHolder: (options: HolderOptions) => Effect.Effect
|
|
|
213
213
|
}, ActorError | OpenRejected, never>;
|
|
214
214
|
deliver: (message: Deliver) => Effect.Effect<Delivered>;
|
|
215
215
|
size: () => number;
|
|
216
|
+
/** The open sessions this holder holds for `tenant`: each one's actor, member and feed tags. */
|
|
217
|
+
census: (tenant: string) => {
|
|
218
|
+
ref: {
|
|
219
|
+
readonly tenant: string;
|
|
220
|
+
readonly actor: string;
|
|
221
|
+
readonly id: string;
|
|
222
|
+
};
|
|
223
|
+
member: string;
|
|
224
|
+
feed: readonly string[];
|
|
225
|
+
}[];
|
|
216
226
|
/** The clock authorization and credential expiry are measured on, in epoch milliseconds. */
|
|
217
227
|
now: Effect.Effect<number, never, never>;
|
|
218
|
-
},
|
|
228
|
+
}, never, Crypto.Crypto | Scope.Scope | SqlClient.SqlClient>;
|
|
219
229
|
/** The holder service `connectionHolder` builds. */
|
|
220
230
|
export type Holder = Effect.Success<ReturnType<typeof connectionHolder>>;
|
|
221
231
|
export type { Scope };
|
|
@@ -3,7 +3,7 @@ import { SqlClient } from "effect/sql";
|
|
|
3
3
|
import { ActorError, ActorUnavailable, SessionEnded, Unauthorized } from "../../errors/actor.js";
|
|
4
4
|
import { connectionSecret } from "../../identity/connection.js";
|
|
5
5
|
import { FrameworkClock } from "../turn/admission.js";
|
|
6
|
-
import {
|
|
6
|
+
import { locatedHere, shardRanges } from "../database/shards.js";
|
|
7
7
|
import { ClientMessage, HolderItem, isWatchMember, watchedQuery, } from "./protocol.js";
|
|
8
8
|
/** The largest encoded inbound frame or open params a holder accepts. */
|
|
9
9
|
export const MAX_INBOUND_BYTES = 65_536;
|
|
@@ -87,7 +87,7 @@ const ended = (cause, resync, retryAfterMs) => ActorError.make({
|
|
|
87
87
|
*/
|
|
88
88
|
export const connectionHolder = Effect.fnUntraced(function* (options) {
|
|
89
89
|
const sql = yield* SqlClient.SqlClient;
|
|
90
|
-
const
|
|
90
|
+
const located = yield* locatedHere;
|
|
91
91
|
const crypto = yield* Crypto.Crypto;
|
|
92
92
|
const clock = yield* FrameworkClock;
|
|
93
93
|
const scope = yield* Effect.scope;
|
|
@@ -129,7 +129,7 @@ export const connectionHolder = Effect.fnUntraced(function* (options) {
|
|
|
129
129
|
const deleteRow = (connection) => sql `DELETE FROM actor_connections WHERE routing_key = ${connection.type.routingKey(connection.ref)}
|
|
130
130
|
AND tenant_id = ${connection.ref.tenant} AND actor_type = ${connection.ref.actor}
|
|
131
131
|
AND actor_id = ${connection.ref.id} AND connection_id = ${connection.id}
|
|
132
|
-
AND holder_epoch = ${options.transport().epoch}`.pipe(Effect.ignore);
|
|
132
|
+
AND holder_epoch = ${options.transport().epoch}`.pipe(located, Effect.ignore);
|
|
133
133
|
const release = (connection) => {
|
|
134
134
|
heldBytes -= connection.outBytes + connection.inBytes + (connection.resync?.deferredBytes ?? 0);
|
|
135
135
|
if (connection.resync !== undefined)
|
|
@@ -524,7 +524,7 @@ export const connectionHolder = Effect.fnUntraced(function* (options) {
|
|
|
524
524
|
const liveness = (at) => Effect.gen(function* () {
|
|
525
525
|
const transport = options.transport();
|
|
526
526
|
const checked = [...held.values()].filter((connection) => connection.open);
|
|
527
|
-
const rows = (yield* Effect.forEach(
|
|
527
|
+
const rows = (yield* Effect.forEach(yield* shardRanges, (range) => sql `
|
|
528
528
|
SELECT c.connection_id
|
|
529
529
|
FROM generate_series(${range.first}::int, ${range.last}::int) AS b(bucket)
|
|
530
530
|
CROSS JOIN LATERAL (
|
|
@@ -810,6 +810,10 @@ export const connectionHolder = Effect.fnUntraced(function* (options) {
|
|
|
810
810
|
open,
|
|
811
811
|
deliver,
|
|
812
812
|
size: () => held.size,
|
|
813
|
+
/** The open sessions this holder holds for `tenant`: each one's actor, member and feed tags. */
|
|
814
|
+
census: (tenant) => [...held.values()].flatMap((connection) => connection.open && !connection.ended && connection.ref.tenant === tenant
|
|
815
|
+
? [{ ref: connection.ref, member: connection.member, feed: connection.feed ?? [] }]
|
|
816
|
+
: []),
|
|
813
817
|
/** The clock authorization and credential expiry are measured on, in epoch milliseconds. */
|
|
814
818
|
now,
|
|
815
819
|
};
|
|
@@ -45,14 +45,14 @@ export declare const streamSubscriptions: ({ registration, activations, transpor
|
|
|
45
45
|
readonly input: string;
|
|
46
46
|
readonly authorizedUntil: number;
|
|
47
47
|
}) => Stream.Stream<{
|
|
48
|
+
readonly _tag: "Started";
|
|
49
|
+
readonly owner: string;
|
|
50
|
+
readonly ownerEpoch: string;
|
|
51
|
+
} | {
|
|
48
52
|
readonly _tag: "Done";
|
|
49
53
|
} | {
|
|
50
54
|
readonly _tag: "Element";
|
|
51
55
|
readonly value: string;
|
|
52
|
-
} | {
|
|
53
|
-
readonly _tag: "Started";
|
|
54
|
-
readonly owner: string;
|
|
55
|
-
readonly ownerEpoch: string;
|
|
56
56
|
}, ActorError | {
|
|
57
57
|
readonly _tag: "StreamFailed";
|
|
58
58
|
readonly value: string;
|
|
@@ -3,6 +3,7 @@ import { sha256Bytes, sha256Hasher } from "../../identity/digest.js";
|
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import { ContentTooLarge } from "../../errors/content.js";
|
|
5
5
|
import { tenantRoutingKey } from "../storage/codec.js";
|
|
6
|
+
import { forEachRange, withinRange } from "../database/shards.js";
|
|
6
7
|
import { textArray } from "../turn/outbox.js";
|
|
7
8
|
import { GRANT_LIFETIME_MS } from "./grant.js";
|
|
8
9
|
/** Bytes one content may hold. */
|
|
@@ -157,9 +158,9 @@ export const tenantContent = (settings) => {
|
|
|
157
158
|
const key = tenantRoutingKey(tenant);
|
|
158
159
|
yield* row.inserted
|
|
159
160
|
? sql `UPDATE tenant_content_chunks SET hash = ${hash}
|
|
160
|
-
|
|
161
|
+
WHERE routing_key = ${key} AND tenant_id = ${tenant} AND hash = ${pending}`
|
|
161
162
|
: sql `DELETE FROM tenant_content_chunks
|
|
162
|
-
|
|
163
|
+
WHERE routing_key = ${key} AND tenant_id = ${tenant} AND hash = ${pending}`;
|
|
163
164
|
return { hash, size, expiresAt: row.expiresAt };
|
|
164
165
|
}));
|
|
165
166
|
return yield* refOf(tenant, stored.hash, stored.size, stored.expiresAt);
|
|
@@ -233,16 +234,17 @@ export const tenantContent = (settings) => {
|
|
|
233
234
|
const sweep = Effect.fnUntraced(function* (force) {
|
|
234
235
|
const sql = yield* SqlClient.SqlClient;
|
|
235
236
|
const started = yield* now;
|
|
236
|
-
const tenants = force
|
|
237
|
-
?
|
|
238
|
-
SELECT routing_key::text AS routing_key, tenant_id FROM tenant_content_sweeps
|
|
239
|
-
|
|
237
|
+
const tenants = (yield* forEachRange((range) => force
|
|
238
|
+
? sql `
|
|
239
|
+
SELECT routing_key::text AS routing_key, tenant_id FROM tenant_content_sweeps
|
|
240
|
+
${withinRange({ sql, range, column: "routing_key", keyword: "WHERE" })}`
|
|
241
|
+
: sql `
|
|
240
242
|
UPDATE tenant_content_sweeps SET swept_at_ms = ${started}
|
|
241
243
|
WHERE (routing_key, tenant_id) IN (
|
|
242
244
|
SELECT routing_key, tenant_id FROM tenant_content_sweeps
|
|
243
|
-
WHERE swept_at_ms <= ${started - SWEEP_INTERVAL_MS}
|
|
245
|
+
WHERE swept_at_ms <= ${started - SWEEP_INTERVAL_MS} ${withinRange({ sql, range, column: "routing_key" })}
|
|
244
246
|
LIMIT ${SWEEP_TENANTS} FOR UPDATE SKIP LOCKED)
|
|
245
|
-
RETURNING routing_key::text AS routing_key, tenant_id
|
|
247
|
+
RETURNING routing_key::text AS routing_key, tenant_id`)).flat();
|
|
246
248
|
const [longest] = yield* sql `
|
|
247
249
|
SELECT COALESCE(max(turn_ms), 0)::float8 AS turn_ms FROM actor_content_types`;
|
|
248
250
|
const turnMs = longest.turn_ms;
|
|
@@ -264,10 +266,12 @@ export const tenantContent = (settings) => {
|
|
|
264
266
|
break;
|
|
265
267
|
const last = candidates.at(-1);
|
|
266
268
|
after = { granted: Number(last.granted_until_ms), hash: last.hash };
|
|
267
|
-
const referenced = new Set((yield* sql `
|
|
269
|
+
const referenced = new Set((yield* forEachRange((range) => sql `
|
|
268
270
|
SELECT DISTINCT hash FROM actor_content_refs
|
|
269
|
-
WHERE tenant_id = ${tenant}
|
|
270
|
-
AND hash = ANY(${textArray({ sql, values: candidates.map((row) => row.hash) })})`)
|
|
271
|
+
WHERE tenant_id = ${tenant} ${withinRange({ sql, range, column: "routing_key" })}
|
|
272
|
+
AND hash = ANY(${textArray({ sql, values: candidates.map((row) => row.hash) })})`))
|
|
273
|
+
.flat()
|
|
274
|
+
.map((row) => row.hash));
|
|
271
275
|
yield* hooks.at("afterReferenceScan");
|
|
272
276
|
const unreferenced = candidates
|
|
273
277
|
.map((row) => row.hash)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PgClient } from "@effect/sql-pg";
|
|
2
|
-
import {
|
|
2
|
+
import type { PgConnection } from "@effect/sql-pg";
|
|
3
|
+
import { Effect, Scope } from "effect";
|
|
3
4
|
import { SqlClient, SqlError, Statement } from "effect/sql";
|
|
4
5
|
/** Extra checkouts each pool holds beyond its connections before refusing further work. */
|
|
5
6
|
export declare const POOL_WAITERS = 64;
|
|
@@ -16,28 +17,13 @@ export declare const retryPoolRefusal: <A, E, R>(effect: Effect.Effect<A, E, R>)
|
|
|
16
17
|
* transactions, streams and listeners; bounding only command dispatch would
|
|
17
18
|
* leave queries and background callers able to grow the pool's waiter list.
|
|
18
19
|
* A refused checkout sent no statement.
|
|
19
|
-
*
|
|
20
|
-
* A checkout passes three stages in order: the bounded admission slot,
|
|
21
|
-
* refused at once past the limit; a first-come, first-served gate with one
|
|
22
|
-
* slot per connection, so a caller never waits behind one that asked after
|
|
23
|
-
* it, which Effect's pool allows because it hands a freed connection to
|
|
24
|
-
* whichever fiber asks next; then the pool, which therefore never has a
|
|
25
|
-
* waiter of its own. Both slots belong to the checkout's scope, or to the
|
|
26
|
-
* borrowed statement, and return with the connection, so cancellation never
|
|
27
|
-
* leaks either. A statement inside a transaction reuses its connection and
|
|
28
|
-
* takes neither.
|
|
29
|
-
*
|
|
30
|
-
* Apart from the gates it is `PgClient.make`: the same compiler, transforms,
|
|
31
|
-
* span attributes, commit check, savepoint release, JSON fragments, and
|
|
32
|
-
* notifications, which use a checkout of their own rather than the caller's
|
|
33
|
-
* transaction.
|
|
34
20
|
*/
|
|
35
21
|
export declare const boundedPool: (options: PgClient.PgPoolConfig) => Effect.Effect<SqlClient.SqlClient & {
|
|
36
22
|
"~@effect/sql-pg/PgClient": "~@effect/sql-pg/PgClient";
|
|
37
23
|
config: PgClient.PgPoolConfig;
|
|
38
24
|
json: (value: unknown) => Statement.Fragment;
|
|
39
|
-
listen: (channel: string) => Effect.Effect<import("effect/Queue").Dequeue<
|
|
25
|
+
listen: (channel: string) => Effect.Effect<import("effect/Queue").Dequeue<PgConnection.Notification, SqlError.SqlError>, SqlError.SqlError, Scope.Scope>;
|
|
40
26
|
notify: (channel: string, payload: string) => Effect.Effect<void, SqlError.SqlError, never>;
|
|
41
|
-
}, SqlError.SqlError, import("effect/reactivity/Reactivity").Reactivity |
|
|
27
|
+
}, SqlError.SqlError, import("effect/reactivity/Reactivity").Reactivity | Scope.Scope>;
|
|
42
28
|
/** Provides the bounded primary/off-turn client without changing its connection configuration. */
|
|
43
29
|
export declare const boundedLayer: (options: PgClient.PgPoolConfig) => import("effect/Layer").Layer<PgClient.PgClient | SqlClient.SqlClient, SqlError.SqlError, never>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PgClient, PgPool } from "@effect/sql-pg";
|
|
2
|
-
import { Duration, Effect, Schedule, Schema } from "effect";
|
|
2
|
+
import { Duration, Effect, Schedule, Schema, Scope } from "effect";
|
|
3
3
|
import { SqlClient, SqlError, Statement } from "effect/sql";
|
|
4
4
|
import { admissionLimit, isOverloaded } from "../admission.js";
|
|
5
5
|
import { ActorError } from "../../errors/actor.js";
|
|
@@ -33,13 +33,6 @@ const commit = (connection) => Effect.flatMap(connection.executeRaw("COMMIT", []
|
|
|
33
33
|
const PgJson = Statement.custom("PgJson");
|
|
34
34
|
const direct = (statement) => statement;
|
|
35
35
|
/**
|
|
36
|
-
* The native Postgres client with at most 64 queued checkouts beyond its
|
|
37
|
-
* connection capacity, handing its connections out first come, first served.
|
|
38
|
-
* All SQL entry points share admission, including reservations,
|
|
39
|
-
* transactions, streams and listeners; bounding only command dispatch would
|
|
40
|
-
* leave queries and background callers able to grow the pool's waiter list.
|
|
41
|
-
* A refused checkout sent no statement.
|
|
42
|
-
*
|
|
43
36
|
* A checkout passes three stages in order: the bounded admission slot,
|
|
44
37
|
* refused at once past the limit; a first-come, first-served gate with one
|
|
45
38
|
* slot per connection, so a caller never waits behind one that asked after
|
|
@@ -47,28 +40,38 @@ const direct = (statement) => statement;
|
|
|
47
40
|
* whichever fiber asks next; then the pool, which therefore never has a
|
|
48
41
|
* waiter of its own. Both slots belong to the checkout's scope, or to the
|
|
49
42
|
* borrowed statement, and return with the connection, so cancellation never
|
|
50
|
-
* leaks either.
|
|
51
|
-
* takes neither.
|
|
52
|
-
*
|
|
53
|
-
* Apart from the gates it is `PgClient.make`: the same compiler, transforms,
|
|
54
|
-
* span attributes, commit check, savepoint release, JSON fragments, and
|
|
55
|
-
* notifications, which use a checkout of their own rather than the caller's
|
|
56
|
-
* transaction.
|
|
43
|
+
* leaks either.
|
|
57
44
|
*/
|
|
58
|
-
|
|
45
|
+
const boundedSessions = Effect.fnUntraced(function* (options) {
|
|
59
46
|
const pool = yield* PgPool.make(options);
|
|
60
47
|
const slots = options.maxConnections ?? 10;
|
|
61
48
|
const admission = admissionLimit({ limit: slots + POOL_WAITERS, wait: Duration.zero });
|
|
62
49
|
const fair = fairGate(slots);
|
|
63
50
|
const enter = Effect.andThen(admission.take.pipe(Effect.mapError(poolRefusal)), fair.take);
|
|
64
51
|
const connection = (session) => asSqlConnection({ connection: session, send: direct });
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
borrower: (use) => admission
|
|
52
|
+
return {
|
|
53
|
+
acquire: Effect.andThen(enter, Effect.map(pool.get, connection)),
|
|
54
|
+
reserve: Effect.andThen(enter, Effect.map(pool.reserve, connection)),
|
|
55
|
+
borrow: (use) => admission
|
|
70
56
|
.admit(fair.use(pool.use((session) => use(connection(session)))))
|
|
71
57
|
.pipe(Effect.mapError((error) => (Schema.is(ActorError)(error) ? poolRefusal(error) : error))),
|
|
58
|
+
listen: (channel) => Effect.andThen(enter, Effect.flatMap(pool.reserve, (session) => session.listen(channel))),
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* `PgClient.make` over `sessions`: the same compiler, transforms, span
|
|
63
|
+
* attributes, commit check, savepoint release, JSON fragments, and
|
|
64
|
+
* notifications, which use a checkout of their own rather than the caller's
|
|
65
|
+
* transaction. `sessions` is read at each checkout, so one client can reach
|
|
66
|
+
* a different pool per caller. A statement inside a transaction reuses its
|
|
67
|
+
* connection and takes no checkout.
|
|
68
|
+
*/
|
|
69
|
+
const clientOver = Effect.fnUntraced(function* (options, sessions) {
|
|
70
|
+
const acquire = Effect.flatMap(sessions, (pool) => pool.acquire);
|
|
71
|
+
const sql = yield* SqlClient.make({
|
|
72
|
+
acquirer: acquire,
|
|
73
|
+
transactionAcquirer: Effect.flatMap(sessions, (pool) => pool.reserve),
|
|
74
|
+
borrower: (use) => Effect.flatMap(sessions, (pool) => pool.borrow(use)),
|
|
72
75
|
compiler: PgClient.makeCompiler(options.transformQueryNames, options.transformJson),
|
|
73
76
|
spanAttributes: [
|
|
74
77
|
...(options.spanAttributes === undefined ? [] : Object.entries(options.spanAttributes)),
|
|
@@ -88,7 +91,7 @@ export const boundedPool = Effect.fnUntraced(function* (options) {
|
|
|
88
91
|
[PgClient.TypeId]: PgClient.TypeId,
|
|
89
92
|
config: options,
|
|
90
93
|
json: ((value) => Statement.fragment([PgJson(value)])),
|
|
91
|
-
listen: (channel) => Effect.
|
|
94
|
+
listen: (channel) => Effect.flatMap(sessions, (pool) => pool.listen(channel)),
|
|
92
95
|
notify: (channel, payload) => {
|
|
93
96
|
if (new TextEncoder().encode(channel).byteLength > 63) {
|
|
94
97
|
const message = "PostgreSQL channel names must not exceed 63 UTF-8 bytes";
|
|
@@ -104,5 +107,16 @@ export const boundedPool = Effect.fnUntraced(function* (options) {
|
|
|
104
107
|
},
|
|
105
108
|
});
|
|
106
109
|
});
|
|
110
|
+
/**
|
|
111
|
+
* The native Postgres client with at most 64 queued checkouts beyond its
|
|
112
|
+
* connection capacity, handing its connections out first come, first served.
|
|
113
|
+
* All SQL entry points share admission, including reservations,
|
|
114
|
+
* transactions, streams and listeners; bounding only command dispatch would
|
|
115
|
+
* leave queries and background callers able to grow the pool's waiter list.
|
|
116
|
+
* A refused checkout sent no statement.
|
|
117
|
+
*/
|
|
118
|
+
export const boundedPool = Effect.fnUntraced(function* (options) {
|
|
119
|
+
return yield* clientOver(options, Effect.succeed(yield* boundedSessions(options)));
|
|
120
|
+
});
|
|
107
121
|
/** Provides the bounded primary/off-turn client without changing its connection configuration. */
|
|
108
122
|
export const boundedLayer = (options) => PgClient.layerFrom(boundedPool(options));
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { PgClient } from "@effect/sql-pg";
|
|
2
|
-
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import { Context, Effect, Layer, type Scope } from "effect";
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
|
-
/** The deployment's
|
|
4
|
+
/** The deployment's coordination database, shared by every runner. */
|
|
5
5
|
export declare const Coordination: Context.Reference<SqlClient.SqlClient | undefined>;
|
|
6
|
+
/** Deployment registries belong to the data database. */
|
|
7
|
+
export declare const registry: Context.Service<SqlClient.SqlClient, SqlClient.SqlClient>;
|
|
8
|
+
/** The client deployment-wide locks are taken on: the designated coordination pool, else `registry`. */
|
|
9
|
+
export declare const authority: Effect.Effect<SqlClient.SqlClient, never, SqlClient.SqlClient>;
|
|
10
|
+
/** Holds startup coordination before even the history table is created, on one leased session. */
|
|
11
|
+
export declare const withMigrationCoordination: <A, E>(effect: Effect.Effect<A, E, SqlClient.SqlClient | Scope.Scope>) => Effect.Effect<A, E | import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
6
12
|
/** Supplies an independent client so coordination transactions never borrow a data transaction's connection. */
|
|
7
13
|
export declare const coordinationLayer: (options: PgClient.PgPoolConfig | undefined) => Layer.Layer<never, import("effect/sql/SqlError").SqlError, never>;
|
|
8
14
|
/**
|
|
@@ -12,8 +18,11 @@ export declare const coordinationLayer: (options: PgClient.PgPoolConfig | undefi
|
|
|
12
18
|
* Row identities are retained so deleting an idle resource cannot split its lock.
|
|
13
19
|
* The local fence has its own namespace because independent clients may point
|
|
14
20
|
* to the same database and must not wait on their own authority transaction.
|
|
21
|
+
* Work that writes no actor data, such as accepting a deployment, passes
|
|
22
|
+
* `writesData: false` and holds no data transaction.
|
|
15
23
|
*/
|
|
16
|
-
export declare const coordinated: <A, E, R>({ resource, work, }: {
|
|
24
|
+
export declare const coordinated: <A, E, R>({ resource, work, writesData, }: {
|
|
17
25
|
readonly resource: string;
|
|
18
26
|
readonly work: Effect.Effect<A, E, R>;
|
|
27
|
+
readonly writesData?: boolean;
|
|
19
28
|
}) => Effect.Effect<A, E | import("effect/sql/SqlError").SqlError, R | SqlClient.SqlClient>;
|
|
@@ -3,10 +3,12 @@ import { Context, Effect, Layer } from "effect";
|
|
|
3
3
|
import { Reactivity } from "effect/reactivity";
|
|
4
4
|
import { Migrator, SqlClient } from "effect/sql";
|
|
5
5
|
import { boundedPool } from "./bounded.js";
|
|
6
|
-
|
|
7
|
-
import { MigrationBarrier, MigrationBoundary, withMigrationCoordination, } from "./neki/migrations.js";
|
|
8
|
-
/** The deployment's unsharded authority, shared by every runner regardless of its data shard. */
|
|
6
|
+
/** The deployment's coordination database, shared by every runner. */
|
|
9
7
|
export const Coordination = Context.Reference("@rikalabs/akter/runtime/database/coordination/Coordination", { defaultValue: () => undefined });
|
|
8
|
+
/** Deployment registries belong to the data database. */
|
|
9
|
+
export const registry = SqlClient.SqlClient;
|
|
10
|
+
/** The client deployment-wide locks are taken on: the designated coordination pool, else `registry`. */
|
|
11
|
+
export const authority = Coordination.pipe(Effect.filterOrElse((designated) => designated !== undefined, () => registry));
|
|
10
12
|
/** Creates the transaction-owned coordination rows on the authoritative database. */
|
|
11
13
|
const prepareCoordination = Effect.gen(function* () {
|
|
12
14
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -14,41 +16,30 @@ const prepareCoordination = Effect.gen(function* () {
|
|
|
14
16
|
resource text PRIMARY KEY
|
|
15
17
|
)`;
|
|
16
18
|
});
|
|
17
|
-
/**
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const connection = yield*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
[
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
yield* boundary(`coordination:${name}:ddl`);
|
|
33
|
-
yield* barrier(connection);
|
|
34
|
-
yield* boundary(`coordination:${name}:propagated`);
|
|
35
|
-
}
|
|
36
|
-
yield* sql `INSERT INTO actor_coordination_migrations (migration_id, name)
|
|
37
|
-
VALUES (1, 'coordination') ON CONFLICT (migration_id) DO NOTHING`;
|
|
38
|
-
yield* boundary("coordination:recorded");
|
|
39
|
-
});
|
|
19
|
+
/** Holds startup coordination before even the history table is created, on one leased session. */
|
|
20
|
+
export const withMigrationCoordination = (effect) => Effect.scoped(Effect.gen(function* () {
|
|
21
|
+
const source = yield* SqlClient.SqlClient;
|
|
22
|
+
const connection = yield* source.reserve;
|
|
23
|
+
yield* Effect.acquireRelease(connection.execute("SELECT pg_advisory_lock(1935764837, 487)", [], undefined), () => connection
|
|
24
|
+
.execute("SELECT pg_advisory_unlock(1935764837, 487)", [], undefined)
|
|
25
|
+
.pipe(Effect.orDie));
|
|
26
|
+
const reactivity = yield* Reactivity.make;
|
|
27
|
+
const client = yield* SqlClient.make({
|
|
28
|
+
acquirer: Effect.succeed(connection),
|
|
29
|
+
compiler: PgClient.makeCompiler(),
|
|
30
|
+
spanAttributes: [],
|
|
31
|
+
}).pipe(Effect.provideService(Reactivity.Reactivity, reactivity));
|
|
32
|
+
return yield* Effect.provideService(effect, SqlClient.SqlClient, client);
|
|
33
|
+
}));
|
|
40
34
|
/** Supplies an independent client so coordination transactions never borrow a data transaction's connection. */
|
|
41
35
|
export const coordinationLayer = (options) => Layer.effect(Coordination, Effect.gen(function* () {
|
|
42
36
|
if (options === undefined)
|
|
43
37
|
return undefined;
|
|
44
38
|
const sql = yield* boundedPool(options);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
table: "actor_coordination_migrations",
|
|
50
|
-
loader: Migrator.fromRecord({ "0001_coordination": prepareCoordination }),
|
|
51
|
-
})).pipe(Effect.provideService(SqlClient.SqlClient, sql), Effect.orDie);
|
|
39
|
+
yield* withMigrationCoordination(Migrator.make({})({
|
|
40
|
+
table: "actor_coordination_migrations",
|
|
41
|
+
loader: Migrator.fromRecord({ "0001_coordination": prepareCoordination }),
|
|
42
|
+
})).pipe(Effect.provideService(SqlClient.SqlClient, sql), Effect.orDie);
|
|
52
43
|
return sql;
|
|
53
44
|
})).pipe(Layer.provide(Reactivity.layer));
|
|
54
45
|
/**
|
|
@@ -58,11 +49,13 @@ export const coordinationLayer = (options) => Layer.effect(Coordination, Effect.
|
|
|
58
49
|
* Row identities are retained so deleting an idle resource cannot split its lock.
|
|
59
50
|
* The local fence has its own namespace because independent clients may point
|
|
60
51
|
* to the same database and must not wait on their own authority transaction.
|
|
52
|
+
* Work that writes no actor data, such as accepting a deployment, passes
|
|
53
|
+
* `writesData: false` and holds no data transaction.
|
|
61
54
|
*/
|
|
62
|
-
export const coordinated = ({ resource, work, }) => Effect.gen(function* () {
|
|
55
|
+
export const coordinated = ({ resource, work, writesData = true, }) => Effect.gen(function* () {
|
|
63
56
|
const data = yield* SqlClient.SqlClient;
|
|
64
|
-
const sql =
|
|
65
|
-
const guarded = sql === data
|
|
57
|
+
const sql = yield* authority;
|
|
58
|
+
const guarded = sql === data || !writesData
|
|
66
59
|
? work
|
|
67
60
|
: data.withTransaction(data `INSERT INTO actor_coordination (resource) VALUES (${`local/${resource}`})
|
|
68
61
|
ON CONFLICT (resource) DO UPDATE SET resource = EXCLUDED.resource`.pipe(Effect.andThen(work), Effect.tap(() => sql `SELECT 1`)));
|