@nylorun/runtime 0.22.0-beta → 0.23.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 +13 -0
- package/README.md +17 -3
- package/dist/adapters/execution/restate.d.ts +15 -3
- package/dist/adapters/execution/restate.js +77 -83
- package/dist/adapters/streams/s2.js +1 -13
- package/dist/core/flow-host.d.ts +1 -19
- package/dist/core/flow-host.js +8 -37
- package/dist/execution/types.d.ts +10 -8
- package/dist/execution/types.js +5 -7
- package/dist/host/execution.d.ts +8 -6
- package/dist/host/main.js +10 -16
- package/dist/host/stack-config.d.ts +6 -0
- package/dist/host/stack-config.js +6 -0
- package/dist/infra/execution.d.ts +18 -1
- package/dist/infra/execution.js +28 -4
- package/dist/infra/index.d.ts +1 -1
- package/dist/management-openapi.json +1 -1
- package/dist/openapi.json +1 -1
- package/dist/retry.d.ts +21 -0
- package/dist/retry.js +34 -0
- package/dist/sandbox/join.js +6 -7
- package/dist/sandbox/pods/reconcile.js +3 -3
- package/dist/store/postgres/control.js +8 -1
- package/dist/store/postgres/drizzle/0018_wake_outbox_retries.sql +3 -0
- package/dist/store/postgres/drizzle/0019_sandbox_signal_outbox.sql +18 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +14 -0
- package/dist/store/postgres/schema.d.ts +263 -2
- package/dist/store/postgres/schema.js +34 -3
- package/dist/store/postgres/store.js +165 -52
- package/dist/store/types.d.ts +96 -39
- package/dist/streams/relay/core.js +1 -13
- package/dist/tenant/advance.js +6 -7
- package/dist/tenant/commands.js +1 -2
- package/dist/tenant/context.d.ts +19 -11
- package/dist/tenant/control.d.ts +8 -1
- package/dist/tenant/control.js +2 -0
- package/dist/tenant/runtime.js +23 -3
- package/dist/tenant/sandboxes.d.ts +2 -1
- package/dist/tenant/sandboxes.js +23 -19
- package/dist/tenant/scheduler.d.ts +7 -0
- package/dist/tenant/scheduler.js +38 -7
- package/dist/tenant/session-streams.d.ts +0 -1
- package/dist/tenant/session-streams.js +1 -13
- package/dist/tenant/streams.js +14 -29
- package/dist/tenant/sweep.d.ts +39 -12
- package/dist/tenant/sweep.js +157 -58
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
|
@@ -39,6 +39,12 @@ export interface StackEndpoints {
|
|
|
39
39
|
restateIngressUrl?: string;
|
|
40
40
|
restateAdminUrl?: string;
|
|
41
41
|
workerUrl?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The Worker deployment's version, from `NYLORUN_WORKER_VERSION` (default: the Runtime's
|
|
44
|
+
* version): the Worker registers `<NYLORUN_WORKER_URL>/nylorun/<version>` with Restate
|
|
45
|
+
* (`infra/execution.ts`, `workerDeployment`).
|
|
46
|
+
*/
|
|
47
|
+
workerVersion?: string;
|
|
42
48
|
s2Endpoint?: string;
|
|
43
49
|
s2Token?: string;
|
|
44
50
|
workspaceStoreUrl?: string;
|
|
@@ -281,6 +281,12 @@ export function parseStackConfig(env, argv) {
|
|
|
281
281
|
const workerUrl = parseUrl(env, "NYLORUN_WORKER_URL", http);
|
|
282
282
|
if (workerUrl)
|
|
283
283
|
endpoints.workerUrl = workerUrl;
|
|
284
|
+
const workerVersion = read(env, "NYLORUN_WORKER_VERSION");
|
|
285
|
+
if (workerVersion !== undefined) {
|
|
286
|
+
if (!/^[A-Za-z0-9._-]{1,64}$/.test(workerVersion))
|
|
287
|
+
throw new StackConfigError("NYLORUN_WORKER_VERSION must be 1 to 64 letters, digits, `.`, `_` or `-` (it is a URL path segment)");
|
|
288
|
+
endpoints.workerVersion = workerVersion;
|
|
289
|
+
}
|
|
284
290
|
const s2Endpoint = parseUrl(env, "NYLORUN_S2_ENDPOINT", http);
|
|
285
291
|
if (s2Endpoint)
|
|
286
292
|
endpoints.s2Endpoint = s2Endpoint;
|
|
@@ -29,7 +29,24 @@ export declare function executionKind(config: Pick<StackConfig, "endpoints">): "
|
|
|
29
29
|
export declare function validateExecutionConfig(config: Pick<StackConfig, "endpoints"> & {
|
|
30
30
|
services?: RuntimeServices;
|
|
31
31
|
}): void;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The deployment a Worker registers with Restate (§14.6): `<NYLORUN_WORKER_URL>/nylorun/<version>`,
|
|
34
|
+
* the version being `NYLORUN_WORKER_VERSION` or the Runtime's. Restate sends new invocations to
|
|
35
|
+
* the latest deployment of a service and keeps each running invocation on the deployment it
|
|
36
|
+
* started on, so during a rolling upgrade the Workers of one version share that version's
|
|
37
|
+
* deployment (behind one load-balanced `NYLORUN_WORKER_URL`) and the old one finishes what it
|
|
38
|
+
* started; the endpoint answers on any path, so an older deployment's URL still reaches it.
|
|
39
|
+
* `force` replaces a deployment already registered at the URL with different code: only
|
|
40
|
+
* outside a container (a development Host, whose code changes under one version). Without
|
|
41
|
+
* it Restate keeps the deployment it already has at the URL, and a Worker whose services that
|
|
42
|
+
* deployment lacks refuses to start: a build whose code changed under the same version sets
|
|
43
|
+
* its own `NYLORUN_WORKER_VERSION`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function workerDeployment(config: Pick<StackConfig, "endpoints" | "listen">): {
|
|
46
|
+
url: string;
|
|
47
|
+
force: boolean;
|
|
48
|
+
} | undefined;
|
|
49
|
+
export declare function createExecution(config: Pick<StackConfig, "endpoints" | "listen"> & {
|
|
33
50
|
services?: RuntimeServices;
|
|
34
51
|
}, options?: CreateExecutionOptions): DurableExecution;
|
|
35
52
|
/**
|
package/dist/infra/execution.js
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* - wakes, timers and sweep arming go through the ingress, so every process can
|
|
9
9
|
* call them;
|
|
10
10
|
* - a Worker (a process running the loop service) serves the Restate endpoint on
|
|
11
|
-
* `0.0.0.0:9080` and registers
|
|
12
|
-
* `
|
|
11
|
+
* `0.0.0.0:9080` and registers it with the admin API when `infra/workers.ts` starts it,
|
|
12
|
+
* as a versioned deployment (`workerDeployment`);
|
|
13
13
|
* - with `NYLORUN_RESTATE_IDENTITY_KEY` the endpoint accepts only requests
|
|
14
14
|
* signed by the Restate server holding the matching private key.
|
|
15
15
|
*
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* only unit tests and a local development Host use.
|
|
18
18
|
*/
|
|
19
19
|
import { createRestateExecution } from "../adapters/execution/restate.js";
|
|
20
|
+
import { RUNTIME_VERSION } from "../version.js";
|
|
20
21
|
import { MemoryExecution } from "../execution/memory.js";
|
|
21
22
|
import { DEFAULT_SERVICES, describeServices, } from "../host/stack-config.js";
|
|
22
23
|
/** Where a Worker serves the Restate endpoint (architecture §14.3). */
|
|
@@ -46,16 +47,39 @@ export function validateExecutionConfig(config) {
|
|
|
46
47
|
if (services.has("loop") && !workerUrl)
|
|
47
48
|
throw new Error(`NYLORUN_WORKER_URL is required for --service ${describeServices(services)}: the Worker endpoint URL Restate calls`);
|
|
48
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The deployment a Worker registers with Restate (§14.6): `<NYLORUN_WORKER_URL>/nylorun/<version>`,
|
|
52
|
+
* the version being `NYLORUN_WORKER_VERSION` or the Runtime's. Restate sends new invocations to
|
|
53
|
+
* the latest deployment of a service and keeps each running invocation on the deployment it
|
|
54
|
+
* started on, so during a rolling upgrade the Workers of one version share that version's
|
|
55
|
+
* deployment (behind one load-balanced `NYLORUN_WORKER_URL`) and the old one finishes what it
|
|
56
|
+
* started; the endpoint answers on any path, so an older deployment's URL still reaches it.
|
|
57
|
+
* `force` replaces a deployment already registered at the URL with different code: only
|
|
58
|
+
* outside a container (a development Host, whose code changes under one version). Without
|
|
59
|
+
* it Restate keeps the deployment it already has at the URL, and a Worker whose services that
|
|
60
|
+
* deployment lacks refuses to start: a build whose code changed under the same version sets
|
|
61
|
+
* its own `NYLORUN_WORKER_VERSION`.
|
|
62
|
+
*/
|
|
63
|
+
export function workerDeployment(config) {
|
|
64
|
+
const { workerUrl, workerVersion } = config.endpoints;
|
|
65
|
+
if (!workerUrl)
|
|
66
|
+
return undefined;
|
|
67
|
+
const version = encodeURIComponent(workerVersion ?? RUNTIME_VERSION);
|
|
68
|
+
return { url: `${workerUrl.replace(/\/+$/, "")}/nylorun/${version}`, force: !config.listen };
|
|
69
|
+
}
|
|
49
70
|
export function createExecution(config, options = {}) {
|
|
50
71
|
validateExecutionConfig(config);
|
|
51
|
-
const { restateIngressUrl, restateAdminUrl,
|
|
72
|
+
const { restateIngressUrl, restateAdminUrl, restateIdentityKeys } = config.endpoints;
|
|
52
73
|
if (!restateIngressUrl || !restateAdminUrl)
|
|
53
74
|
return new MemoryExecution();
|
|
75
|
+
const deployment = workerDeployment(config);
|
|
54
76
|
return createRestateExecution({
|
|
55
77
|
ingressUrl: restateIngressUrl,
|
|
56
78
|
adminUrl: restateAdminUrl,
|
|
57
79
|
workerListen: options.workerListen ?? { ...WORKER_LISTEN },
|
|
58
|
-
...(
|
|
80
|
+
...(deployment
|
|
81
|
+
? { workerAdvertisedUrl: deployment.url, forceRegistration: deployment.force }
|
|
82
|
+
: {}),
|
|
59
83
|
...(restateIdentityKeys?.length ? { identityKeys: [...restateIdentityKeys] } : {}),
|
|
60
84
|
...(options.servicePrefix ? { servicePrefix: options.servicePrefix } : {}),
|
|
61
85
|
...(options.logger ? { logger: options.logger } : {}),
|
package/dist/infra/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface Infra {
|
|
|
36
36
|
* Builds the configured clients. Throws naming the variable when endpoints are
|
|
37
37
|
* incomplete (e.g. a Restate ingress without an admin URL).
|
|
38
38
|
*/
|
|
39
|
-
export declare function createInfra(config: Pick<StackConfig, "services" | "endpoints">, options?: {
|
|
39
|
+
export declare function createInfra(config: Pick<StackConfig, "services" | "endpoints" | "listen">, options?: {
|
|
40
40
|
logger?: Logger;
|
|
41
41
|
execution?: CreateExecutionOptions;
|
|
42
42
|
streams?: CreateStreamsOptions;
|
package/dist/openapi.json
CHANGED
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Waits `ms`, or until `signal` aborts; resolves either way and never rejects. */
|
|
2
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
3
|
+
export interface RetryOptions {
|
|
4
|
+
/** The first wait after a failure. Doubled after each, up to `maxMs`. */
|
|
5
|
+
minMs: number;
|
|
6
|
+
maxMs: number;
|
|
7
|
+
/** Attempts, the first included, before giving up. Default: no limit. */
|
|
8
|
+
attempts?: number;
|
|
9
|
+
/** Gives up when the next wait would end later than this long after the first attempt. */
|
|
10
|
+
deadlineMs?: number;
|
|
11
|
+
/** Gives up when it aborts, after the attempt or wait under way. */
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
/** Called with each failure that is retried, and how many attempts have failed so far. */
|
|
14
|
+
onError?: (error: unknown, failures: number) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Runs `attempt` until it resolves, waiting between failures with exponential backoff
|
|
18
|
+
* (`minMs`, doubling to `maxMs`). Rejects with the last failure once `attempts`, `deadlineMs`
|
|
19
|
+
* or `signal` ends the retries.
|
|
20
|
+
*/
|
|
21
|
+
export declare function retry<T>(attempt: () => Promise<T>, options: RetryOptions): Promise<T>;
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waiting and retrying, shared by the Runtime's background loops and its calls to other
|
|
3
|
+
* services. Nothing here logs or decides what is worth retrying: callers do.
|
|
4
|
+
*/
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
+
/** Waits `ms`, or until `signal` aborts; resolves either way and never rejects. */
|
|
7
|
+
export function sleep(ms, signal) {
|
|
8
|
+
return delay(ms, undefined, signal ? { signal } : undefined).catch(() => undefined);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Runs `attempt` until it resolves, waiting between failures with exponential backoff
|
|
12
|
+
* (`minMs`, doubling to `maxMs`). Rejects with the last failure once `attempts`, `deadlineMs`
|
|
13
|
+
* or `signal` ends the retries.
|
|
14
|
+
*/
|
|
15
|
+
export async function retry(attempt, options) {
|
|
16
|
+
const deadline = options.deadlineMs === undefined ? Infinity : Date.now() + options.deadlineMs;
|
|
17
|
+
let wait = options.minMs;
|
|
18
|
+
for (let failures = 1;; failures += 1) {
|
|
19
|
+
try {
|
|
20
|
+
return await attempt();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (failures >= (options.attempts ?? Infinity) ||
|
|
24
|
+
Date.now() + wait > deadline ||
|
|
25
|
+
options.signal?.aborted)
|
|
26
|
+
throw error;
|
|
27
|
+
options.onError?.(error, failures);
|
|
28
|
+
await sleep(wait, options.signal);
|
|
29
|
+
if (options.signal?.aborted)
|
|
30
|
+
throw error;
|
|
31
|
+
wait = Math.min(wait * 2, options.maxMs);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/sandbox/join.js
CHANGED
|
@@ -134,6 +134,9 @@ export function hostAuthority(ctx) {
|
|
|
134
134
|
const reason = "The sandbox's volume was replaced: its files are gone";
|
|
135
135
|
await t.updateSandboxPod(sandboxId, { observed: "lost", reason, hostEpoch: epoch }, now);
|
|
136
136
|
await t.sandboxEvent(sandboxId, "sandbox.lost", { reason });
|
|
137
|
+
// Its Sandbox is deleted; only a reset brings the sandbox back.
|
|
138
|
+
await t.signal({ type: "host.revoked", sandboxId, epoch });
|
|
139
|
+
await t.signalSandbox(sandboxId, { kind: "reconcile" });
|
|
137
140
|
return { lost: epoch };
|
|
138
141
|
}
|
|
139
142
|
await t.updateSandboxPod(sandboxId, {
|
|
@@ -148,16 +151,12 @@ export function hostAuthority(ctx) {
|
|
|
148
151
|
else
|
|
149
152
|
await t.sandboxEvent(sandboxId, "sandbox.running", { hostEpoch: epoch });
|
|
150
153
|
}
|
|
154
|
+
// The previous host's connection (an older epoch) serves nothing more, on any process.
|
|
155
|
+
await t.signal({ type: "host.revoked", sandboxId, epoch });
|
|
151
156
|
return epoch;
|
|
152
157
|
});
|
|
153
|
-
if (typeof joined === "object")
|
|
154
|
-
ctx.harness.revokeHost(sandboxId, joined.lost);
|
|
155
|
-
// Its Sandbox is deleted; only a reset brings the sandbox back.
|
|
156
|
-
await ctx.sandboxSignal(sandboxId, { kind: "reconcile" });
|
|
158
|
+
if (typeof joined === "object")
|
|
157
159
|
return refuse("the volume was replaced: the sandbox is lost");
|
|
158
|
-
}
|
|
159
|
-
// The previous host's connection (an older epoch) serves nothing more.
|
|
160
|
-
ctx.harness.revokeHost(sandboxId, joined);
|
|
161
160
|
ctx.config.logger.info("sandbox host joined", { sandboxId, epoch: joined });
|
|
162
161
|
return answerOf(await mintHostTokens(ctx, { sandboxId, epoch: joined, podUid }), await ctx.keys.egressCa());
|
|
163
162
|
},
|
|
@@ -135,7 +135,9 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
135
135
|
await t.updateSandboxPod(sandboxId, finalPatch, new Date().toISOString());
|
|
136
136
|
for (const item of decision.events)
|
|
137
137
|
await t.sandboxEvent(sandboxId, item.type, item.payload);
|
|
138
|
-
|
|
138
|
+
if (epochMoved)
|
|
139
|
+
await t.signal({ type: "host.revoked", sandboxId, epoch: finalPatch.hostEpoch });
|
|
140
|
+
return { kind: "written" };
|
|
139
141
|
});
|
|
140
142
|
if (written.kind === "changed")
|
|
141
143
|
return { retryAfterMs: 0 };
|
|
@@ -143,8 +145,6 @@ export async function reconcileSandbox(ctx, sandboxId, trigger) {
|
|
|
143
145
|
await ctx.sandbox.removeSandbox(sandboxId).catch(() => undefined);
|
|
144
146
|
return {};
|
|
145
147
|
}
|
|
146
|
-
if (written.kind === "written" && written.epoch !== undefined)
|
|
147
|
-
ctx.harness.revokeHost(sandboxId, written.epoch);
|
|
148
148
|
return {
|
|
149
149
|
...(decision.retryAfterMs === undefined ? {} : { retryAfterMs: decision.retryAfterMs }),
|
|
150
150
|
...(decision.arm.length > 0 ? { arm: decision.arm } : {}),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The control bus on Postgres (blueprint D21, D48): how one process tells the others with the
|
|
3
3
|
* Tenant open to act now. `session.cancel` aborts the advance of a cancelled turn;
|
|
4
|
-
* `sessions.reset` moves the session streams to the Tenant's new basin generation
|
|
4
|
+
* `sessions.reset` moves the session streams to the Tenant's new basin generation;
|
|
5
|
+
* `host.revoked` closes a pod sandbox's host connections of an older epoch. Nothing
|
|
5
6
|
* internal goes through S2, which only serves API listeners.
|
|
6
7
|
*
|
|
7
8
|
* - **Write.** `writeSignal` inserts a `control_signals` row and calls `pg_notify` on
|
|
@@ -36,6 +37,8 @@ const SIGNAL = {
|
|
|
36
37
|
sessionId: controlSignals.sessionId,
|
|
37
38
|
turnId: controlSignals.turnId,
|
|
38
39
|
generation: controlSignals.generation,
|
|
40
|
+
sandboxId: controlSignals.sandboxId,
|
|
41
|
+
epoch: controlSignals.epoch,
|
|
39
42
|
};
|
|
40
43
|
/** Puts `signal` on the bus in the transaction `db` runs: a row, notified at commit. */
|
|
41
44
|
export async function writeSignal(db, signal) {
|
|
@@ -46,6 +49,8 @@ export async function writeSignal(db, signal) {
|
|
|
46
49
|
sessionId: signal.type === "session.cancel" ? signal.sessionId : null,
|
|
47
50
|
turnId: signal.type === "session.cancel" ? (signal.turnId ?? null) : null,
|
|
48
51
|
generation: signal.type === "sessions.reset" ? signal.generation : null,
|
|
52
|
+
sandboxId: signal.type === "host.revoked" ? signal.sandboxId : null,
|
|
53
|
+
epoch: signal.type === "host.revoked" ? signal.epoch : null,
|
|
49
54
|
})
|
|
50
55
|
.returning({ id: controlSignals.id });
|
|
51
56
|
// The payload is only the id: a follower reads the rows back either way.
|
|
@@ -183,5 +188,7 @@ function signalOf(row) {
|
|
|
183
188
|
};
|
|
184
189
|
if (row.kind === "sessions.reset" && row.generation !== null)
|
|
185
190
|
return { type: "sessions.reset", generation: row.generation };
|
|
191
|
+
if (row.kind === "host.revoked" && row.sandboxId !== null && row.epoch !== null)
|
|
192
|
+
return { type: "host.revoked", sandboxId: row.sandboxId, epoch: row.epoch };
|
|
186
193
|
return undefined;
|
|
187
194
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
ALTER TABLE "nylorun"."wakes" ADD COLUMN "attempts" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
|
2
|
+
ALTER TABLE "nylorun"."wakes" ADD COLUMN "retry_at" timestamp with time zone;--> statement-breakpoint
|
|
3
|
+
ALTER TABLE "nylorun"."wakes" ADD COLUMN "parked_at" timestamp with time zone;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
CREATE TABLE "nylorun"."sandbox_signals" (
|
|
2
|
+
"id" text COLLATE "C" PRIMARY KEY NOT NULL,
|
|
3
|
+
"sandbox_id" text COLLATE "C" NOT NULL,
|
|
4
|
+
"kind" text NOT NULL,
|
|
5
|
+
"timer" text,
|
|
6
|
+
"at" bigint,
|
|
7
|
+
"created_at" timestamp with time zone DEFAULT clock_timestamp() NOT NULL,
|
|
8
|
+
"attempts" integer DEFAULT 0 NOT NULL,
|
|
9
|
+
"retry_at" timestamp with time zone,
|
|
10
|
+
"parked_at" timestamp with time zone,
|
|
11
|
+
CONSTRAINT "sandbox_signals_kind_check" CHECK ("nylorun"."sandbox_signals"."kind" IN ('reconcile', 'arm'))
|
|
12
|
+
);
|
|
13
|
+
--> statement-breakpoint
|
|
14
|
+
ALTER TABLE "nylorun"."control_signals" DROP CONSTRAINT "control_signals_kind_check";--> statement-breakpoint
|
|
15
|
+
ALTER TABLE "nylorun"."control_signals" ADD COLUMN "sandbox_id" text COLLATE "C";--> statement-breakpoint
|
|
16
|
+
ALTER TABLE "nylorun"."control_signals" ADD COLUMN "epoch" integer;--> statement-breakpoint
|
|
17
|
+
CREATE INDEX "sandbox_signals_created_at" ON "nylorun"."sandbox_signals" USING btree ("created_at");--> statement-breakpoint
|
|
18
|
+
ALTER TABLE "nylorun"."control_signals" ADD CONSTRAINT "control_signals_kind_check" CHECK ("nylorun"."control_signals"."kind" IN ('session.cancel', 'sessions.reset', 'host.revoked'));
|
|
@@ -127,6 +127,20 @@
|
|
|
127
127
|
"when": 1791455573196,
|
|
128
128
|
"tag": "0017_wake_outbox",
|
|
129
129
|
"breakpoints": true
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"idx": 18,
|
|
133
|
+
"version": "7",
|
|
134
|
+
"when": 1791529391438,
|
|
135
|
+
"tag": "0018_wake_outbox_retries",
|
|
136
|
+
"breakpoints": true
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"idx": 19,
|
|
140
|
+
"version": "7",
|
|
141
|
+
"when": 1791530515498,
|
|
142
|
+
"tag": "0019_sandbox_signal_outbox",
|
|
143
|
+
"breakpoints": true
|
|
130
144
|
}
|
|
131
145
|
]
|
|
132
146
|
}
|
|
@@ -3225,7 +3225,8 @@ export declare const definitionFileUses: import("drizzle-orm/pg-core").PgTableWi
|
|
|
3225
3225
|
* written in the transaction that commits what they announce and notified on
|
|
3226
3226
|
* `nylorun_control` at commit (`control.ts`). A row outlives its notification so a process
|
|
3227
3227
|
* whose listener was down catches up; the Tenant sweep deletes old ones. `created_at` is the
|
|
3228
|
-
* insert's own clock (`clock_timestamp()`), not its transaction's start.
|
|
3228
|
+
* insert's own clock (`clock_timestamp()`), not its transaction's start. `sandbox_id` and
|
|
3229
|
+
* `epoch` are a `host.revoked` signal's.
|
|
3229
3230
|
*/
|
|
3230
3231
|
export declare const controlSignals: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
3231
3232
|
name: "control_signals";
|
|
@@ -3337,6 +3338,42 @@ export declare const controlSignals: import("drizzle-orm/pg-core").PgTableWithCo
|
|
|
3337
3338
|
identity: undefined;
|
|
3338
3339
|
generated: undefined;
|
|
3339
3340
|
}, {}, {}>;
|
|
3341
|
+
sandboxId: import("drizzle-orm/pg-core").PgColumn<{
|
|
3342
|
+
name: "sandboxId";
|
|
3343
|
+
tableName: "control_signals";
|
|
3344
|
+
dataType: "custom";
|
|
3345
|
+
columnType: "PgCustomColumn";
|
|
3346
|
+
data: string;
|
|
3347
|
+
driverParam: string;
|
|
3348
|
+
notNull: false;
|
|
3349
|
+
hasDefault: false;
|
|
3350
|
+
isPrimaryKey: false;
|
|
3351
|
+
isAutoincrement: false;
|
|
3352
|
+
hasRuntimeDefault: false;
|
|
3353
|
+
enumValues: undefined;
|
|
3354
|
+
baseColumn: never;
|
|
3355
|
+
identity: undefined;
|
|
3356
|
+
generated: undefined;
|
|
3357
|
+
}, {}, {
|
|
3358
|
+
pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
|
|
3359
|
+
}>;
|
|
3360
|
+
epoch: import("drizzle-orm/pg-core").PgColumn<{
|
|
3361
|
+
name: "epoch";
|
|
3362
|
+
tableName: "control_signals";
|
|
3363
|
+
dataType: "number";
|
|
3364
|
+
columnType: "PgInteger";
|
|
3365
|
+
data: number;
|
|
3366
|
+
driverParam: string | number;
|
|
3367
|
+
notNull: false;
|
|
3368
|
+
hasDefault: false;
|
|
3369
|
+
isPrimaryKey: false;
|
|
3370
|
+
isAutoincrement: false;
|
|
3371
|
+
hasRuntimeDefault: false;
|
|
3372
|
+
enumValues: undefined;
|
|
3373
|
+
baseColumn: never;
|
|
3374
|
+
identity: undefined;
|
|
3375
|
+
generated: undefined;
|
|
3376
|
+
}, {}, {}>;
|
|
3340
3377
|
};
|
|
3341
3378
|
dialect: 'pg';
|
|
3342
3379
|
}>;
|
|
@@ -3345,7 +3382,9 @@ export declare const controlSignals: import("drizzle-orm/pg-core").PgTableWithCo
|
|
|
3345
3382
|
* (`Tx.wake`), written in that transaction, so a commit never loses its wake. The row is
|
|
3346
3383
|
* deleted once Durable Session Execution has accepted the wake; until then the Tenant sweep
|
|
3347
3384
|
* delivers it again, with the same idempotency key (`dedupe_key`, or `wake:<id>` without
|
|
3348
|
-
* one). `created_at` is the insert's own clock (`clock_timestamp()`).
|
|
3385
|
+
* one). `created_at` is the insert's own clock (`clock_timestamp()`). `attempts` counts the
|
|
3386
|
+
* sweep's failed deliveries, `retry_at` is when it tries the row again, and a row
|
|
3387
|
+
* `parked_at` is never tried again: it stays for an operator (`Tx.failOutbox`).
|
|
3349
3388
|
*/
|
|
3350
3389
|
export declare const wakes: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
3351
3390
|
name: "wakes";
|
|
@@ -3440,6 +3479,228 @@ export declare const wakes: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
3440
3479
|
identity: undefined;
|
|
3441
3480
|
generated: undefined;
|
|
3442
3481
|
}, {}, {}>;
|
|
3482
|
+
attempts: import("drizzle-orm/pg-core").PgColumn<{
|
|
3483
|
+
name: "attempts";
|
|
3484
|
+
tableName: "wakes";
|
|
3485
|
+
dataType: "number";
|
|
3486
|
+
columnType: "PgInteger";
|
|
3487
|
+
data: number;
|
|
3488
|
+
driverParam: string | number;
|
|
3489
|
+
notNull: true;
|
|
3490
|
+
hasDefault: true;
|
|
3491
|
+
isPrimaryKey: false;
|
|
3492
|
+
isAutoincrement: false;
|
|
3493
|
+
hasRuntimeDefault: false;
|
|
3494
|
+
enumValues: undefined;
|
|
3495
|
+
baseColumn: never;
|
|
3496
|
+
identity: undefined;
|
|
3497
|
+
generated: undefined;
|
|
3498
|
+
}, {}, {}>;
|
|
3499
|
+
retryAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
3500
|
+
name: "retryAt";
|
|
3501
|
+
tableName: "wakes";
|
|
3502
|
+
dataType: "date";
|
|
3503
|
+
columnType: "PgTimestamp";
|
|
3504
|
+
data: Date;
|
|
3505
|
+
driverParam: string;
|
|
3506
|
+
notNull: false;
|
|
3507
|
+
hasDefault: false;
|
|
3508
|
+
isPrimaryKey: false;
|
|
3509
|
+
isAutoincrement: false;
|
|
3510
|
+
hasRuntimeDefault: false;
|
|
3511
|
+
enumValues: undefined;
|
|
3512
|
+
baseColumn: never;
|
|
3513
|
+
identity: undefined;
|
|
3514
|
+
generated: undefined;
|
|
3515
|
+
}, {}, {}>;
|
|
3516
|
+
parkedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
3517
|
+
name: "parkedAt";
|
|
3518
|
+
tableName: "wakes";
|
|
3519
|
+
dataType: "date";
|
|
3520
|
+
columnType: "PgTimestamp";
|
|
3521
|
+
data: Date;
|
|
3522
|
+
driverParam: string;
|
|
3523
|
+
notNull: false;
|
|
3524
|
+
hasDefault: false;
|
|
3525
|
+
isPrimaryKey: false;
|
|
3526
|
+
isAutoincrement: false;
|
|
3527
|
+
hasRuntimeDefault: false;
|
|
3528
|
+
enumValues: undefined;
|
|
3529
|
+
baseColumn: never;
|
|
3530
|
+
identity: undefined;
|
|
3531
|
+
generated: undefined;
|
|
3532
|
+
}, {}, {}>;
|
|
3533
|
+
};
|
|
3534
|
+
dialect: 'pg';
|
|
3535
|
+
}>;
|
|
3536
|
+
/**
|
|
3537
|
+
* The sandbox signal outbox (F7.2): one row per pod sandbox reconcile or timer a committed
|
|
3538
|
+
* transaction asked for (`Tx.signalSandbox`), kept and delivered as the wake outbox's rows are
|
|
3539
|
+
* (`attempts`, `retry_at`, `parked_at`). A reconcile is sent with the idempotency key
|
|
3540
|
+
* `signal:<id>`; a timer (`kind` `arm`) is its `timer` at `at` (ms since the epoch). Not in
|
|
3541
|
+
* `wakes`, so a Runtime that delivers only wakes never reads one.
|
|
3542
|
+
*/
|
|
3543
|
+
export declare const sandboxSignals: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
3544
|
+
name: "sandbox_signals";
|
|
3545
|
+
schema: "nylorun";
|
|
3546
|
+
columns: {
|
|
3547
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
3548
|
+
name: "id";
|
|
3549
|
+
tableName: "sandbox_signals";
|
|
3550
|
+
dataType: "custom";
|
|
3551
|
+
columnType: "PgCustomColumn";
|
|
3552
|
+
data: string;
|
|
3553
|
+
driverParam: string;
|
|
3554
|
+
notNull: true;
|
|
3555
|
+
hasDefault: false;
|
|
3556
|
+
isPrimaryKey: true;
|
|
3557
|
+
isAutoincrement: false;
|
|
3558
|
+
hasRuntimeDefault: false;
|
|
3559
|
+
enumValues: undefined;
|
|
3560
|
+
baseColumn: never;
|
|
3561
|
+
identity: undefined;
|
|
3562
|
+
generated: undefined;
|
|
3563
|
+
}, {}, {
|
|
3564
|
+
pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
|
|
3565
|
+
}>;
|
|
3566
|
+
sandboxId: import("drizzle-orm/pg-core").PgColumn<{
|
|
3567
|
+
name: "sandboxId";
|
|
3568
|
+
tableName: "sandbox_signals";
|
|
3569
|
+
dataType: "custom";
|
|
3570
|
+
columnType: "PgCustomColumn";
|
|
3571
|
+
data: string;
|
|
3572
|
+
driverParam: string;
|
|
3573
|
+
notNull: true;
|
|
3574
|
+
hasDefault: false;
|
|
3575
|
+
isPrimaryKey: false;
|
|
3576
|
+
isAutoincrement: false;
|
|
3577
|
+
hasRuntimeDefault: false;
|
|
3578
|
+
enumValues: undefined;
|
|
3579
|
+
baseColumn: never;
|
|
3580
|
+
identity: undefined;
|
|
3581
|
+
generated: undefined;
|
|
3582
|
+
}, {}, {
|
|
3583
|
+
pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
|
|
3584
|
+
}>;
|
|
3585
|
+
kind: import("drizzle-orm/pg-core").PgColumn<{
|
|
3586
|
+
name: "kind";
|
|
3587
|
+
tableName: "sandbox_signals";
|
|
3588
|
+
dataType: "string";
|
|
3589
|
+
columnType: "PgText";
|
|
3590
|
+
data: string;
|
|
3591
|
+
driverParam: string;
|
|
3592
|
+
notNull: true;
|
|
3593
|
+
hasDefault: false;
|
|
3594
|
+
isPrimaryKey: false;
|
|
3595
|
+
isAutoincrement: false;
|
|
3596
|
+
hasRuntimeDefault: false;
|
|
3597
|
+
enumValues: [string, ...string[]];
|
|
3598
|
+
baseColumn: never;
|
|
3599
|
+
identity: undefined;
|
|
3600
|
+
generated: undefined;
|
|
3601
|
+
}, {}, {}>;
|
|
3602
|
+
timer: import("drizzle-orm/pg-core").PgColumn<{
|
|
3603
|
+
name: "timer";
|
|
3604
|
+
tableName: "sandbox_signals";
|
|
3605
|
+
dataType: "string";
|
|
3606
|
+
columnType: "PgText";
|
|
3607
|
+
data: string;
|
|
3608
|
+
driverParam: string;
|
|
3609
|
+
notNull: false;
|
|
3610
|
+
hasDefault: false;
|
|
3611
|
+
isPrimaryKey: false;
|
|
3612
|
+
isAutoincrement: false;
|
|
3613
|
+
hasRuntimeDefault: false;
|
|
3614
|
+
enumValues: [string, ...string[]];
|
|
3615
|
+
baseColumn: never;
|
|
3616
|
+
identity: undefined;
|
|
3617
|
+
generated: undefined;
|
|
3618
|
+
}, {}, {}>;
|
|
3619
|
+
at: import("drizzle-orm/pg-core").PgColumn<{
|
|
3620
|
+
name: "at";
|
|
3621
|
+
tableName: "sandbox_signals";
|
|
3622
|
+
dataType: "number";
|
|
3623
|
+
columnType: "PgBigInt53";
|
|
3624
|
+
data: number;
|
|
3625
|
+
driverParam: string | number;
|
|
3626
|
+
notNull: false;
|
|
3627
|
+
hasDefault: false;
|
|
3628
|
+
isPrimaryKey: false;
|
|
3629
|
+
isAutoincrement: false;
|
|
3630
|
+
hasRuntimeDefault: false;
|
|
3631
|
+
enumValues: undefined;
|
|
3632
|
+
baseColumn: never;
|
|
3633
|
+
identity: undefined;
|
|
3634
|
+
generated: undefined;
|
|
3635
|
+
}, {}, {}>;
|
|
3636
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
3637
|
+
name: "createdAt";
|
|
3638
|
+
tableName: "sandbox_signals";
|
|
3639
|
+
dataType: "date";
|
|
3640
|
+
columnType: "PgTimestamp";
|
|
3641
|
+
data: Date;
|
|
3642
|
+
driverParam: string;
|
|
3643
|
+
notNull: true;
|
|
3644
|
+
hasDefault: true;
|
|
3645
|
+
isPrimaryKey: false;
|
|
3646
|
+
isAutoincrement: false;
|
|
3647
|
+
hasRuntimeDefault: false;
|
|
3648
|
+
enumValues: undefined;
|
|
3649
|
+
baseColumn: never;
|
|
3650
|
+
identity: undefined;
|
|
3651
|
+
generated: undefined;
|
|
3652
|
+
}, {}, {}>;
|
|
3653
|
+
attempts: import("drizzle-orm/pg-core").PgColumn<{
|
|
3654
|
+
name: "attempts";
|
|
3655
|
+
tableName: "sandbox_signals";
|
|
3656
|
+
dataType: "number";
|
|
3657
|
+
columnType: "PgInteger";
|
|
3658
|
+
data: number;
|
|
3659
|
+
driverParam: string | number;
|
|
3660
|
+
notNull: true;
|
|
3661
|
+
hasDefault: true;
|
|
3662
|
+
isPrimaryKey: false;
|
|
3663
|
+
isAutoincrement: false;
|
|
3664
|
+
hasRuntimeDefault: false;
|
|
3665
|
+
enumValues: undefined;
|
|
3666
|
+
baseColumn: never;
|
|
3667
|
+
identity: undefined;
|
|
3668
|
+
generated: undefined;
|
|
3669
|
+
}, {}, {}>;
|
|
3670
|
+
retryAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
3671
|
+
name: "retryAt";
|
|
3672
|
+
tableName: "sandbox_signals";
|
|
3673
|
+
dataType: "date";
|
|
3674
|
+
columnType: "PgTimestamp";
|
|
3675
|
+
data: Date;
|
|
3676
|
+
driverParam: string;
|
|
3677
|
+
notNull: false;
|
|
3678
|
+
hasDefault: false;
|
|
3679
|
+
isPrimaryKey: false;
|
|
3680
|
+
isAutoincrement: false;
|
|
3681
|
+
hasRuntimeDefault: false;
|
|
3682
|
+
enumValues: undefined;
|
|
3683
|
+
baseColumn: never;
|
|
3684
|
+
identity: undefined;
|
|
3685
|
+
generated: undefined;
|
|
3686
|
+
}, {}, {}>;
|
|
3687
|
+
parkedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
3688
|
+
name: "parkedAt";
|
|
3689
|
+
tableName: "sandbox_signals";
|
|
3690
|
+
dataType: "date";
|
|
3691
|
+
columnType: "PgTimestamp";
|
|
3692
|
+
data: Date;
|
|
3693
|
+
driverParam: string;
|
|
3694
|
+
notNull: false;
|
|
3695
|
+
hasDefault: false;
|
|
3696
|
+
isPrimaryKey: false;
|
|
3697
|
+
isAutoincrement: false;
|
|
3698
|
+
hasRuntimeDefault: false;
|
|
3699
|
+
enumValues: undefined;
|
|
3700
|
+
baseColumn: never;
|
|
3701
|
+
identity: undefined;
|
|
3702
|
+
generated: undefined;
|
|
3703
|
+
}, {}, {}>;
|
|
3443
3704
|
};
|
|
3444
3705
|
dialect: 'pg';
|
|
3445
3706
|
}>;
|