@rikalabs/akter 0.1.0-alpha.0 → 0.1.0-alpha.1
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 +5 -1
- package/dist/actor/descriptor.d.ts +1 -1
- package/dist/members/payload.d.ts +1 -1
- 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/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/layer.d.ts +3 -3
- package/dist/runtime/layer.js +1 -1
- package/dist/runtime/payloads/versions.d.ts +4 -4
- package/dist/runtime/payloads/versions.js +4 -4
- package/dist/runtime/telemetry/spans.d.ts +1 -1
- package/dist/runtime/telemetry/spans.js +1 -1
- package/dist/runtime/turn/rows.js +4 -4
- package/dist/runtime/workflows/compatibility.d.ts +1 -1
- package/dist/runtime/workflows/compatibility.js +1 -1
- package/dist/tables/owned.d.ts +1 -1
- package/dist/tables/owned.js +1 -1
- package/dist/testing/actor-test.d.ts +1 -1
- package/dist/testing/conformance/adoption.js +1 -1
- package/dist/testing/conformance/fleet.d.ts +1 -1
- package/dist/testing/conformance/fleet.js +3 -3
- package/dist/testing/conformance/payload-migrations.js +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.0-alpha.
|
|
3
|
+
## 0.1.0-alpha.1 (2026-10-04)
|
|
4
|
+
|
|
5
|
+
The first version published by the release workflow, through npm trusted publishing with provenance. The framework code is unchanged from `0.1.0-alpha.0`.
|
|
6
|
+
|
|
7
|
+
## 0.1.0-alpha.0 (2026-10-04)
|
|
4
8
|
|
|
5
9
|
The first published build of Akter, on the `alpha` npm dist-tag. It is an **alpha for a single runner**: run one runtime process per database, and expect APIs and stored formats to change between alphas without a migration path.
|
|
6
10
|
|
|
@@ -143,7 +143,7 @@ export interface Descriptor {
|
|
|
143
143
|
readonly registeredSubscriptions: ReadonlyArray<RegisteredSubscription>;
|
|
144
144
|
/** Event and job chains this actor writes (or only reads, with `writes` false). */
|
|
145
145
|
readonly payloads: (writes: boolean) => ReadonlyArray<PayloadDeclaration>;
|
|
146
|
-
/** What `
|
|
146
|
+
/** What `akter payloads check` and `clear` read. */
|
|
147
147
|
readonly definitionPayloads: DefinitionPayloads;
|
|
148
148
|
readonly served: ServedDefinition;
|
|
149
149
|
/** The handle that reaches `internal` commands too, for test harnesses. */
|
|
@@ -84,7 +84,7 @@ export interface PayloadDeclaration {
|
|
|
84
84
|
/** Whether this layer writes new values of the class, so it records and heartbeats its write version. */
|
|
85
85
|
readonly writes: boolean;
|
|
86
86
|
}
|
|
87
|
-
/** What `
|
|
87
|
+
/** What `akter payloads check` and `clear` need of an actor definition. */
|
|
88
88
|
export interface DefinitionPayloads {
|
|
89
89
|
readonly declarations: ReadonlyArray<PayloadDeclaration>;
|
|
90
90
|
readonly keepEventsMs: number;
|
|
@@ -42,7 +42,7 @@ const backfillTable = Effect.fnUntraced(function* (target, batch) {
|
|
|
42
42
|
WHERE table_schema = ${target.schema} AND table_name = ${target.table}`;
|
|
43
43
|
if (adoption === undefined)
|
|
44
44
|
return yield* AdoptionRefused.make({
|
|
45
|
-
message: `${name} is not observed; run
|
|
45
|
+
message: `${name} is not observed; run akter adopt observe ${target.table} first`,
|
|
46
46
|
});
|
|
47
47
|
if (adoption.mode === "enforce")
|
|
48
48
|
return yield* AdoptionRefused.make({
|
|
@@ -53,7 +53,7 @@ const enforcementRefusals = Effect.fnUntraced(function* (target, options) {
|
|
|
53
53
|
OR ${identifier(target.tenantColumn)}::text = '' OR ${identifier(target.actorColumn)}::text = '')::text AS unowned
|
|
54
54
|
FROM ${table}`);
|
|
55
55
|
if (Number(counts.unfilled) > 0)
|
|
56
|
-
refusals.push(`${counts.unfilled} rows of ${name} have no routing_key; run
|
|
56
|
+
refusals.push(`${counts.unfilled} rows of ${name} have no routing_key; run akter adopt backfill ${target.table}`);
|
|
57
57
|
if (Number(counts.unowned) > 0)
|
|
58
58
|
refusals.push(`${counts.unowned} rows of ${name} have a NULL or empty ${target.tenantColumn} or ${target.actorColumn}, so no actor owns them`);
|
|
59
59
|
const recent = yield* sql `
|
|
@@ -152,7 +152,7 @@ export const enforceAdoption = Effect.fnUntraced(function* (actors, options) {
|
|
|
152
152
|
WHERE table_schema = ${target.schema} AND table_name = ${target.table} FOR UPDATE`;
|
|
153
153
|
if (adoption === undefined)
|
|
154
154
|
return yield* AdoptionRefused.make({
|
|
155
|
-
message: `${name} is not observed; run
|
|
155
|
+
message: `${name} is not observed; run akter adopt observe ${target.table} first`,
|
|
156
156
|
});
|
|
157
157
|
if (adoption.mode === "enforce")
|
|
158
158
|
return yield* AdoptionRefused.make({ message: `${name} is already enforced` });
|
|
@@ -66,7 +66,7 @@ export const observeAdoption = Effect.fnUntraced(function* (actors, only, lockTi
|
|
|
66
66
|
});
|
|
67
67
|
if (existing?.mode === "enforce")
|
|
68
68
|
return yield* AdoptionRefused.make({
|
|
69
|
-
message: `${name} is enforced; run
|
|
69
|
+
message: `${name} is enforced; run akter adopt release ${target.table} --to observe first`,
|
|
70
70
|
});
|
|
71
71
|
const [column] = yield* sql `
|
|
72
72
|
SELECT t.typname AS type FROM pg_attribute a
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
3
|
import { type AdoptionTarget } from "./target.ts";
|
|
4
|
-
/** What `
|
|
4
|
+
/** What `akter adopt plan` reports for one table; it changes nothing. */
|
|
5
5
|
export interface AdoptionPlan {
|
|
6
6
|
readonly table: string;
|
|
7
7
|
readonly actor: string;
|
|
@@ -32,7 +32,7 @@ export const checkEnforcedTable = Effect.fnUntraced(function* ({ target, writerR
|
|
|
32
32
|
]) {
|
|
33
33
|
const found = triggers.find((trigger) => trigger.name === name);
|
|
34
34
|
if (found === undefined)
|
|
35
|
-
return yield* refuse(`is enforced but its guard trigger ${name} is missing; run
|
|
35
|
+
return yield* refuse(`is enforced but its guard trigger ${name} is missing; run akter adopt release ${target.table} --to observe and enforce it again`);
|
|
36
36
|
if (found.enabled !== "A")
|
|
37
37
|
return yield* refuse(`is enforced but its guard trigger ${name} is not enabled always; run ALTER TABLE ${target.table} ENABLE ALWAYS TRIGGER ${name}`);
|
|
38
38
|
if (found.function !== "actor_adoption_guard" || found.type !== type)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
|
-
/** One adopted table as `
|
|
3
|
+
/** One adopted table as `akter adopt status` reports it. */
|
|
4
4
|
export interface AdoptionStatus {
|
|
5
5
|
readonly table: string;
|
|
6
6
|
readonly actor: string;
|
|
@@ -54,9 +54,9 @@ export const checkFleet = Effect.fnUntraced(function* (views, tenantRole) {
|
|
|
54
54
|
if (table === undefined)
|
|
55
55
|
return yield* refuse(`${view.name} reads ${source}, which does not exist; apply its drizzle-kit migration`);
|
|
56
56
|
if (!table.published)
|
|
57
|
-
return yield* refuse(`${source} is not in publication ${FLEET_PUBLICATION}; run
|
|
57
|
+
return yield* refuse(`${source} is not in publication ${FLEET_PUBLICATION}; run akter fleet setup`);
|
|
58
58
|
if (table.identity !== "f")
|
|
59
|
-
return yield* refuse(`${source} does not have full replica identity; run
|
|
59
|
+
return yield* refuse(`${source} does not have full replica identity; run akter fleet setup (ALTER TABLE ${source} REPLICA IDENTITY FULL)`);
|
|
60
60
|
const leading = [view.routingColumn, view.tenantColumn, ...view.groupColumns];
|
|
61
61
|
const indexes = yield* sql `
|
|
62
62
|
SELECT array(
|
|
@@ -88,9 +88,9 @@ export const checkFleet = Effect.fnUntraced(function* (views, tenantRole) {
|
|
|
88
88
|
SELECT wal_status FROM pg_replication_slots
|
|
89
89
|
WHERE slot_name = ${FLEET_SLOT} AND database = current_database() AND slot_type = 'logical'`;
|
|
90
90
|
if (slot === undefined)
|
|
91
|
-
return yield* refuse(`replication slot ${FLEET_SLOT} does not exist; run
|
|
91
|
+
return yield* refuse(`replication slot ${FLEET_SLOT} does not exist; run akter fleet setup`);
|
|
92
92
|
if (slot.wal_status === "lost")
|
|
93
|
-
return yield* refuse(`replication slot ${FLEET_SLOT} is lost; run
|
|
93
|
+
return yield* refuse(`replication slot ${FLEET_SLOT} is lost; run akter fleet setup to recreate it, and every view rebuilds`);
|
|
94
94
|
const at = yield* Clock.currentTimeMillis;
|
|
95
95
|
for (const { view, sourceSchema } of resolved) {
|
|
96
96
|
yield* sql `INSERT INTO actor_fleet_views (view_name, source_schema, source_table, definition_hash,
|
|
@@ -31,7 +31,7 @@ export interface ResolvedView {
|
|
|
31
31
|
* the two replays a batch whose recompute is idempotent. When no change is
|
|
32
32
|
* waiting it rebuilds `building` and `stale` views a few tenants at a time.
|
|
33
33
|
* A view whose recompute fails is marked stale with its error and skipped
|
|
34
|
-
* until `
|
|
34
|
+
* until `akter fleet rebuild`, so it never stops the others. A slot that
|
|
35
35
|
* is missing, lost, or moved by anyone else marks every view stale. The
|
|
36
36
|
* runner that cannot take the lock retries every `LOCK_RETRY`.
|
|
37
37
|
*/
|
|
@@ -101,7 +101,7 @@ const innermost = (error) => {
|
|
|
101
101
|
* the two replays a batch whose recompute is idempotent. When no change is
|
|
102
102
|
* waiting it rebuilds `building` and `stale` views a few tenants at a time.
|
|
103
103
|
* A view whose recompute fails is marked stale with its error and skipped
|
|
104
|
-
* until `
|
|
104
|
+
* until `akter fleet rebuild`, so it never stops the others. A slot that
|
|
105
105
|
* is missing, lost, or moved by anyone else marks every view stale. The
|
|
106
106
|
* runner that cannot take the lock retries every `LOCK_RETRY`.
|
|
107
107
|
*/
|
package/dist/runtime/layer.d.ts
CHANGED
|
@@ -81,7 +81,7 @@ export interface Options {
|
|
|
81
81
|
};
|
|
82
82
|
/** Telemetry this runner keeps beside the spans and metrics it reports. */
|
|
83
83
|
readonly observability?: {
|
|
84
|
-
/** Defect spans the runner keeps for `
|
|
84
|
+
/** Defect spans the runner keeps for `akter defects list`, newest last. Default 1,000. */
|
|
85
85
|
readonly defects?: number;
|
|
86
86
|
/**
|
|
87
87
|
* How often one runner of the deployment samples the database gauges
|
|
@@ -94,7 +94,7 @@ export interface Options {
|
|
|
94
94
|
* How long this runtime keeps writing event and effect payload versions
|
|
95
95
|
* without refreshing its writer rows; it refreshes every half window and
|
|
96
96
|
* refuses new turns once a window passes without a refresh, so
|
|
97
|
-
* `
|
|
97
|
+
* `akter payloads clear` can tell when no turn still writes an old
|
|
98
98
|
* version. Default 2 minutes, at least 1 second.
|
|
99
99
|
*/
|
|
100
100
|
readonly payloadWriterWindow?: Duration.Input;
|
|
@@ -159,7 +159,7 @@ export interface Options {
|
|
|
159
159
|
};
|
|
160
160
|
/**
|
|
161
161
|
* The fleet views this runtime maintains and serves. They need Postgres with
|
|
162
|
-
* `wal_level=logical`, a login with `REPLICATION`, and `
|
|
162
|
+
* `wal_level=logical`, a login with `REPLICATION`, and `akter fleet setup`;
|
|
163
163
|
* startup refuses otherwise, and PGlite refuses any view. One runner at a
|
|
164
164
|
* time maintains them, off the turn path.
|
|
165
165
|
*/
|
package/dist/runtime/layer.js
CHANGED
|
@@ -341,7 +341,7 @@ export const layer = (options = {}) => {
|
|
|
341
341
|
}).pipe(refreshing.withPermits(1), Effect.provideContext(services), Effect.provideService(FrameworkClock, frameworkClock));
|
|
342
342
|
/**
|
|
343
343
|
* A runtime that could not refresh its writer rows within the window
|
|
344
|
-
* may already count as gone to `
|
|
344
|
+
* may already count as gone to `akter payloads clear`, so it starts
|
|
345
345
|
* no turn until a refresh succeeds. The check is local.
|
|
346
346
|
*/
|
|
347
347
|
const writable = Effect.gen(function* () {
|
|
@@ -17,7 +17,7 @@ export declare const formatPayloadProblem: (problem: PayloadProblem) => string;
|
|
|
17
17
|
* Compares declared chains with the versions the database may hold. A
|
|
18
18
|
* version recorded above a chain's current version is a rollback past a
|
|
19
19
|
* schema change. A chain that starts above version `v` can't read values of
|
|
20
|
-
* `v`: an event version counts as stored until `
|
|
20
|
+
* `v`: an event version counts as stored until `akter payloads clear` marks
|
|
21
21
|
* it cleared. Every stored event has a recorded version, because a runtime
|
|
22
22
|
* records what it writes before it takes a shard and the migration refused
|
|
23
23
|
* databases that held rows before it. Job versions are read from the
|
|
@@ -39,9 +39,9 @@ export declare const recordPayloadVersions: (declarations: readonly PayloadDecla
|
|
|
39
39
|
export declare const refreshWriters: (runtimeId: string, windowMs: number, declarations: readonly PayloadDeclaration[]) => Effect.Effect<void, import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
40
40
|
/** Drops a stopping runtime's writer rows, so a clear need not wait out its window. */
|
|
41
41
|
export declare const dropWriters: (runtimeId: string) => Effect.Effect<void, import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
42
|
-
/** `
|
|
42
|
+
/** `akter payloads check`: the startup check for `actors`, read-only. */
|
|
43
43
|
export declare const checkPayloads: (actors: ReadonlyArray<object>) => Effect.Effect<PayloadProblem[], import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
44
|
-
/** What `
|
|
44
|
+
/** What `akter payloads clear` found for one superseded event version. */
|
|
45
45
|
export interface ClearResult {
|
|
46
46
|
readonly actorType: string;
|
|
47
47
|
readonly tag: string;
|
|
@@ -54,7 +54,7 @@ export interface ClearResult {
|
|
|
54
54
|
readonly outcome: "cleared" | "writer" | "stored";
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
* `
|
|
57
|
+
* `akter payloads clear`: marks each superseded event version of `actors`
|
|
58
58
|
* cleared once its retention horizon has passed, no runtime can still write
|
|
59
59
|
* it, and no event of it remains. The version row is locked first, the
|
|
60
60
|
* writer rows are read again after the scan, and each statement of the
|
|
@@ -14,7 +14,7 @@ const keyOf = (declared) => `${declared.actorType}\u0000${declared.kind}\u0000${
|
|
|
14
14
|
* Compares declared chains with the versions the database may hold. A
|
|
15
15
|
* version recorded above a chain's current version is a rollback past a
|
|
16
16
|
* schema change. A chain that starts above version `v` can't read values of
|
|
17
|
-
* `v`: an event version counts as stored until `
|
|
17
|
+
* `v`: an event version counts as stored until `akter payloads clear` marks
|
|
18
18
|
* it cleared. Every stored event has a recorded version, because a runtime
|
|
19
19
|
* records what it writes before it takes a shard and the migration refused
|
|
20
20
|
* databases that held rows before it. Job versions are read from the
|
|
@@ -60,7 +60,7 @@ export const findPayloadProblems = Effect.fnUntraced(function* (declarations, wr
|
|
|
60
60
|
if (declared.kind === "event") {
|
|
61
61
|
for (const row of versions)
|
|
62
62
|
if (row.version < first && !row.cleared)
|
|
63
|
-
problem(`version ${row.version} may still be stored below this chain's first version ${first}; run
|
|
63
|
+
problem(`version ${row.version} may still be stored below this chain's first version ${first}; run akter payloads clear once its events are gone`);
|
|
64
64
|
}
|
|
65
65
|
else {
|
|
66
66
|
const [stored] = yield* sql `
|
|
@@ -159,7 +159,7 @@ const definitionsOf = (actors) => actors.flatMap((actor) => {
|
|
|
159
159
|
const payloads = descriptorOf(actor)?.definitionPayloads;
|
|
160
160
|
return payloads === undefined ? [] : [payloads];
|
|
161
161
|
});
|
|
162
|
-
/** `
|
|
162
|
+
/** `akter payloads check`: the startup check for `actors`, read-only. */
|
|
163
163
|
export const checkPayloads = (actors) => Effect.suspend(() => {
|
|
164
164
|
const definitions = definitionsOf(actors);
|
|
165
165
|
return inReadOnlySnapshot(findPayloadProblems(definitions.flatMap((d) => d.declarations), definitions.flatMap((d) => {
|
|
@@ -177,7 +177,7 @@ export const checkPayloads = (actors) => Effect.suspend(() => {
|
|
|
177
177
|
})));
|
|
178
178
|
});
|
|
179
179
|
/**
|
|
180
|
-
* `
|
|
180
|
+
* `akter payloads clear`: marks each superseded event version of `actors`
|
|
181
181
|
* cleared once its retention horizon has passed, no runtime can still write
|
|
182
182
|
* it, and no event of it remains. The version row is locked first, the
|
|
183
183
|
* writer rows are read again after the scan, and each statement of the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Request } from "../request.ts";
|
|
2
2
|
/**
|
|
3
|
-
* Span names are public: dashboards, alerts, and `
|
|
3
|
+
* Span names are public: dashboards, alerts, and `akter defects list` match
|
|
4
4
|
* on them, so a rename is a breaking change. Every name is bounded by the
|
|
5
5
|
* deployment's declarations, never by an actor id or command id; those are
|
|
6
6
|
* attributes.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
import { System } from "../../identity/caller.js";
|
|
3
3
|
/**
|
|
4
|
-
* Span names are public: dashboards, alerts, and `
|
|
4
|
+
* Span names are public: dashboards, alerts, and `akter defects list` match
|
|
5
5
|
* on them, so a rename is a breaking change. Every name is bounded by the
|
|
6
6
|
* deployment's declarations, never by an actor id or command id; those are
|
|
7
7
|
* attributes.
|
|
@@ -461,7 +461,7 @@ export const bindTables = Effect.fnUntraced(function* (database, scope, write, c
|
|
|
461
461
|
/**
|
|
462
462
|
* Refuses an adopted table whose physical state does not match its
|
|
463
463
|
* declaration: a writable one needs its `actor_adoptions` row (which
|
|
464
|
-
* `
|
|
464
|
+
* `akter adopt observe` writes) with the declared type and columns and a
|
|
465
465
|
* bigint `routing_key`; every adopted table needs mapped columns of a mappable
|
|
466
466
|
* type and an index that leads with them. Each message names the fix.
|
|
467
467
|
*/
|
|
@@ -494,16 +494,16 @@ const checkAdoptedDeclaration = Effect.fnUntraced(function* ({ actor, table, inf
|
|
|
494
494
|
const [recorded] = yield* sql `SELECT actor_type, tenant_column, actor_column, mode, writer_role, allowed_roles
|
|
495
495
|
FROM actor_adoptions WHERE table_schema = ${schema} AND table_name = ${info.table}`;
|
|
496
496
|
if (recorded === undefined)
|
|
497
|
-
return yield* refuse(`has no adoption record; run
|
|
497
|
+
return yield* refuse(`has no adoption record; run akter adopt observe ${info.table} before serving it`);
|
|
498
498
|
if (recorded.actor_type !== actor)
|
|
499
499
|
return yield* refuse(`is adopted by actor ${recorded.actor_type}, not ${actor}`);
|
|
500
500
|
if (recorded.tenant_column !== tenantColumn || recorded.actor_column !== actorColumn)
|
|
501
|
-
return yield* refuse(`was adopted with columns (${recorded.tenant_column}, ${recorded.actor_column}) but declares (${tenantColumn}, ${actorColumn}); run
|
|
501
|
+
return yield* refuse(`was adopted with columns (${recorded.tenant_column}, ${recorded.actor_column}) but declares (${tenantColumn}, ${actorColumn}); run akter adopt observe ${info.table} again`);
|
|
502
502
|
if (recorded.mode === "enforce")
|
|
503
503
|
enforcement = { writerRole: recorded.writer_role, allowedRoles: recorded.allowed_roles };
|
|
504
504
|
const routing = physical.find((candidate) => candidate.name === "routing_key");
|
|
505
505
|
if (routing?.type !== "int8")
|
|
506
|
-
return yield* refuse(`has no bigint routing_key; run
|
|
506
|
+
return yield* refuse(`has no bigint routing_key; run akter adopt observe ${info.table}`);
|
|
507
507
|
}
|
|
508
508
|
const target = {
|
|
509
509
|
schema,
|
|
@@ -115,7 +115,7 @@ export declare const acceptWorkflows: (actor: DeclaredActor) => Effect.Effect<{
|
|
|
115
115
|
retained: boolean;
|
|
116
116
|
}, import("effect/sql/SqlError").SqlError, import("effect/Crypto").Crypto | SqlClient.SqlClient>;
|
|
117
117
|
/**
|
|
118
|
-
* The deploy check `
|
|
118
|
+
* The deploy check `akter workflows check` runs: every actor type in the
|
|
119
119
|
* database against `actors`, in one read-only snapshot.
|
|
120
120
|
*/
|
|
121
121
|
export declare const checkWorkflows: (actors: ReadonlyArray<{
|
|
@@ -279,7 +279,7 @@ export const acceptWorkflows = Effect.fnUntraced(function* (actor) {
|
|
|
279
279
|
});
|
|
280
280
|
});
|
|
281
281
|
/**
|
|
282
|
-
* The deploy check `
|
|
282
|
+
* The deploy check `akter workflows check` runs: every actor type in the
|
|
283
283
|
* database against `actors`, in one read-only snapshot.
|
|
284
284
|
*/
|
|
285
285
|
export const checkWorkflows = (actors) => Effect.suspend(() => inReadOnlySnapshot(findIncompatibilities(actors.map(declaredOf), { everyActorType: true })));
|
package/dist/tables/owned.d.ts
CHANGED
|
@@ -235,7 +235,7 @@ export type Declared<T extends AnyPgTable, O extends OwnerColumns<T> | undefined
|
|
|
235
235
|
* `tenant` and `actor` columns, which must be text, varchar, or uuid, hold the
|
|
236
236
|
* tenant and the actor's key, and nothing else about the table changes, so
|
|
237
237
|
* foreign keys stay. A writable adopted table gains a nullable `routing_key`
|
|
238
|
-
* and starts only after `
|
|
238
|
+
* and starts only after `akter adopt observe`; its primary key stays global,
|
|
239
239
|
* so a key another actor already holds fails the turn. `access: "read"` maps
|
|
240
240
|
* the two columns for reading and adds nothing.
|
|
241
241
|
*
|
package/dist/tables/owned.js
CHANGED
|
@@ -119,7 +119,7 @@ const adopt = (source, options) => {
|
|
|
119
119
|
* `tenant` and `actor` columns, which must be text, varchar, or uuid, hold the
|
|
120
120
|
* tenant and the actor's key, and nothing else about the table changes, so
|
|
121
121
|
* foreign keys stay. A writable adopted table gains a nullable `routing_key`
|
|
122
|
-
* and starts only after `
|
|
122
|
+
* and starts only after `akter adopt observe`; its primary key stays global,
|
|
123
123
|
* so a key another actor already holds fails the turn. `access: "read"` maps
|
|
124
124
|
* the two columns for reading and adds nothing.
|
|
125
125
|
*
|
|
@@ -259,7 +259,7 @@ export declare const ProgressRecord: {
|
|
|
259
259
|
/** Options of `ActorTest.actor`. */
|
|
260
260
|
export interface TestActorOptions {
|
|
261
261
|
/**
|
|
262
|
-
* Path of a seed file `
|
|
262
|
+
* Path of a seed file `akter export` wrote. The actor starts from its
|
|
263
263
|
* state and pending work, staged as the caller the test runs as; the seed
|
|
264
264
|
* carries no tenant, caller, or credential. Reading it needs a `FileSystem`.
|
|
265
265
|
* An actor that already exists, a seed of another actor type, and a seed
|
|
@@ -500,7 +500,7 @@ export const adoptionConformance = [
|
|
|
500
500
|
run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
|
|
501
501
|
const target = yield* prepared(environment, { observe: false });
|
|
502
502
|
const exit = yield* runtimeOn(target).pipe(Effect.exit);
|
|
503
|
-
expect(defect(exit)).toContain("conformance_invoices has no adoption record; run
|
|
503
|
+
expect(defect(exit)).toContain("conformance_invoices has no adoption record; run akter adopt observe conformance_invoices before serving it");
|
|
504
504
|
})),
|
|
505
505
|
},
|
|
506
506
|
{
|
|
@@ -261,7 +261,7 @@ export declare const OrdersByRegion: import("../../index.ts").FleetView<"OrdersB
|
|
|
261
261
|
readonly orders: import("../../index.ts").Aggregate<"count", never>;
|
|
262
262
|
readonly total: import("../../index.ts").Aggregate<"sum", "amountCents">;
|
|
263
263
|
}>;
|
|
264
|
-
/** The views an entry module exports for `
|
|
264
|
+
/** The views an entry module exports for `akter fleet setup`. */
|
|
265
265
|
export declare const fleet: (import("../../index.ts").FleetView<"OrdersByRegion", import("../../index.ts").OwnedTable<import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
266
266
|
name: "fleet_orders";
|
|
267
267
|
schema: undefined;
|
|
@@ -70,7 +70,7 @@ export const OrdersByRegion = Fleet.view("OrdersByRegion", {
|
|
|
70
70
|
groupBy: ["region", "status"],
|
|
71
71
|
select: { orders: Fleet.count(), total: Fleet.sum("amountCents") },
|
|
72
72
|
});
|
|
73
|
-
/** The views an entry module exports for `
|
|
73
|
+
/** The views an entry module exports for `akter fleet setup`. */
|
|
74
74
|
export const fleet = [OrdersByStatus, OrdersByRegion];
|
|
75
75
|
/** `OrdersByStatus` with its filter dropped: the same table, another definition. */
|
|
76
76
|
const OrdersByStatusAll = Fleet.view("OrdersByStatus", {
|
|
@@ -236,7 +236,7 @@ const dropSlot = (sql) => sql `SELECT count(*)::int AS dropped FROM (SELECT pg_d
|
|
|
236
236
|
FROM pg_replication_slots WHERE slot_name = ${FLEET_SLOT} AND database = current_database()) d`.pipe(Effect.retry({ times: 200, schedule: Schedule.spaced("50 millis") }), Effect.orDie);
|
|
237
237
|
/**
|
|
238
238
|
* A fresh database with the fleet tables and their indexes, set up with
|
|
239
|
-
* `
|
|
239
|
+
* `akter fleet setup` unless `setup` is false, and an administrative client
|
|
240
240
|
* to it. The slot is dropped when the scope closes, before the backend drops
|
|
241
241
|
* the database, which a logical slot would block.
|
|
242
242
|
*/
|
|
@@ -752,7 +752,7 @@ export const fleetConformance = [
|
|
|
752
752
|
const { database, admin } = yield* fleetDatabase(environment, { setup: false });
|
|
753
753
|
const check = (fleet, role) => checkFleet(fleet, role).pipe(Effect.provideService(SqlClient.SqlClient, admin), Effect.exit);
|
|
754
754
|
const runtime = yield* Layer.build(ActorTest.layer({ database, fleet: views, authorize: () => Effect.succeed(true) })).pipe(Effect.exit);
|
|
755
|
-
expect(refusal(runtime)).toContain("public.fleet_orders is not in publication durable_fleet; run
|
|
755
|
+
expect(refusal(runtime)).toContain("public.fleet_orders is not in publication durable_fleet; run akter fleet setup");
|
|
756
756
|
yield* admin `CREATE PUBLICATION durable_fleet FOR TABLE fleet_orders`.pipe(Effect.orDie);
|
|
757
757
|
expect(refusal(yield* check(views))).toContain("does not have full replica identity");
|
|
758
758
|
yield* admin `ALTER TABLE fleet_orders REPLICA IDENTITY FULL`.pipe(Effect.orDie);
|
|
@@ -391,7 +391,7 @@ export const payloadMigrationsConformance = [
|
|
|
391
391
|
expect(yield* refusal(database, Shortened.layer)).toContain("deploy refused");
|
|
392
392
|
const shortened = yield* deploy(database, Layer.empty, checkPayloads([Shortened.Ledger]).pipe(Effect.orDie));
|
|
393
393
|
expect(shortened.map(formatPayloadProblem)).toEqual([
|
|
394
|
-
"Ledger/Placed (event) version 0 may still be stored below this chain's first version 1; run
|
|
394
|
+
"Ledger/Placed (event) version 0 may still be stored below this chain's first version 1; run akter payloads clear once its events are gone",
|
|
395
395
|
"Ledger/Charge (job) version 0 stored in 1 pending job or dead letter row below this chain's first version 1",
|
|
396
396
|
]);
|
|
397
397
|
yield* deploy(database, Next.layer, Effect.void);
|
|
@@ -406,7 +406,7 @@ export const payloadMigrationsConformance = [
|
|
|
406
406
|
})),
|
|
407
407
|
},
|
|
408
408
|
{
|
|
409
|
-
name: "payload migrations: refuses a shortened chain after the retention horizon until
|
|
409
|
+
name: "payload migrations: refuses a shortened chain after the retention horizon until akter payloads clear finds no row of the dropped version, and refuses again after restoring a snapshot taken before the clear",
|
|
410
410
|
run: ({ expect, environment }) => withCase(environment, (database) => Effect.gen(function* () {
|
|
411
411
|
yield* deploy(database, Base.layer, Effect.gen(function* () {
|
|
412
412
|
yield* Base.place("o1", 100);
|
|
@@ -440,7 +440,7 @@ export const payloadMigrationsConformance = [
|
|
|
440
440
|
{
|
|
441
441
|
requiresIndependentConnections: true,
|
|
442
442
|
timeoutMs: 120_000,
|
|
443
|
-
name: "payload migrations: refuses
|
|
443
|
+
name: "payload migrations: refuses akter payloads clear while a runtime writing that version refreshed within the window, and a runtime past its window refuses new turns until it refreshes",
|
|
444
444
|
run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
|
|
445
445
|
yield* reset;
|
|
446
446
|
const database = yield* environment.freshDatabase;
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@rikalabs/akter","version":"0.1.0-alpha.
|
|
1
|
+
{"name":"@rikalabs/akter","version":"0.1.0-alpha.1","files":["dist","CHANGELOG.md","README.md","LICENSE","NOTICE"],"dependencies":{"@effect/platform-node-shared":"4.0.0","@electric-sql/pglite":"0.5.8","@noble/hashes":"2.4.0","@node-rs/xxhash":"1.7.8","koffi":"3.3.2"},"peerDependencies":{"@effect/platform-bun":"4.0.0","@effect/platform-node":"4.0.0","@effect/sql-pg":"4.0.0","@effect/sql-pglite":"4.0.0","drizzle-orm":"1.0.0-rc.5-5935859","effect":"4.0.0"},"publishConfig":{"access":"public"},"description":"The framework for durable, stateful backends that power realtime apps, background work, and agents.","keywords":["actor-model","actors","agents","background-jobs","bun","durable","effect","effect-ts","pglite","postgres","realtime","stateful","transactions","workflows"],"homepage":"https://github.com/Rika-Labs/akter#readme","bugs":"https://github.com/Rika-Labs/akter/issues","license":"Apache-2.0","author":"Rika Labs","repository":{"type":"git","url":"git+https://github.com/Rika-Labs/akter.git","directory":"packages/akter"},"type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./runtime":{"types":"./dist/runtime/index.d.ts","default":"./dist/runtime/index.js"},"./client":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"},"./package.json":"./package.json"},"peerDependenciesMeta":{"@effect/platform-bun":{"optional":true},"@effect/platform-node":{"optional":true}},"engines":{"bun":">=1.4.2","node":">=24.0.0"}}
|