lopata 0.18.4 → 0.19.0
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/dist/types/api/dispatch.d.ts +2 -2
- package/dist/types/api/handlers/workflows.d.ts +2 -2
- package/dist/types/bindings/container-cleanup.d.ts +43 -0
- package/dist/types/bindings/container-docker.d.ts +24 -0
- package/dist/types/bindings/container.d.ts +4 -2
- package/dist/types/bindings/do-executor-worker.d.ts +73 -89
- package/dist/types/bindings/do-executor.d.ts +22 -1
- package/dist/types/bindings/do-worker-env.d.ts +16 -7
- package/dist/types/bindings/durable-object.d.ts +66 -4
- package/dist/types/bindings/queue.d.ts +4 -1
- package/dist/types/bindings/rpc-stub.d.ts +23 -1
- package/dist/types/bindings/scheduled.d.ts +13 -3
- package/dist/types/bindings/service-binding.d.ts +13 -4
- package/dist/types/bindings/static-assets.d.ts +1 -1
- package/dist/types/bindings/websocket-pair.d.ts +12 -3
- package/dist/types/bindings/workflow.d.ts +29 -0
- package/dist/types/config.d.ts +2 -0
- package/dist/types/env.d.ts +6 -0
- package/dist/types/error-page-render.d.ts +6 -0
- package/dist/types/execution-context.d.ts +8 -0
- package/dist/types/generation-manager.d.ts +21 -2
- package/dist/types/generation.d.ts +16 -21
- package/dist/types/import-graph.d.ts +28 -0
- package/dist/types/lopata-config.d.ts +3 -3
- package/dist/types/plugin.d.ts +4 -1
- package/dist/types/rpc-validate.d.ts +9 -0
- package/dist/types/setup-globals.d.ts +5 -1
- package/dist/types/tracing/context.d.ts +12 -0
- package/dist/types/tracing/span.d.ts +21 -3
- package/dist/types/tracing/store.d.ts +17 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dist/types/vite-plugin/index.d.ts +8 -1
- package/dist/types/worker-registry.d.ts +11 -4
- package/dist/types/worker-thread/do-protocol.d.ts +256 -0
- package/dist/types/worker-thread/entry.d.ts +2 -0
- package/dist/types/worker-thread/execution-context.d.ts +22 -0
- package/dist/types/worker-thread/executor.d.ts +123 -0
- package/dist/types/worker-thread/protocol.d.ts +553 -0
- package/dist/types/worker-thread/remote-trace-store.d.ts +27 -0
- package/dist/types/worker-thread/rpc-client.d.ts +4 -0
- package/dist/types/worker-thread/rpc-shared.d.ts +138 -0
- package/dist/types/worker-thread/serialize.d.ts +12 -0
- package/dist/types/worker-thread/stream-shared.d.ts +147 -0
- package/dist/types/worker-thread/thread-env.d.ts +42 -0
- package/dist/types/worker-thread/wire-handlers.d.ts +16 -0
- package/dist/types/worker-thread/ws-bridge-shared.d.ts +163 -0
- package/package.json +1 -1
- package/src/api/handlers/containers.ts +2 -1
- package/src/api/handlers/workflows.ts +39 -34
- package/src/bindings/container-cleanup.ts +125 -0
- package/src/bindings/container-docker.ts +49 -34
- package/src/bindings/container.ts +24 -9
- package/src/bindings/do-executor-inprocess.ts +9 -5
- package/src/bindings/do-executor-worker.ts +386 -158
- package/src/bindings/do-executor.ts +23 -1
- package/src/bindings/do-worker-entry.ts +242 -60
- package/src/bindings/do-worker-env.ts +296 -11
- package/src/bindings/durable-object.ts +231 -27
- package/src/bindings/email.ts +6 -0
- package/src/bindings/queue.ts +11 -1
- package/src/bindings/rpc-stub.ts +79 -10
- package/src/bindings/scheduled.ts +37 -30
- package/src/bindings/service-binding.ts +87 -33
- package/src/bindings/static-assets.ts +3 -2
- package/src/bindings/websocket-pair.ts +19 -3
- package/src/bindings/workflow.ts +91 -0
- package/src/cli/dev.ts +106 -41
- package/src/config.ts +6 -3
- package/src/db.ts +1 -0
- package/src/env.ts +40 -21
- package/src/error-page-render.ts +21 -0
- package/src/execution-context.ts +13 -3
- package/src/generation-manager.ts +144 -143
- package/src/generation.ts +150 -306
- package/src/import-graph.ts +140 -0
- package/src/lopata-config.ts +3 -3
- package/src/plugin.ts +7 -0
- package/src/rpc-validate.ts +29 -0
- package/src/setup-globals.ts +6 -17
- package/src/testing/durable-object.ts +5 -3
- package/src/testing/index.ts +8 -4
- package/src/tracing/context.ts +18 -0
- package/src/tracing/span.ts +83 -57
- package/src/tracing/store.ts +41 -3
- package/src/virtual-modules.ts +2 -0
- package/src/vite-plugin/dev-server-plugin.ts +4 -0
- package/src/vite-plugin/index.ts +8 -1
- package/src/worker-registry.ts +15 -2
- package/src/worker-thread/do-protocol.ts +237 -0
- package/src/worker-thread/entry.ts +453 -0
- package/src/worker-thread/execution-context.ts +53 -0
- package/src/worker-thread/executor.ts +595 -0
- package/src/worker-thread/protocol.ts +552 -0
- package/src/worker-thread/remote-trace-store.ts +90 -0
- package/src/worker-thread/rpc-client.ts +5 -0
- package/src/worker-thread/rpc-shared.ts +503 -0
- package/src/worker-thread/serialize.ts +37 -0
- package/src/worker-thread/stream-shared.ts +414 -0
- package/src/worker-thread/thread-env.ts +350 -0
- package/src/worker-thread/wire-handlers.ts +80 -0
- package/src/worker-thread/ws-bridge-shared.ts +482 -0
- package/dist/types/bindings/do-websocket-bridge.d.ts +0 -60
- package/dist/types/module-cache.d.ts +0 -23
- package/src/bindings/do-websocket-bridge.ts +0 -79
- package/src/module-cache.ts +0 -58
- package/src/tracing/global.d.ts +0 -50
|
@@ -238,10 +238,10 @@ declare const allHandlers: {
|
|
|
238
238
|
name: string;
|
|
239
239
|
status?: string;
|
|
240
240
|
}): import("./types").WorkflowInstance[];
|
|
241
|
-
'workflows.getInstance'({ id }: {
|
|
241
|
+
'workflows.getInstance'({ name, id }: {
|
|
242
242
|
name: string;
|
|
243
243
|
id: string;
|
|
244
|
-
}): import("./types").WorkflowDetail
|
|
244
|
+
}, ctx: HandlerContext): Promise<import("./types").WorkflowDetail>;
|
|
245
245
|
'workflows.terminate'({ name, id }: {
|
|
246
246
|
name: string;
|
|
247
247
|
id: string;
|
|
@@ -5,10 +5,10 @@ export declare const handlers: {
|
|
|
5
5
|
name: string;
|
|
6
6
|
status?: string;
|
|
7
7
|
}): WorkflowInstance[];
|
|
8
|
-
'workflows.getInstance'({ id }: {
|
|
8
|
+
'workflows.getInstance'({ name, id }: {
|
|
9
9
|
name: string;
|
|
10
10
|
id: string;
|
|
11
|
-
}): WorkflowDetail
|
|
11
|
+
}, ctx: HandlerContext): Promise<WorkflowDetail>;
|
|
12
12
|
'workflows.terminate'({ name, id }: {
|
|
13
13
|
name: string;
|
|
14
14
|
id: string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main-side registry of active Docker containers spawned by lopata.
|
|
3
|
+
*
|
|
4
|
+
* Containers come from two places: the in-process path (dashboard API
|
|
5
|
+
* handlers + the legacy/vite-plugin executor in `env.ts`) and the DO worker
|
|
6
|
+
* threads. DO workers call `register` / `unregister` via postMessage
|
|
7
|
+
* (`container-registered` / `container-removed` in `do-executor-worker.ts`);
|
|
8
|
+
* the in-process callers invoke the helpers directly through the
|
|
9
|
+
* `DockerManager` constructor callbacks.
|
|
10
|
+
*
|
|
11
|
+
* Two safety nets:
|
|
12
|
+
*
|
|
13
|
+
* 1. An `exit` listener `docker rm -f`s anything still tracked at process
|
|
14
|
+
* shutdown. Synchronous (`Bun.spawnSync`) since `exit` doesn't await.
|
|
15
|
+
*
|
|
16
|
+
* 2. `reapOrphanContainers()` scans for containers labeled `lopata=1`
|
|
17
|
+
* whose `lopata.pid` is no longer alive — cleans up leftovers from
|
|
18
|
+
* crashes / SIGKILL where (1) never ran. Invoked once at lopata
|
|
19
|
+
* startup from `cli/dev.ts`.
|
|
20
|
+
*/
|
|
21
|
+
/** Marker label on every lopata-managed container. */
|
|
22
|
+
export declare const LOPATA_LABEL_KEY = "lopata";
|
|
23
|
+
export declare const LOPATA_LABEL_VALUE = "1";
|
|
24
|
+
/** Pid sub-label so the reaper can detect orphans of dead processes. */
|
|
25
|
+
export declare const LOPATA_PID_LABEL_KEY = "lopata.pid";
|
|
26
|
+
/** Labels passed to `DockerManager` (constructor) for every `docker run`. */
|
|
27
|
+
export declare function containerLabels(): Record<string, string>;
|
|
28
|
+
export declare function registerContainer(name: string): void;
|
|
29
|
+
export declare function unregisterContainer(name: string): void;
|
|
30
|
+
/** @internal Test hook — current tracked container set. */
|
|
31
|
+
export declare function _activeContainersSnapshot(): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Reap containers labeled `lopata=1` whose `lopata.pid` is no longer alive.
|
|
34
|
+
* Returns the number of orphans removed. Silently returns 0 if docker isn't
|
|
35
|
+
* available so non-container users aren't penalised.
|
|
36
|
+
*/
|
|
37
|
+
export declare function reapOrphanContainers(): Promise<number>;
|
|
38
|
+
/**
|
|
39
|
+
* @internal Pure-function core of {@link reapOrphanContainers}, exported for
|
|
40
|
+
* tests. Parses the `docker ps` `{{.Names}}|{{.Label ...}}` table and returns
|
|
41
|
+
* the container names whose owner pid is unknown, unparseable, or dead.
|
|
42
|
+
*/
|
|
43
|
+
export declare function _selectOrphanNames(listOut: string, alive: (pid: number) => boolean): string[];
|
|
@@ -12,8 +12,26 @@ export interface DockerContainerInfo {
|
|
|
12
12
|
state: string;
|
|
13
13
|
exitCode: number | null;
|
|
14
14
|
ports: Record<string, string>;
|
|
15
|
+
/** Container labels (e.g. `lopata.pid`) — used to detect a container left by a
|
|
16
|
+
* crashed/foreign lopata process before adopting it by name. */
|
|
17
|
+
labels: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export interface DockerManagerOptions {
|
|
20
|
+
/** Called after a container is successfully created via `run()`. */
|
|
21
|
+
onRegister?: (name: string) => void;
|
|
22
|
+
/** Called after a container is removed via `remove()`. */
|
|
23
|
+
onRemove?: (name: string) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Labels passed to every `docker run` (`--label key=value`).
|
|
26
|
+
* Cleanup uses these to detect orphans from crashed processes.
|
|
27
|
+
*/
|
|
28
|
+
labels?: Record<string, string>;
|
|
15
29
|
}
|
|
16
30
|
export declare class DockerManager {
|
|
31
|
+
private _onRegister?;
|
|
32
|
+
private _onRemove?;
|
|
33
|
+
private _labels?;
|
|
34
|
+
constructor(options?: DockerManagerOptions);
|
|
17
35
|
/**
|
|
18
36
|
* Build an image from a Dockerfile, with lazy mtime-based caching.
|
|
19
37
|
* Skips rebuild if the Dockerfile hasn't changed since last build.
|
|
@@ -23,6 +41,12 @@ export declare class DockerManager {
|
|
|
23
41
|
* Run a container and return its container ID.
|
|
24
42
|
*/
|
|
25
43
|
run(options: DockerRunOptions): Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Fire the `onRegister` callback for a container we didn't create via
|
|
46
|
+
* `run()` — e.g. one adopted at startup because it was already running.
|
|
47
|
+
* Lets cleanup hooks (process-exit handler) track it the same way.
|
|
48
|
+
*/
|
|
49
|
+
registerExisting(name: string): void;
|
|
26
50
|
/**
|
|
27
51
|
* Stop a running container gracefully.
|
|
28
52
|
*/
|
|
@@ -86,8 +86,10 @@ export declare class ContainerRuntime {
|
|
|
86
86
|
*/
|
|
87
87
|
cleanup(): Promise<void>;
|
|
88
88
|
/**
|
|
89
|
-
* Recover host port mappings from
|
|
90
|
-
*
|
|
89
|
+
* Recover host port mappings from the flattened ports map returned by
|
|
90
|
+
* `DockerManager.inspect()`: { "8080/tcp": "0.0.0.0:32768", ... }.
|
|
91
|
+
* (inspect() already flattens docker's raw `[{HostIp,HostPort}]` shape, so
|
|
92
|
+
* the value here is a "host:port" string, not an array.)
|
|
91
93
|
*/
|
|
92
94
|
private _recoverPortMappings;
|
|
93
95
|
private _startHealthCheck;
|
|
@@ -4,110 +4,75 @@
|
|
|
4
4
|
* Main thread side: spawns a Worker on first command, maintains a serial
|
|
5
5
|
* command queue, and bridges WebSocket events between main thread and worker.
|
|
6
6
|
*/
|
|
7
|
+
import type { WranglerConfig } from '../config';
|
|
7
8
|
import type { DOExecutor, DOExecutorFactory, ExecutorConfig } from './do-executor';
|
|
8
|
-
|
|
9
|
-
/** Commands sent from main thread to worker */
|
|
10
|
-
export type DOCommand = {
|
|
11
|
-
type: 'fetch';
|
|
12
|
-
url: string;
|
|
13
|
-
method: string;
|
|
14
|
-
headers: [string, string][];
|
|
15
|
-
body: ArrayBuffer | null;
|
|
16
|
-
} | {
|
|
17
|
-
type: 'rpc-call';
|
|
18
|
-
method: string;
|
|
19
|
-
args: unknown[];
|
|
20
|
-
} | {
|
|
21
|
-
type: 'rpc-get';
|
|
22
|
-
prop: string;
|
|
23
|
-
} | {
|
|
24
|
-
type: 'alarm';
|
|
25
|
-
retryCount: number;
|
|
26
|
-
} | {
|
|
27
|
-
type: 'ws-create';
|
|
28
|
-
wsId: string;
|
|
29
|
-
};
|
|
30
|
-
/** Results returned from worker to main thread */
|
|
31
|
-
export type DOResult = {
|
|
32
|
-
type: 'fetch';
|
|
33
|
-
status: number;
|
|
34
|
-
statusText: string;
|
|
35
|
-
headers: [string, string][];
|
|
36
|
-
body: ArrayBuffer | null;
|
|
37
|
-
} | {
|
|
38
|
-
type: 'rpc-call';
|
|
39
|
-
value: unknown;
|
|
40
|
-
} | {
|
|
41
|
-
type: 'rpc-get';
|
|
42
|
-
value: unknown;
|
|
43
|
-
} | {
|
|
44
|
-
type: 'alarm';
|
|
45
|
-
} | {
|
|
46
|
-
type: 'ws-created';
|
|
47
|
-
wsId: string;
|
|
48
|
-
} | {
|
|
49
|
-
type: 'error';
|
|
50
|
-
message: string;
|
|
51
|
-
stack?: string;
|
|
52
|
-
name?: string;
|
|
53
|
-
};
|
|
54
|
-
/** Messages from main thread → worker */
|
|
55
|
-
export type DOWorkerMessage = {
|
|
56
|
-
type: 'command';
|
|
57
|
-
id: number;
|
|
58
|
-
command: DOCommand;
|
|
59
|
-
} | {
|
|
60
|
-
type: 'ws-message';
|
|
61
|
-
wsId: string;
|
|
62
|
-
data: string | ArrayBuffer;
|
|
63
|
-
} | {
|
|
64
|
-
type: 'ws-close';
|
|
65
|
-
wsId: string;
|
|
66
|
-
code: number;
|
|
67
|
-
reason: string;
|
|
68
|
-
wasClean: boolean;
|
|
69
|
-
} | {
|
|
70
|
-
type: 'ws-error';
|
|
71
|
-
wsId: string;
|
|
72
|
-
};
|
|
73
|
-
/** Messages from worker → main thread */
|
|
74
|
-
export type DOMainMessage = {
|
|
75
|
-
type: 'need-init';
|
|
76
|
-
} | {
|
|
77
|
-
type: 'ready';
|
|
78
|
-
} | {
|
|
79
|
-
type: 'result';
|
|
80
|
-
id: number;
|
|
81
|
-
result: DOResult;
|
|
82
|
-
} | {
|
|
83
|
-
type: 'alarm-set';
|
|
84
|
-
time: number | null;
|
|
85
|
-
} | {
|
|
86
|
-
type: 'ws-bridge';
|
|
87
|
-
payload: WsBridgeOutbound;
|
|
88
|
-
};
|
|
9
|
+
export type { DOCommand, DOMainMessage, DoReqStreamAck, DoReqStreamCancel, DoReqStreamChunk, DoReqStreamEnd, DoReqStreamError, DOResult, DoStreamAck, DoStreamCancel, DoStreamChunk, DoStreamEnd, DoStreamError, DOWorkerMessage, } from '../worker-thread/do-protocol';
|
|
89
10
|
export declare class WorkerExecutor implements DOExecutor {
|
|
90
11
|
private _config;
|
|
91
12
|
private _worker;
|
|
92
13
|
private _ready;
|
|
93
14
|
private _readyResolve;
|
|
15
|
+
private _readyReject;
|
|
94
16
|
private _pending;
|
|
95
17
|
private _nextId;
|
|
96
18
|
private _disposed;
|
|
19
|
+
/** Swallowed copy of `_priorDisposal`, created lazily on the first command.
|
|
20
|
+
* Shared so CONCURRENT first commands all await the prior container's
|
|
21
|
+
* teardown — a boolean gate would let the second racer skip straight to
|
|
22
|
+
* `docker run` while the old container's `docker rm` is still in flight. */
|
|
23
|
+
private _priorDisposalGate;
|
|
97
24
|
private _inFlightCount;
|
|
25
|
+
/** Mirrors of the DO worker's `state` lifecycle, fed by `do-state` signals. */
|
|
98
26
|
private _blocked;
|
|
99
|
-
private
|
|
100
|
-
|
|
27
|
+
private _aborted;
|
|
28
|
+
/**
|
|
29
|
+
* `wsId`s of every open WebSocket this DO's fetch handler returned — both
|
|
30
|
+
* hibernation (`state.acceptWebSocket`) and plain (`new WebSocketPair`) ones.
|
|
31
|
+
* Each pins the executor against the idle reaper until it closes. Added when
|
|
32
|
+
* `executeFetch` registers the bridge socket; removed on the `fetch-ws-close-out`
|
|
33
|
+
* dispatch (server-side close) and the `_fetchBridge` clientClose envelope
|
|
34
|
+
* (client disconnect) — both delete from the same set so neither double-counts.
|
|
35
|
+
*/
|
|
36
|
+
private _openFetchWsIds;
|
|
37
|
+
/**
|
|
38
|
+
* Main-side WS bridge for `Response{webSocket}` returned by the DO worker's
|
|
39
|
+
* fetch handler. The bridge's peer forwards outgoing bytes (Bun.serve → here)
|
|
40
|
+
* down to the DO worker as `fetch-ws-incoming` / `fetch-ws-close-in`.
|
|
41
|
+
*/
|
|
42
|
+
private _fetchBridge;
|
|
43
|
+
/**
|
|
44
|
+
* Main-side WS bridge for upstream CFWebSockets adopted from env-binding
|
|
45
|
+
* fetches the DO worker initiated (e.g. `this.env.SVC.fetch('/ws')` returning
|
|
46
|
+
* a 101). Direction is opposite of `_fetchBridge`: we already hold the real
|
|
47
|
+
* CFWebSocket from main's `resolveBinding(...).fetch()`, the DO worker side
|
|
48
|
+
* holds the synthetic user-facing peer.
|
|
49
|
+
*/
|
|
50
|
+
private _envBindingWsBridge;
|
|
51
|
+
/** Main-side host of the unified cross-thread RPC channel — env-binding
|
|
52
|
+
* call/get/fetch from inside the DO worker (`this.env.SVC.…`), with
|
|
53
|
+
* response/request-body streaming. Shared with the user-worker executor;
|
|
54
|
+
* channel-specifics injected as hooks. */
|
|
55
|
+
private _rpcChannel;
|
|
56
|
+
/** Reconstruction of streamed DO-fetch response bodies (DO worker → main). */
|
|
57
|
+
private _fetchStreams;
|
|
58
|
+
/** Outbound request-body pumps for DO-fetch (main → DO worker). A
|
|
59
|
+
* `do-req-stream-cancel` from the DO worker (instance code cancelled the
|
|
60
|
+
* body) stops the source reader. */
|
|
61
|
+
private _fetchRequestStreams;
|
|
101
62
|
constructor(config: ExecutorConfig);
|
|
102
63
|
private _ensureWorker;
|
|
103
64
|
private _resolveModulePath;
|
|
104
65
|
private _resolveConfigPath;
|
|
105
66
|
private _resolveDataDir;
|
|
106
67
|
private _sendCommand;
|
|
107
|
-
private _handleWsBridge;
|
|
108
|
-
/** Register a real WebSocket for bridging to the worker */
|
|
109
|
-
_bridgeWebSocket(wsId: string, ws: WebSocket): void;
|
|
110
68
|
executeFetch(request: Request): Promise<Response>;
|
|
69
|
+
private _pumpFetchRequestBody;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve a binding from main's env. Mirrors the user-worker channel:
|
|
72
|
+
* when `target.instanceId` is set, route through the namespace's `.get()`
|
|
73
|
+
* so DO-stub redirection works (cross-DO and self-DO access via env-RPC).
|
|
74
|
+
*/
|
|
75
|
+
private _resolveBinding;
|
|
111
76
|
executeRpc(method: string, args: unknown[]): Promise<unknown>;
|
|
112
77
|
executeRpcGet(prop: string): Promise<unknown>;
|
|
113
78
|
executeAlarm(retryCount: number): Promise<void>;
|
|
@@ -115,15 +80,34 @@ export declare class WorkerExecutor implements DOExecutor {
|
|
|
115
80
|
isBlocked(): boolean;
|
|
116
81
|
activeWebSocketCount(): number;
|
|
117
82
|
isAborted(): boolean;
|
|
83
|
+
isDisposed(): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Ask the DO worker to tear down its Docker container before we terminate the
|
|
86
|
+
* thread. terminate() kills the worker's activity/health timers but leaves the
|
|
87
|
+
* Docker process running — mirrors `InProcessExecutor.dispose()`'s
|
|
88
|
+
* `containerRuntime.cleanup()`. Bounded so a hung `docker rm` can't block
|
|
89
|
+
* reload; the thread is terminated right after regardless.
|
|
90
|
+
*/
|
|
91
|
+
private _cleanupContainer;
|
|
118
92
|
dispose(): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* The one teardown sequence — shared by `dispose()` (planned), `onerror`
|
|
95
|
+
* (worker crashed) and the init-error path, so the three can't drift: mark
|
|
96
|
+
* disposed, terminate the thread, reject every pending command and the ready
|
|
97
|
+
* promise with `error`, and dispose every bridge/stream/rpc registry.
|
|
98
|
+
* Idempotent. Counterpart of `WorkerThreadExecutor._failAll`.
|
|
99
|
+
*/
|
|
100
|
+
private _teardown;
|
|
119
101
|
}
|
|
120
102
|
export declare class WorkerExecutorFactory implements DOExecutorFactory {
|
|
121
103
|
private _modulePath?;
|
|
122
104
|
private _configPath?;
|
|
105
|
+
private _wranglerConfig?;
|
|
123
106
|
/**
|
|
124
|
-
* Set the module
|
|
125
|
-
* Called by the generation manager after
|
|
107
|
+
* Set the module + config paths and the parsed env-overridden config for all
|
|
108
|
+
* executors created by this factory. Called by the generation manager after
|
|
109
|
+
* loading config.
|
|
126
110
|
*/
|
|
127
|
-
configure(modulePath: string, configPath: string): void;
|
|
111
|
+
configure(modulePath: string, configPath: string, wranglerConfig?: WranglerConfig): void;
|
|
128
112
|
create(config: ExecutorConfig): DOExecutor;
|
|
129
113
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import type { WranglerConfig } from '../config';
|
|
2
3
|
import type { ContainerConfig } from './container';
|
|
3
4
|
import type { DurableObjectBase, DurableObjectIdImpl, DurableObjectLimits } from './durable-object';
|
|
4
5
|
export interface ExecutorConfig {
|
|
@@ -6,11 +7,24 @@ export interface ExecutorConfig {
|
|
|
6
7
|
db: Database;
|
|
7
8
|
namespaceName: string;
|
|
8
9
|
cls: new (ctx: any, env: unknown) => DurableObjectBase;
|
|
9
|
-
env: unknown
|
|
10
|
+
env: Record<string, unknown>;
|
|
10
11
|
dataDir?: string;
|
|
11
12
|
limits?: DurableObjectLimits;
|
|
12
13
|
containerConfig?: ContainerConfig;
|
|
13
14
|
onAlarmSet?: (time: number | null) => void;
|
|
15
|
+
/** @internal Worker-thread DO executors re-import the user module + config
|
|
16
|
+
* inside their Bun Worker; the factory injects these paths. */
|
|
17
|
+
_modulePath?: string;
|
|
18
|
+
_configPath?: string;
|
|
19
|
+
/** @internal Main's already-parsed, env-overridden wrangler config. Passed to
|
|
20
|
+
* the DO worker so it doesn't re-load from `_configPath` WITHOUT the `--env`
|
|
21
|
+
* overrides (which the re-parse silently dropped). */
|
|
22
|
+
_wranglerConfig?: WranglerConfig;
|
|
23
|
+
/** @internal Disposal of the PRIOR executor for this same id, still in flight
|
|
24
|
+
* (its Docker container is being `docker rm`'d). A container DO awaits this
|
|
25
|
+
* before its first command so a fresh `docker run` for the same name doesn't
|
|
26
|
+
* race the teardown — which could `docker rm -f` the replacement. */
|
|
27
|
+
_priorDisposal?: Promise<void>;
|
|
14
28
|
}
|
|
15
29
|
export interface DOExecutor {
|
|
16
30
|
/** Execute a fetch call on the DO instance */
|
|
@@ -29,6 +43,10 @@ export interface DOExecutor {
|
|
|
29
43
|
activeWebSocketCount(): number;
|
|
30
44
|
/** Whether the instance has been aborted */
|
|
31
45
|
isAborted(): boolean;
|
|
46
|
+
/** Whether the underlying executor is dead (e.g. its Worker crashed). A
|
|
47
|
+
* disposed executor is unusable — the namespace drops it and recreates a
|
|
48
|
+
* fresh one on next access. Optional: executors that can't die report `false`. */
|
|
49
|
+
isDisposed?(): boolean;
|
|
32
50
|
/** Hot-swap the DO class and env without disposing (preserves WebSocket connections) */
|
|
33
51
|
reloadClass?(cls: new (ctx: any, env: unknown) => DurableObjectBase, env: unknown): void;
|
|
34
52
|
/** Kill the instance */
|
|
@@ -36,4 +54,7 @@ export interface DOExecutor {
|
|
|
36
54
|
}
|
|
37
55
|
export interface DOExecutorFactory {
|
|
38
56
|
create(config: ExecutorConfig): DOExecutor;
|
|
57
|
+
/** Tell the factory where user code lives + the already-parsed env-overridden
|
|
58
|
+
* config (only used by worker-thread DO executors). */
|
|
59
|
+
configure?(modulePath: string, configPath: string, wranglerConfig?: WranglerConfig): void;
|
|
39
60
|
}
|
|
@@ -8,16 +8,25 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Database } from 'bun:sqlite';
|
|
10
10
|
import type { WranglerConfig } from '../config';
|
|
11
|
-
import {
|
|
11
|
+
import { RpcClient } from '../worker-thread/rpc-shared';
|
|
12
|
+
import type { WsGuestBridge } from '../worker-thread/ws-bridge-shared';
|
|
13
|
+
import type { DOMainMessage } from './do-executor-worker';
|
|
14
|
+
/** Build an RpcClient that bridges DO-worker → main over the DO executor channel. */
|
|
15
|
+
export declare function createDoEnvRpc(post: (msg: DOMainMessage) => void): RpcClient;
|
|
12
16
|
/**
|
|
13
17
|
* Build a minimal env for use inside a DO worker thread.
|
|
14
|
-
*
|
|
18
|
+
*
|
|
19
|
+
* Stateless bindings (KV/R2/D1/queue producer/nested DO) are instantiated
|
|
20
|
+
* locally against the shared SQLite/filesystem. Stateful ones (service
|
|
21
|
+
* bindings, email, workflow, …) become RPC proxies that route through main
|
|
22
|
+
* via the DO-executor message channel.
|
|
23
|
+
*
|
|
24
|
+
* `envWsBridge` is shared with `do-worker-entry.ts`'s message router: when an
|
|
25
|
+
* env-binding fetch returns a 101 response with a `webSocketId`, the proxy
|
|
26
|
+
* here calls `envWsBridge.createBridgedSocket(id)` to reconstruct a
|
|
27
|
+
* user-facing CFWebSocket whose events flow over the bridge to main.
|
|
15
28
|
*/
|
|
16
|
-
export declare function buildWorkerEnv(config: WranglerConfig, dataDir: string): {
|
|
29
|
+
export declare function buildWorkerEnv(config: WranglerConfig, dataDir: string, baseDir: string, rpc: RpcClient, _hostNamespaceName: string, envWsBridge: WsGuestBridge<DOMainMessage>): {
|
|
17
30
|
db: Database;
|
|
18
31
|
env: Record<string, unknown>;
|
|
19
|
-
doNamespaces: {
|
|
20
|
-
className: string;
|
|
21
|
-
namespace: DurableObjectNamespaceImpl;
|
|
22
|
-
}[];
|
|
23
32
|
};
|
|
@@ -113,6 +113,10 @@ export declare class DurableObjectIdImpl {
|
|
|
113
113
|
toString(): string;
|
|
114
114
|
equals(other: DurableObjectIdImpl): boolean;
|
|
115
115
|
}
|
|
116
|
+
/** Deterministic id from name. Shared by the in-process namespace and the thread-mode proxy. */
|
|
117
|
+
export declare function hashIdFromName(name: string): string;
|
|
118
|
+
/** Random 128-bit id matching CF's `newUniqueId` hex shape. */
|
|
119
|
+
export declare function randomUniqueIdHex(): string;
|
|
116
120
|
export declare class DurableObjectStateImpl {
|
|
117
121
|
readonly id: DurableObjectIdImpl;
|
|
118
122
|
readonly storage: SqliteDurableObjectStorage;
|
|
@@ -166,9 +170,21 @@ export declare class DurableObjectBase {
|
|
|
166
170
|
}
|
|
167
171
|
export declare class DurableObjectNamespaceImpl {
|
|
168
172
|
private _executors;
|
|
173
|
+
/** In-flight disposals per id (container DOs only). A respawn for the same id
|
|
174
|
+
* chains its first command on this so a fresh `docker run` doesn't race the
|
|
175
|
+
* old container's teardown. */
|
|
176
|
+
private _disposing;
|
|
169
177
|
private _stubs;
|
|
170
178
|
private _knownIds;
|
|
171
179
|
private _class?;
|
|
180
|
+
private _externalClassName?;
|
|
181
|
+
/**
|
|
182
|
+
* In thread mode the real DO class isn't available on main. The user-worker
|
|
183
|
+
* introspects its prototype and reports `alarm()` presence back through the
|
|
184
|
+
* `'ready'` message; `GenerationManager` forwards it via `_setAlarmHandlerHint`.
|
|
185
|
+
* Read by `hasAlarmHandler()` when `_externalClassName` is set.
|
|
186
|
+
*/
|
|
187
|
+
private _externalAlarmHandler?;
|
|
172
188
|
private _env?;
|
|
173
189
|
private db;
|
|
174
190
|
private namespaceName;
|
|
@@ -186,9 +202,22 @@ export declare class DurableObjectNamespaceImpl {
|
|
|
186
202
|
constructor(db: Database, namespaceName: string, dataDir?: string, limits?: DurableObjectLimits, factory?: DOExecutorFactory, clock?: Clock);
|
|
187
203
|
private _getFactory;
|
|
188
204
|
/** Called after worker module is loaded to wire the actual class */
|
|
189
|
-
_setClass(cls: new (ctx: DurableObjectStateImpl, env: unknown) => DurableObjectBase, env:
|
|
205
|
+
_setClass(cls: new (ctx: DurableObjectStateImpl, env: unknown) => DurableObjectBase, env: Record<string, unknown>, generationId?: number): void;
|
|
206
|
+
/**
|
|
207
|
+
* Thread-mode counterpart of `_setClass`: the real DO class lives in a worker
|
|
208
|
+
* thread, so main only stores the className for diagnostics and treats the
|
|
209
|
+
* namespace as wired. Must be used with an executor factory that does not
|
|
210
|
+
* need the JS class on main (e.g. `WorkerExecutorFactory`).
|
|
211
|
+
*/
|
|
212
|
+
_setExternalClass(className: string, env: Record<string, unknown>, generationId?: number): void;
|
|
190
213
|
/** Set container config for this namespace (makes it a container namespace) */
|
|
191
214
|
_setContainerConfig(config: ContainerConfig): void;
|
|
215
|
+
/**
|
|
216
|
+
* Hint for thread-mode namespaces: whether the (worker-side) DO class defines
|
|
217
|
+
* an `alarm()` handler. Called by `GenerationManager` after the worker's
|
|
218
|
+
* `'ready'` message lands. Idempotent — overwrites on each reload.
|
|
219
|
+
*/
|
|
220
|
+
_setAlarmHandlerHint(value: boolean): void;
|
|
192
221
|
/** @internal Restore all persisted alarms for this namespace */
|
|
193
222
|
private _restoreAlarms;
|
|
194
223
|
/** @internal Schedule a timer for an alarm */
|
|
@@ -197,12 +226,36 @@ export declare class DurableObjectNamespaceImpl {
|
|
|
197
226
|
private _fireAlarm;
|
|
198
227
|
/** @internal Get or create a DO executor by id string */
|
|
199
228
|
private _getOrCreateExecutor;
|
|
229
|
+
/**
|
|
230
|
+
* @internal Dispose an executor and, for container DOs, record the disposal so a
|
|
231
|
+
* respawn for the same id can wait for the old container's teardown (`docker rm`)
|
|
232
|
+
* to finish before its own `docker run` — otherwise the teardown can race and
|
|
233
|
+
* `docker rm -f` the freshly started replacement. Non-container DOs skip the
|
|
234
|
+
* tracking (dispose is fast and there's no shared Docker name to collide on).
|
|
235
|
+
*/
|
|
236
|
+
private _disposeExecutor;
|
|
237
|
+
/**
|
|
238
|
+
* @internal Dispose an executor detached from the namespace (during reload swap)
|
|
239
|
+
* once its in-flight command settles, so an old-generation request mid-DO-call
|
|
240
|
+
* isn't rejected with "Worker terminated". Polls `isActive()`; bounded (≈10s) so
|
|
241
|
+
* a hung command can't keep the old worker thread alive indefinitely.
|
|
242
|
+
*/
|
|
243
|
+
private _disposeExecutorWhenIdle;
|
|
244
|
+
private _pollThenDispose;
|
|
200
245
|
/** @internal Evict idle executors */
|
|
201
246
|
private _evictIdle;
|
|
202
247
|
/** @internal Clear all in-memory alarm timers (alarms persist in DB for new generation to restore) */
|
|
203
248
|
clearAlarmTimers(): void;
|
|
204
|
-
/**
|
|
205
|
-
|
|
249
|
+
/**
|
|
250
|
+
* @internal Destroy this namespace: clear timers, evict executors without
|
|
251
|
+
* active WebSockets. Pass `{ force: true }` for final teardown (test dispose /
|
|
252
|
+
* shutdown) to dispose EVERY executor and never restart the eviction timer —
|
|
253
|
+
* there's no next generation to reap survivors, and a restarted 30s interval
|
|
254
|
+
* would outlive teardown (and the DB close that follows).
|
|
255
|
+
*/
|
|
256
|
+
destroy(options?: {
|
|
257
|
+
force?: boolean;
|
|
258
|
+
}): void;
|
|
206
259
|
/** @internal Get a raw instance for testing (no proxy) */
|
|
207
260
|
_getInstance(idStr: string): DurableObjectBase | null;
|
|
208
261
|
/** @internal Get the executor for a given id */
|
|
@@ -212,10 +265,19 @@ export declare class DurableObjectNamespaceImpl {
|
|
|
212
265
|
id: string;
|
|
213
266
|
wsCount: number;
|
|
214
267
|
}>;
|
|
268
|
+
/** @internal Whether any executor in this namespace currently holds at least one accepted WebSocket. */
|
|
269
|
+
hasActiveWebSockets(): boolean;
|
|
215
270
|
/** @internal List all instance IDs in this namespace (from DB). */
|
|
216
271
|
_listInstanceIds(): string[];
|
|
217
|
-
/**
|
|
272
|
+
/**
|
|
273
|
+
* Whether the DO class defines an alarm() handler. In thread mode the class
|
|
274
|
+
* lives on the worker, so main relies on the hint shipped through the
|
|
275
|
+
* worker's `'ready'` message (see `_setAlarmHandlerHint`). Returns `false`
|
|
276
|
+
* during the brief window between `_setExternalClass` and the hint arriving.
|
|
277
|
+
*/
|
|
218
278
|
hasAlarmHandler(): boolean;
|
|
279
|
+
/** @internal Whether the namespace is wired (class set, in-process or external). */
|
|
280
|
+
private _isWired;
|
|
219
281
|
/** @internal Trigger the alarm handler immediately (used by dashboard) */
|
|
220
282
|
triggerAlarm(idStr: string): Promise<void>;
|
|
221
283
|
/** @internal Fire all alarms whose scheduled time is <= clock.now(). Returns an array of promises. */
|
|
@@ -66,8 +66,11 @@ export declare class QueueConsumer {
|
|
|
66
66
|
private batchTimer;
|
|
67
67
|
private polling;
|
|
68
68
|
private _activeDeliveries;
|
|
69
|
+
/** Optional hook to register an in-flight batch with reload-drain accounting,
|
|
70
|
+
* so a generation isn't terminated mid-batch (thread mode). */
|
|
71
|
+
private _trackBatch?;
|
|
69
72
|
private clock;
|
|
70
|
-
constructor(db: Database, config: ConsumerConfig, handler: QueueHandler, env: Record<string, unknown>, workerName?: string, clock?: Clock);
|
|
73
|
+
constructor(db: Database, config: ConsumerConfig, handler: QueueHandler, env: Record<string, unknown>, workerName?: string, clock?: Clock, trackBatch?: (p: Promise<unknown>) => void);
|
|
71
74
|
start(intervalMs?: number): void;
|
|
72
75
|
stop(): void;
|
|
73
76
|
poll(): Promise<void>;
|
|
@@ -9,12 +9,34 @@
|
|
|
9
9
|
export declare const RPC_TARGET_BRAND: unique symbol;
|
|
10
10
|
export declare function isRpcTarget(value: unknown): boolean;
|
|
11
11
|
export declare const NON_RPC_PROPS: Set<string | symbol>;
|
|
12
|
+
/** No-op disposer shared by every proxy that needs to satisfy `using` declarations. */
|
|
13
|
+
export declare const noopDispose: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Build a Proxy that exposes `.fetch` (HTTP round-trip) and turns any other
|
|
16
|
+
* property access into a method call. Used to construct cross-thread binding
|
|
17
|
+
* proxies — main-worker service bindings, DO stubs, and DO-worker env-binding
|
|
18
|
+
* proxies all share this shape.
|
|
19
|
+
*
|
|
20
|
+
* `callbacks.call` is invoked for any prop that isn't `'fetch'` or in
|
|
21
|
+
* `NON_RPC_PROPS`. `extras` overrides specific props (DO stubs use it to
|
|
22
|
+
* surface `id`/`name`, service bindings to surface `connect`). Per-prop
|
|
23
|
+
* methods are cached so hot callers don't allocate a fresh closure per
|
|
24
|
+
* access.
|
|
25
|
+
*/
|
|
26
|
+
export declare function makeBindingProxy(callbacks: {
|
|
27
|
+
fetch: (input: Request | string | URL, init?: RequestInit) => Promise<Response>;
|
|
28
|
+
call: (prop: string, args: unknown[]) => unknown;
|
|
29
|
+
/** Optional property read (`await binding.prop`). When supplied, the
|
|
30
|
+
* per-prop callable is also thenable, issuing a property-get instead of
|
|
31
|
+
* resolving to the function itself. */
|
|
32
|
+
getProperty?: (prop: string) => Promise<unknown>;
|
|
33
|
+
}, extras?: Record<string | symbol, unknown>): Record<string, unknown>;
|
|
12
34
|
/**
|
|
13
35
|
* Wrap an RpcTarget instance in a Proxy that mimics CF stub behavior:
|
|
14
36
|
* - Method calls: validate args → call → wrap return value
|
|
15
37
|
* - Property access: thenable pattern, wraps returned RpcTarget/function values
|
|
16
38
|
* - Filters `_`-prefixed properties (returns undefined)
|
|
17
|
-
* - Symbol.dispose → no-op
|
|
39
|
+
* - Symbol.dispose / Symbol.asyncDispose → undefined (spec-defined no-op for `using`)
|
|
18
40
|
* - dup() → new stub wrapping same target
|
|
19
41
|
*/
|
|
20
42
|
export declare function createRpcStub(target: object): object;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ExecutionContext } from '../execution-context';
|
|
2
1
|
export interface ScheduledController {
|
|
3
2
|
readonly scheduledTime: number;
|
|
4
3
|
readonly cron: string;
|
|
@@ -20,6 +19,17 @@ interface ParsedCron {
|
|
|
20
19
|
export declare function parseCron(expression: string): ParsedCron;
|
|
21
20
|
export declare function cronMatchesDate(cron: ParsedCron, date: Date): boolean;
|
|
22
21
|
export declare function createScheduledController(cron: string, scheduledTime: number): ScheduledController;
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Generic cron timer. `invoke` is called with the matched expression and the
|
|
24
|
+
* tick time; the caller decides how to dispatch (in-process handler vs RPC).
|
|
25
|
+
*/
|
|
26
|
+
export declare function startCronTimer(crons: string[], invoke: (cronExpr: string, now: Date) => Promise<unknown>, workerName?: string,
|
|
27
|
+
/**
|
|
28
|
+
* Per-cron last-fired-minute dedup state, keyed by cron expression. Pass a map
|
|
29
|
+
* OWNED BY THE GENERATION MANAGER (not per-generation) so it survives reloads:
|
|
30
|
+
* a fresh per-generation map would start empty and re-fire a cron for a minute
|
|
31
|
+
* the previous generation already handled — for `* * * * *` that's nearly every
|
|
32
|
+
* save. Defaults to a local map for standalone callers/tests.
|
|
33
|
+
*/
|
|
34
|
+
lastFiredMinute?: Map<string, number>): NodeJS.Timer;
|
|
25
35
|
export {};
|
|
@@ -7,12 +7,24 @@
|
|
|
7
7
|
* - `.myProperty` — RPC property access (returns thenable/Promise)
|
|
8
8
|
* - `.connect()` — stub for TCP socket (throws — not supported in dev)
|
|
9
9
|
*/
|
|
10
|
+
import type { ResolvedTarget } from '../worker-registry';
|
|
10
11
|
export interface ServiceBindingLimits {
|
|
11
12
|
/** Max subrequests per top-level request (CF default: 1000 for workers, 32 for service bindings) */
|
|
12
13
|
maxSubrequests?: number;
|
|
13
14
|
/** Max payload size in bytes for RPC arguments (CF default: 32 MiB) */
|
|
14
15
|
maxRpcPayloadSize?: number;
|
|
15
16
|
}
|
|
17
|
+
/** Error thrown by `connect()` (both in-process and worker-thread paths). */
|
|
18
|
+
export declare function serviceBindingConnectError(name: string): Error;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the call target for a service binding RPC (`fetch` or method):
|
|
21
|
+
* a named entrypoint class, an unnamed default class, or the default object.
|
|
22
|
+
*
|
|
23
|
+
* Used by `ServiceBinding._getTarget` (in-process) and the worker-thread's
|
|
24
|
+
* `invokeEntrypointRpc`. Single source of truth so the in-process and
|
|
25
|
+
* thread-mode paths can't drift.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveEntrypointTarget(workerModule: Record<string, unknown>, entrypoint: string | undefined, ctx: unknown, env: unknown): Record<string, unknown>;
|
|
16
28
|
export declare class ServiceBinding {
|
|
17
29
|
private _resolver;
|
|
18
30
|
private _entrypoint;
|
|
@@ -21,10 +33,7 @@ export declare class ServiceBinding {
|
|
|
21
33
|
_subrequestCount: number;
|
|
22
34
|
private _props;
|
|
23
35
|
constructor(serviceName: string, entrypoint?: string, limits?: ServiceBindingLimits, props?: Record<string, unknown>);
|
|
24
|
-
_wire(resolverOrModule: (() =>
|
|
25
|
-
workerModule: Record<string, unknown>;
|
|
26
|
-
env: Record<string, unknown>;
|
|
27
|
-
}) | Record<string, unknown>, env?: Record<string, unknown>): void;
|
|
36
|
+
_wire(resolverOrModule: (() => ResolvedTarget) | Record<string, unknown>, env?: Record<string, unknown>): void;
|
|
28
37
|
get isWired(): boolean;
|
|
29
38
|
private _resolve;
|
|
30
39
|
private _checkSubrequestLimit;
|
|
@@ -30,7 +30,7 @@ export declare class StaticAssets {
|
|
|
30
30
|
private headerRules;
|
|
31
31
|
private redirectRules;
|
|
32
32
|
constructor(directory: string, htmlHandling?: string, notFoundHandling?: string, limits?: StaticAssetsLimits);
|
|
33
|
-
fetch(
|
|
33
|
+
fetch(input: Request | string | URL): Promise<Response>;
|
|
34
34
|
private getRedirectRules;
|
|
35
35
|
/**
|
|
36
36
|
* Returns Response for 3xx redirects, string for 200 rewrites, or null for no match.
|