@nylorun/runtime 0.12.0-beta → 0.14.0-beta
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 +361 -0
- package/README.md +69 -15
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +38 -8
- package/dist/adapters/replication/pgoutput.d.ts +16 -0
- package/dist/adapters/replication/pgoutput.js +169 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +1071 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +7825 -0
- package/dist/api/components.js +83 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +130 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +259 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +10 -0
- package/dist/api/http/routes/tenant.js +172 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +138 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +19 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/host/admin-api.d.ts +22 -0
- package/dist/host/admin-api.js +178 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +185 -0
- package/dist/host/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +31 -3
- package/dist/host/create-host.js +168 -327
- package/dist/host/http.d.ts +14 -3
- package/dist/host/http.js +64 -23
- package/dist/host/main.js +55 -4
- package/dist/host/stack-config.d.ts +20 -0
- package/dist/host/stack-config.js +55 -0
- package/dist/mcp/pool.d.ts +19 -1
- package/dist/mcp/pool.js +60 -10
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25696 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/event.d.ts +19 -0
- package/dist/store/event.js +49 -0
- package/dist/store/memory.d.ts +20 -8
- package/dist/store/memory.js +265 -100
- package/dist/store/postgres/connect.d.ts +13 -0
- package/dist/store/postgres/connect.js +5 -0
- package/dist/store/postgres/migrations/003_subject_tokens.d.ts +8 -0
- package/dist/store/postgres/migrations/003_subject_tokens.js +42 -0
- package/dist/store/postgres/migrations/004_publishable_keys.d.ts +6 -0
- package/dist/store/postgres/migrations/004_publishable_keys.js +18 -0
- package/dist/store/postgres/migrations/005_action_endpoints.d.ts +6 -0
- package/dist/store/postgres/migrations/005_action_endpoints.js +32 -0
- package/dist/store/postgres/migrations/006_drop_executors.d.ts +11 -0
- package/dist/store/postgres/migrations/006_drop_executors.js +32 -0
- package/dist/store/postgres/migrations/007_event_time.d.ts +7 -0
- package/dist/store/postgres/migrations/007_event_time.js +14 -0
- package/dist/store/postgres/migrations/008_durable_streams.d.ts +13 -0
- package/dist/store/postgres/migrations/008_durable_streams.js +35 -0
- package/dist/store/postgres/migrations/index.d.ts +5 -1
- package/dist/store/postgres/migrations/index.js +22 -2
- package/dist/store/postgres/migrations/shared/index.d.ts +31 -0
- package/dist/store/postgres/migrations/shared/index.js +92 -0
- package/dist/store/postgres/record.d.ts +11 -0
- package/dist/store/postgres/record.js +64 -0
- package/dist/store/postgres/store.js +337 -95
- package/dist/store/postgres/tenants.js +14 -0
- package/dist/store/types.d.ts +176 -81
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +29 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/memory.d.ts +69 -0
- package/dist/streams/relay/memory.js +176 -0
- package/dist/streams/relay/types.d.ts +77 -0
- package/dist/streams/relay/types.js +7 -0
- package/dist/streams/types.d.ts +66 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.js +32 -0
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +121 -129
- package/dist/tenant/context.d.ts +75 -18
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +105 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +476 -0
- package/dist/tenant/effects.d.ts +5 -0
- package/dist/tenant/effects.js +138 -48
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +6 -0
- package/dist/tenant/ephemeral.js +60 -36
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/module.d.ts +3 -0
- package/dist/tenant/module.js +6 -12
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/pool.d.ts +0 -2
- package/dist/tenant/pool.js +0 -18
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +10 -20
- package/dist/tenant/runtime.js +59 -42
- package/dist/tenant/scheduler.d.ts +7 -2
- package/dist/tenant/scheduler.js +7 -3
- package/dist/tenant/session-streams.d.ts +125 -0
- package/dist/tenant/session-streams.js +399 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +0 -3
- package/dist/tenant/status.js +19 -12
- package/dist/tenant/streams.d.ts +60 -67
- package/dist/tenant/streams.js +140 -208
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +178 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +29 -7
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +9 -3
- package/dist/vault/service.d.ts +18 -2
- package/dist/vault/service.js +57 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -8
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Migration } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Protocol 3 removes executors: Actions are only delivered to Action endpoints.
|
|
4
|
+
*
|
|
5
|
+
* An Action an executor had claimed becomes `delivering` with a deadline that has already
|
|
6
|
+
* passed, so the next sweep applies the delivery loss rule to it (`delivery.ts` `loseAction`):
|
|
7
|
+
* a tool becomes `uncertain` with its `action.uncertain` event, and a hook, `fn` or `verify` is
|
|
8
|
+
* delivered again. Open Actions lose the executor-only `claimId` and `leaseExpiresAt`; settled
|
|
9
|
+
* ones are never delivered again and keep them.
|
|
10
|
+
*/
|
|
11
|
+
export declare const dropExecutors: Migration;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol 3 removes executors: Actions are only delivered to Action endpoints.
|
|
3
|
+
*
|
|
4
|
+
* An Action an executor had claimed becomes `delivering` with a deadline that has already
|
|
5
|
+
* passed, so the next sweep applies the delivery loss rule to it (`delivery.ts` `loseAction`):
|
|
6
|
+
* a tool becomes `uncertain` with its `action.uncertain` event, and a hook, `fn` or `verify` is
|
|
7
|
+
* delivered again. Open Actions lose the executor-only `claimId` and `leaseExpiresAt`; settled
|
|
8
|
+
* ones are never delivered again and keep them.
|
|
9
|
+
*/
|
|
10
|
+
export const dropExecutors = {
|
|
11
|
+
version: 6,
|
|
12
|
+
name: "drop_executors",
|
|
13
|
+
up: (s) => `
|
|
14
|
+
DROP INDEX ${s}.actions_lease;
|
|
15
|
+
ALTER TABLE ${s}.actions DROP COLUMN lease_expires_at;
|
|
16
|
+
|
|
17
|
+
UPDATE ${s}.actions
|
|
18
|
+
SET body = (
|
|
19
|
+
${s}.doc(body) - 'claimId' - 'leaseExpiresAt'
|
|
20
|
+
|| jsonb_build_object(
|
|
21
|
+
'status', 'delivering',
|
|
22
|
+
'deadlineAt', to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
|
|
23
|
+
)
|
|
24
|
+
)::json
|
|
25
|
+
WHERE status = 'claimed';
|
|
26
|
+
UPDATE ${s}.actions
|
|
27
|
+
SET body = (${s}.doc(body) - 'claimId' - 'leaseExpiresAt')::json
|
|
28
|
+
WHERE status IN ('pending', 'delivering');
|
|
29
|
+
|
|
30
|
+
DROP TABLE ${s}.executors;
|
|
31
|
+
`,
|
|
32
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Migration } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Protocol 4 puts events on the `nylorun.event/2` envelope, where `createdAt` is `time`. The
|
|
4
|
+
* outbox's `created_at` (its age, for relay lag) is generated from either, so rows written
|
|
5
|
+
* before the upgrade keep theirs.
|
|
6
|
+
*/
|
|
7
|
+
export declare const eventTime: Migration;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol 4 puts events on the `nylorun.event/2` envelope, where `createdAt` is `time`. The
|
|
3
|
+
* outbox's `created_at` (its age, for relay lag) is generated from either, so rows written
|
|
4
|
+
* before the upgrade keep theirs.
|
|
5
|
+
*/
|
|
6
|
+
export const eventTime = {
|
|
7
|
+
version: 7,
|
|
8
|
+
name: "event_time",
|
|
9
|
+
up: (s) => `
|
|
10
|
+
ALTER TABLE ${s}.outbox DROP COLUMN created_at;
|
|
11
|
+
ALTER TABLE ${s}.outbox ADD COLUMN created_at text COLLATE "C"
|
|
12
|
+
GENERATED ALWAYS AS (coalesce(${s}.doc(body)->>'time', ${s}.doc(body)->>'createdAt')) STORED;
|
|
13
|
+
`,
|
|
14
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Migration } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Durable Streams v1 (Durable Streams §18.1): session events move from the Tenant's outbox to
|
|
4
|
+
* the shared record (`nylorun_streams`), and stream names lose their incarnation.
|
|
5
|
+
*
|
|
6
|
+
* **A fresh start for session data.** A Tenant that has sessions loses them, with their
|
|
7
|
+
* commands, checkpoints, effects, Actions, links, outbox and turn buckets, as a
|
|
8
|
+
* sessions-scope reset does; settings, agents, Action endpoints, keys, policy and vaults stay.
|
|
9
|
+
* It moves to basin generation 1, and generation 0 (the basin holding the old streams) is
|
|
10
|
+
* retired, so the Runtime deletes it when the Tenant opens. A Tenant without sessions stays at
|
|
11
|
+
* generation 0: its basin holds no session stream it could collide with.
|
|
12
|
+
*/
|
|
13
|
+
export declare const durableStreams: Migration;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable Streams v1 (Durable Streams §18.1): session events move from the Tenant's outbox to
|
|
3
|
+
* the shared record (`nylorun_streams`), and stream names lose their incarnation.
|
|
4
|
+
*
|
|
5
|
+
* **A fresh start for session data.** A Tenant that has sessions loses them, with their
|
|
6
|
+
* commands, checkpoints, effects, Actions, links, outbox and turn buckets, as a
|
|
7
|
+
* sessions-scope reset does; settings, agents, Action endpoints, keys, policy and vaults stay.
|
|
8
|
+
* It moves to basin generation 1, and generation 0 (the basin holding the old streams) is
|
|
9
|
+
* retired, so the Runtime deletes it when the Tenant opens. A Tenant without sessions stays at
|
|
10
|
+
* generation 0: its basin holds no session stream it could collide with.
|
|
11
|
+
*/
|
|
12
|
+
export const durableStreams = {
|
|
13
|
+
version: 8,
|
|
14
|
+
name: "durable_streams",
|
|
15
|
+
up: (s) => `
|
|
16
|
+
ALTER TABLE ${s}.tenant
|
|
17
|
+
ADD COLUMN basin_generation integer NOT NULL DEFAULT 0,
|
|
18
|
+
ADD COLUMN retired_generations integer[] NOT NULL DEFAULT '{}';
|
|
19
|
+
UPDATE ${s}.tenant SET basin_generation = 1, retired_generations = '{0}'
|
|
20
|
+
WHERE EXISTS (SELECT 1 FROM ${s}.sessions);
|
|
21
|
+
|
|
22
|
+
DELETE FROM ${s}.links;
|
|
23
|
+
DELETE FROM ${s}.actions;
|
|
24
|
+
DELETE FROM ${s}.effects;
|
|
25
|
+
DELETE FROM ${s}.checkpoints;
|
|
26
|
+
DELETE FROM ${s}.commands;
|
|
27
|
+
DELETE FROM ${s}.sessions;
|
|
28
|
+
DELETE FROM ${s}.subject_usage;
|
|
29
|
+
DROP TABLE ${s}.outbox;
|
|
30
|
+
|
|
31
|
+
ALTER TABLE ${s}.sessions
|
|
32
|
+
DROP COLUMN stream_incarnation,
|
|
33
|
+
DROP COLUMN next_event_seq;
|
|
34
|
+
`,
|
|
35
|
+
};
|
|
@@ -24,7 +24,11 @@ export declare const MIGRATIONS: readonly Migration[];
|
|
|
24
24
|
export declare const POSTGRES_SCHEMA_VERSION: number;
|
|
25
25
|
/** Checks that versions run 1, 2, 3, … Throws otherwise. */
|
|
26
26
|
export declare function assertMigrations(migrations: readonly Migration[]): void;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Takes the schema's migration lock until the transaction ends. Waiting for it, and the
|
|
29
|
+
* migrations or deletion done under it, may outlast the pool's statement timeout: it is lifted
|
|
30
|
+
* for the transaction, since a migration that times out would quarantine the Tenant.
|
|
31
|
+
*/
|
|
28
32
|
export declare function lockSchema(tx: TransactionSql, schema: string): Promise<void>;
|
|
29
33
|
export declare function schemaExists(sql: Sql | TransactionSql, schema: string): Promise<boolean>;
|
|
30
34
|
/**
|
|
@@ -2,7 +2,22 @@ import { quarantine } from "../../../tenant/quarantine.js";
|
|
|
2
2
|
import { quoteIdentifier, tenantIdFromSchema } from "../names.js";
|
|
3
3
|
import { initial } from "./001_initial.js";
|
|
4
4
|
import { sessionOwner } from "./002_session_owner.js";
|
|
5
|
-
|
|
5
|
+
import { subjectTokens } from "./003_subject_tokens.js";
|
|
6
|
+
import { publishableKeys } from "./004_publishable_keys.js";
|
|
7
|
+
import { actionEndpoints } from "./005_action_endpoints.js";
|
|
8
|
+
import { dropExecutors } from "./006_drop_executors.js";
|
|
9
|
+
import { eventTime } from "./007_event_time.js";
|
|
10
|
+
import { durableStreams } from "./008_durable_streams.js";
|
|
11
|
+
export const MIGRATIONS = [
|
|
12
|
+
initial,
|
|
13
|
+
sessionOwner,
|
|
14
|
+
subjectTokens,
|
|
15
|
+
publishableKeys,
|
|
16
|
+
actionEndpoints,
|
|
17
|
+
dropExecutors,
|
|
18
|
+
eventTime,
|
|
19
|
+
durableStreams,
|
|
20
|
+
];
|
|
6
21
|
/** The schema version this Runtime writes and expects. */
|
|
7
22
|
export const POSTGRES_SCHEMA_VERSION = MIGRATIONS.at(-1).version;
|
|
8
23
|
/** Checks that versions run 1, 2, 3, … Throws otherwise. */
|
|
@@ -12,8 +27,13 @@ export function assertMigrations(migrations) {
|
|
|
12
27
|
throw new Error(`Migration ${migration.name} has version ${migration.version}, expected ${index + 1}`);
|
|
13
28
|
});
|
|
14
29
|
}
|
|
15
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Takes the schema's migration lock until the transaction ends. Waiting for it, and the
|
|
32
|
+
* migrations or deletion done under it, may outlast the pool's statement timeout: it is lifted
|
|
33
|
+
* for the transaction, since a migration that times out would quarantine the Tenant.
|
|
34
|
+
*/
|
|
16
35
|
export async function lockSchema(tx, schema) {
|
|
36
|
+
await tx `SET LOCAL statement_timeout = 0`;
|
|
17
37
|
await tx `SELECT pg_advisory_xact_lock(hashtextextended(${`nylorun.schema:${schema}`}, 0))`;
|
|
18
38
|
}
|
|
19
39
|
export async function schemaExists(sql, schema) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forward-only migrations of the shared `nylorun_streams` schema (Durable Streams §6): the
|
|
3
|
+
* record of every Tenant's session events, the log heads, the relay's state and the
|
|
4
|
+
* publication the stream relay reads. Unlike Tenant schemas, there is one for the database;
|
|
5
|
+
* the Host migrates it at startup, before any Tenant opens, under an advisory lock.
|
|
6
|
+
*
|
|
7
|
+
* Adding a migration: append to `STREAMS_MIGRATIONS` with the next version. Never edit or
|
|
8
|
+
* reorder an applied one.
|
|
9
|
+
*/
|
|
10
|
+
import type { Sql } from "postgres";
|
|
11
|
+
import { type Migration } from "../index.js";
|
|
12
|
+
export declare const STREAMS_SCHEMA = "nylorun_streams";
|
|
13
|
+
/** The publication the stream relay subscribes to. */
|
|
14
|
+
export declare const STREAMS_PUBLICATION = "nylorun_stream_relay";
|
|
15
|
+
export declare const STREAMS_MIGRATIONS: readonly Migration[];
|
|
16
|
+
/** The shared schema version this Runtime writes and expects. */
|
|
17
|
+
export declare const STREAMS_SCHEMA_VERSION: number;
|
|
18
|
+
/**
|
|
19
|
+
* Creates `nylorun_streams` when missing and applies every missing migration, in one
|
|
20
|
+
* transaction under the schema's migration lock. Throws when the schema is newer than this
|
|
21
|
+
* Runtime.
|
|
22
|
+
*/
|
|
23
|
+
export declare function migrateStreamsSchema(sql: Sql, migrations?: readonly Migration[]): Promise<{
|
|
24
|
+
from: number;
|
|
25
|
+
to: number;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Throws, naming the setting, when Postgres cannot run the stream relay: logical decoding
|
|
29
|
+
* needs `wal_level = logical` (a restart) and a role allowed to replicate.
|
|
30
|
+
*/
|
|
31
|
+
export declare function assertLogicalReplication(sql: Sql): Promise<void>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { assertMigrations, lockSchema } from "../index.js";
|
|
2
|
+
export const STREAMS_SCHEMA = "nylorun_streams";
|
|
3
|
+
/** The publication the stream relay subscribes to. */
|
|
4
|
+
export const STREAMS_PUBLICATION = "nylorun_stream_relay";
|
|
5
|
+
export const STREAMS_MIGRATIONS = [
|
|
6
|
+
{
|
|
7
|
+
version: 1,
|
|
8
|
+
name: "record",
|
|
9
|
+
up: (s) => `
|
|
10
|
+
CREATE TABLE ${s}.session_log_heads (
|
|
11
|
+
tenant_id text COLLATE "C" NOT NULL,
|
|
12
|
+
session_id text COLLATE "C" NOT NULL,
|
|
13
|
+
-- The basin generation the session's events go to.
|
|
14
|
+
generation integer NOT NULL,
|
|
15
|
+
-- The next seq.
|
|
16
|
+
head bigint NOT NULL DEFAULT 0,
|
|
17
|
+
PRIMARY KEY (tenant_id, session_id)
|
|
18
|
+
);
|
|
19
|
+
CREATE TABLE ${s}.session_events (
|
|
20
|
+
tenant_id text COLLATE "C" NOT NULL,
|
|
21
|
+
session_id text COLLATE "C" NOT NULL,
|
|
22
|
+
seq bigint NOT NULL,
|
|
23
|
+
generation integer NOT NULL,
|
|
24
|
+
type text NOT NULL,
|
|
25
|
+
-- The nylorun.event/2 envelope, as written.
|
|
26
|
+
body json NOT NULL,
|
|
27
|
+
committed_at timestamptz NOT NULL DEFAULT now(),
|
|
28
|
+
PRIMARY KEY (tenant_id, session_id, seq)
|
|
29
|
+
);
|
|
30
|
+
-- One row per replication slot: whether the record still has to be reconciled with S2
|
|
31
|
+
-- after the slot was created (a crash during reconciliation leaves it pending).
|
|
32
|
+
CREATE TABLE ${s}.relay_slots (
|
|
33
|
+
slot_name text PRIMARY KEY,
|
|
34
|
+
reconcile_pending boolean NOT NULL
|
|
35
|
+
);
|
|
36
|
+
CREATE PUBLICATION ${STREAMS_PUBLICATION}
|
|
37
|
+
FOR TABLE ${s}.session_events WITH (publish = 'insert');
|
|
38
|
+
`,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
/** The shared schema version this Runtime writes and expects. */
|
|
42
|
+
export const STREAMS_SCHEMA_VERSION = STREAMS_MIGRATIONS.at(-1).version;
|
|
43
|
+
/**
|
|
44
|
+
* Creates `nylorun_streams` when missing and applies every missing migration, in one
|
|
45
|
+
* transaction under the schema's migration lock. Throws when the schema is newer than this
|
|
46
|
+
* Runtime.
|
|
47
|
+
*/
|
|
48
|
+
export async function migrateStreamsSchema(sql, migrations = STREAMS_MIGRATIONS) {
|
|
49
|
+
assertMigrations(migrations);
|
|
50
|
+
const result = await sql.begin(async (tx) => {
|
|
51
|
+
await lockSchema(tx, STREAMS_SCHEMA);
|
|
52
|
+
return { result: await migrateInTx(tx, migrations) };
|
|
53
|
+
});
|
|
54
|
+
return result.result;
|
|
55
|
+
}
|
|
56
|
+
async function migrateInTx(tx, migrations) {
|
|
57
|
+
const s = STREAMS_SCHEMA;
|
|
58
|
+
await tx.unsafe(`
|
|
59
|
+
CREATE SCHEMA IF NOT EXISTS ${s};
|
|
60
|
+
CREATE TABLE IF NOT EXISTS ${s}.schema_version (
|
|
61
|
+
version integer PRIMARY KEY,
|
|
62
|
+
name text NOT NULL,
|
|
63
|
+
applied_at timestamptz NOT NULL DEFAULT now()
|
|
64
|
+
);`);
|
|
65
|
+
const [row] = await tx `
|
|
66
|
+
SELECT max(version)::int AS version FROM ${tx(`${s}.schema_version`)}`;
|
|
67
|
+
const from = row?.version ?? 0;
|
|
68
|
+
const latest = migrations.length;
|
|
69
|
+
if (from > latest)
|
|
70
|
+
throw new Error(`The ${s} schema is at version ${from}, newer than this Runtime's ${latest}: upgrade the Runtime`);
|
|
71
|
+
for (const migration of migrations.slice(from)) {
|
|
72
|
+
await tx.unsafe(migration.up(s));
|
|
73
|
+
await tx `INSERT INTO ${tx(`${s}.schema_version`)} (version, name)
|
|
74
|
+
VALUES (${migration.version}, ${migration.name})`;
|
|
75
|
+
}
|
|
76
|
+
return { from, to: latest };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Throws, naming the setting, when Postgres cannot run the stream relay: logical decoding
|
|
80
|
+
* needs `wal_level = logical` (a restart) and a role allowed to replicate.
|
|
81
|
+
*/
|
|
82
|
+
export async function assertLogicalReplication(sql) {
|
|
83
|
+
const [row] = await sql `
|
|
84
|
+
SELECT current_setting('wal_level') AS wal_level,
|
|
85
|
+
(SELECT rolreplication OR rolsuper FROM pg_roles WHERE rolname = current_user) AS replicates`;
|
|
86
|
+
if (row?.wal_level !== "logical")
|
|
87
|
+
throw new Error(`Postgres has wal_level = ${row?.wal_level ?? "unknown"}; the stream relay needs logical replication. ` +
|
|
88
|
+
"Set wal_level = logical and restart Postgres (`nylorun start` does this for the local stack; " +
|
|
89
|
+
"on a managed Postgres, turn on its logical replication option). See DEPLOYMENT.md.");
|
|
90
|
+
if (!row.replicates)
|
|
91
|
+
throw new Error("The Runtime's Postgres role cannot replicate: grant it REPLICATION (ALTER ROLE … REPLICATION). See DEPLOYMENT.md.");
|
|
92
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the shared record back for the stream relay (Durable Streams §7.2–7.3): a session's
|
|
3
|
+
* rows to refill a gap in S2, every log head to reconcile after a fresh slot, and a Tenant's
|
|
4
|
+
* current basin generation to tell obsolete rows apart. Reads every Tenant's rows by design.
|
|
5
|
+
*/
|
|
6
|
+
import type { Sql } from "postgres";
|
|
7
|
+
import type { RecordReader } from "../../streams/relay/types.js";
|
|
8
|
+
/** Every Tenant's record (the stream relay), or one Tenant's (`tenantId`, its Session Store). */
|
|
9
|
+
export declare function createPostgresRecordReader(sql: Sql, options?: {
|
|
10
|
+
tenantId?: string;
|
|
11
|
+
}): RecordReader;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { STREAMS_SCHEMA } from "./migrations/shared/index.js";
|
|
2
|
+
import { quoteIdentifier, tenantSchemaName } from "./names.js";
|
|
3
|
+
/** Every Tenant's record (the stream relay), or one Tenant's (`tenantId`, its Session Store). */
|
|
4
|
+
export function createPostgresRecordReader(sql, options = {}) {
|
|
5
|
+
const only = options.tenantId;
|
|
6
|
+
const events = sql(`${STREAMS_SCHEMA}.session_events`);
|
|
7
|
+
const heads = sql(`${STREAMS_SCHEMA}.session_log_heads`);
|
|
8
|
+
return {
|
|
9
|
+
async readRange(tenantId, sessionId, from, to) {
|
|
10
|
+
const rows = await sql `
|
|
11
|
+
SELECT seq, generation, body FROM ${events}
|
|
12
|
+
WHERE tenant_id = ${tenantId} AND session_id = ${sessionId}
|
|
13
|
+
AND seq >= ${from} AND seq < ${to}
|
|
14
|
+
ORDER BY seq`;
|
|
15
|
+
return rows.map((row) => ({
|
|
16
|
+
tenantId,
|
|
17
|
+
sessionId,
|
|
18
|
+
seq: Number(row.seq),
|
|
19
|
+
generation: row.generation,
|
|
20
|
+
body: row.body,
|
|
21
|
+
}));
|
|
22
|
+
},
|
|
23
|
+
async heads(after, limit) {
|
|
24
|
+
const rows = await sql `
|
|
25
|
+
SELECT tenant_id, session_id, generation, head FROM ${heads}
|
|
26
|
+
WHERE head > 0
|
|
27
|
+
${only === undefined ? sql `` : sql `AND tenant_id = ${only}`}
|
|
28
|
+
${after ? sql `AND (tenant_id, session_id) > (${after.tenantId}, ${after.sessionId})` : sql ``}
|
|
29
|
+
ORDER BY tenant_id, session_id
|
|
30
|
+
LIMIT ${limit}`;
|
|
31
|
+
return rows.map((row) => ({
|
|
32
|
+
tenantId: row.tenant_id,
|
|
33
|
+
sessionId: row.session_id,
|
|
34
|
+
generation: row.generation,
|
|
35
|
+
head: Number(row.head),
|
|
36
|
+
}));
|
|
37
|
+
},
|
|
38
|
+
async generation(tenantId) {
|
|
39
|
+
let schema;
|
|
40
|
+
try {
|
|
41
|
+
schema = tenantSchemaName(tenantId);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const [exists] = await sql `
|
|
47
|
+
SELECT to_regclass(${`${quoteIdentifier(schema)}.tenant`}) IS NOT NULL AS exists`;
|
|
48
|
+
if (!exists?.exists)
|
|
49
|
+
return undefined;
|
|
50
|
+
try {
|
|
51
|
+
const [row] = await sql `
|
|
52
|
+
SELECT basin_generation FROM ${sql(`${schema}.tenant`)}`;
|
|
53
|
+
// A schema without its Tenant row (store tests) is at generation 0.
|
|
54
|
+
return row ? Number(row.basin_generation) : 0;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
// Deleted between the two reads.
|
|
58
|
+
if (error.code === "42P01")
|
|
59
|
+
return undefined;
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|