@ricsam/r5d-worker 0.0.123 → 0.0.124
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/cjs/main.cjs +488 -238
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-checkout-garbage.cjs +32 -3
- package/dist/cjs/project-workspace-state.cjs +51 -35
- package/dist/cjs/project-worktrees.cjs +79 -34
- package/dist/cjs/recovery-journal-protocol.cjs +56 -0
- package/dist/cjs/recovery-journal-runtime.cjs +133 -0
- package/dist/cjs/recovery-journal-thread.cjs +9 -0
- package/dist/cjs/recovery-journal.cjs +735 -0
- package/dist/cjs/recovery-store.cjs +8 -0
- package/dist/cjs/session-file-mutations.cjs +61 -0
- package/dist/cjs/working-tree-mirror.cjs +1 -0
- package/dist/cjs/workspace-command-sync-policy.cjs +37 -8
- package/dist/cjs/workspace-filesystem-executor-thread.cjs +36 -0
- package/dist/cjs/workspace-filesystem-executor.cjs +327 -0
- package/dist/cjs/workspace-filesystem-job-types.cjs +134 -0
- package/dist/cjs/workspace-filesystem-jobs.cjs +57 -0
- package/dist/cjs/workspace-git-sync.cjs +275 -201
- package/dist/cjs/workspace-mount-hold-fence.cjs +120 -0
- package/dist/mjs/main.mjs +491 -244
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-checkout-garbage.mjs +30 -2
- package/dist/mjs/project-workspace-state.mjs +51 -35
- package/dist/mjs/project-worktrees.mjs +74 -34
- package/dist/mjs/recovery-journal-protocol.mjs +30 -0
- package/dist/mjs/recovery-journal-runtime.mjs +112 -0
- package/dist/mjs/recovery-journal-thread.mjs +8 -0
- package/dist/mjs/recovery-journal.mjs +687 -0
- package/dist/mjs/recovery-store.mjs +8 -0
- package/dist/mjs/session-file-mutations.mjs +37 -0
- package/dist/mjs/working-tree-mirror.mjs +1 -0
- package/dist/mjs/workspace-command-sync-policy.mjs +37 -8
- package/dist/mjs/workspace-filesystem-executor-thread.mjs +38 -0
- package/dist/mjs/workspace-filesystem-executor.mjs +287 -0
- package/dist/mjs/workspace-filesystem-job-types.mjs +106 -0
- package/dist/mjs/workspace-filesystem-jobs.mjs +51 -0
- package/dist/mjs/workspace-git-sync.mjs +264 -202
- package/dist/mjs/workspace-mount-hold-fence.mjs +95 -0
- package/dist/types/main.d.ts +24 -19
- package/dist/types/project-checkout-garbage.d.ts +19 -2
- package/dist/types/project-workspace-state.d.ts +9 -9
- package/dist/types/project-worktrees.d.ts +49 -5
- package/dist/types/recovery-journal-protocol.d.ts +35 -0
- package/dist/types/recovery-journal-runtime.d.ts +12 -0
- package/dist/types/recovery-journal-stall-fixture.d.ts +1 -0
- package/dist/types/recovery-journal-thread.d.ts +1 -0
- package/dist/types/recovery-journal.d.ts +246 -0
- package/dist/types/recovery-store.d.ts +6 -0
- package/dist/types/session-file-mutations.d.ts +22 -0
- package/dist/types/workspace-command-sync-policy.d.ts +22 -7
- package/dist/types/workspace-filesystem-executor-thread.d.ts +1 -0
- package/dist/types/workspace-filesystem-executor.d.ts +123 -0
- package/dist/types/workspace-filesystem-job-types.d.ts +246 -0
- package/dist/types/workspace-filesystem-jobs.d.ts +7 -0
- package/dist/types/workspace-git-sync.d.ts +113 -7
- package/dist/types/workspace-mount-hold-fence.d.ts +42 -0
- package/package.json +1 -1
- package/dist/cjs/project-snapshot-recovery-runner.cjs +0 -171
- package/dist/mjs/project-snapshot-recovery-runner.mjs +0 -135
- package/dist/types/project-snapshot-recovery-runner.d.ts +0 -10
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
class WorkspaceMountHoldAbortedError extends Error {
|
|
2
|
+
constructor(reason) {
|
|
3
|
+
super(reason);
|
|
4
|
+
this.name = "WorkspaceMountHoldAbortedError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
class WorkspaceMountHoldFence {
|
|
8
|
+
holds = /* @__PURE__ */ new Map();
|
|
9
|
+
waiters = /* @__PURE__ */ new Set();
|
|
10
|
+
now;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.now = options.now ?? (() => Date.now());
|
|
13
|
+
}
|
|
14
|
+
/** Take one hold on every listed mount; the returned release is idempotent. */
|
|
15
|
+
hold(mountIds, description) {
|
|
16
|
+
const ids = [...new Set(mountIds)];
|
|
17
|
+
for (const id of ids) {
|
|
18
|
+
const existing = this.holds.get(id);
|
|
19
|
+
if (existing) {
|
|
20
|
+
existing.count += 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
this.holds.set(id, { count: 1, description, sinceMs: this.now() });
|
|
24
|
+
}
|
|
25
|
+
let released = false;
|
|
26
|
+
return () => {
|
|
27
|
+
if (released) return;
|
|
28
|
+
released = true;
|
|
29
|
+
for (const id of ids) {
|
|
30
|
+
const existing = this.holds.get(id);
|
|
31
|
+
if (!existing) continue;
|
|
32
|
+
existing.count -= 1;
|
|
33
|
+
if (existing.count === 0) this.holds.delete(id);
|
|
34
|
+
}
|
|
35
|
+
this.wake();
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
isHeld(mountIds) {
|
|
39
|
+
return mountIds.some((id) => this.holds.has(id));
|
|
40
|
+
}
|
|
41
|
+
/** Human-readable holder of the first held mount among `mountIds`, for diagnostics. */
|
|
42
|
+
describeHold(mountIds) {
|
|
43
|
+
for (const id of mountIds) {
|
|
44
|
+
const hold = this.holds.get(id);
|
|
45
|
+
if (hold) return `${hold.description} (held for ${Math.round((this.now() - hold.sinceMs) / 1e3)}s)`;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/** Every held mount id, sorted, for status reporting. */
|
|
50
|
+
heldMountIds() {
|
|
51
|
+
return [...this.holds.keys()].sort();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolves in the same turn as the release that leaves none of `mountIds`
|
|
55
|
+
* held, so the caller can re-check and reserve without an interleaving
|
|
56
|
+
* hold. Resolves immediately when nothing is held. Rejects with
|
|
57
|
+
* WorkspaceMountHoldAbortedError when `signal` aborts first.
|
|
58
|
+
*/
|
|
59
|
+
waitForRelease(mountIds, options = {}) {
|
|
60
|
+
if (options.signal?.aborted) {
|
|
61
|
+
return Promise.reject(new WorkspaceMountHoldAbortedError(abortReason(options.signal)));
|
|
62
|
+
}
|
|
63
|
+
if (!this.isHeld(mountIds)) return Promise.resolve();
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const waiter = { mountIds, resolve, reject, signal: options.signal };
|
|
66
|
+
if (options.signal) {
|
|
67
|
+
const signal = options.signal;
|
|
68
|
+
waiter.onAbort = () => {
|
|
69
|
+
this.waiters.delete(waiter);
|
|
70
|
+
reject(new WorkspaceMountHoldAbortedError(abortReason(signal)));
|
|
71
|
+
};
|
|
72
|
+
signal.addEventListener("abort", waiter.onAbort, { once: true });
|
|
73
|
+
}
|
|
74
|
+
this.waiters.add(waiter);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
wake() {
|
|
78
|
+
for (const waiter of [...this.waiters]) {
|
|
79
|
+
if (this.isHeld(waiter.mountIds)) continue;
|
|
80
|
+
this.waiters.delete(waiter);
|
|
81
|
+
if (waiter.signal && waiter.onAbort) waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
82
|
+
waiter.resolve();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function abortReason(signal) {
|
|
87
|
+
const reason = signal.reason;
|
|
88
|
+
if (reason instanceof Error) return reason.message;
|
|
89
|
+
if (typeof reason === "string" && reason) return reason;
|
|
90
|
+
return "Workspace mount reservation was aborted";
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
WorkspaceMountHoldAbortedError,
|
|
94
|
+
WorkspaceMountHoldFence
|
|
95
|
+
};
|
package/dist/types/main.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { WORKER_RESUMABLE_PROTOCOL, type WorkerRecoveryClientMessage, type Worke
|
|
|
4
4
|
import { Database } from "bun:sqlite";
|
|
5
5
|
import type { WorkerGitIdentity } from "./git-identity";
|
|
6
6
|
import { configureGitHubRegistryAuthFiles, type PreparedPrivateAuthFileGeneration } from "./registry-auth";
|
|
7
|
+
import { WorkspaceMountHoldFence } from "./workspace-mount-hold-fence";
|
|
7
8
|
import { type WorkspaceHydrationTransactionHooks } from "./workspace-git-sync";
|
|
8
9
|
type WorkerProjectConfig = {
|
|
9
10
|
projectId: string;
|
|
@@ -48,19 +49,22 @@ declare function workerServerHeartbeatAction(input: {
|
|
|
48
49
|
watchdogSweepDelayed: boolean;
|
|
49
50
|
}): "wait" | "start_probe" | "terminate";
|
|
50
51
|
/**
|
|
51
|
-
* A hydration transaction
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* what turned one slow hydration into a mandatory canonical
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
52
|
+
* A hydration transaction runs on the workspace filesystem executor thread
|
|
53
|
+
* and on a stalled disk it can run for a long time; the server bounds its
|
|
54
|
+
* deadlines by a hydration heartbeat lease rather than by the ordinary
|
|
55
|
+
* request timeout. Declare that lease first: the server keeps the control
|
|
56
|
+
* socket open and defers the affected request deadlines while the lease is
|
|
57
|
+
* active, which is what turned one slow hydration into a mandatory canonical
|
|
58
|
+
* reset before the lease existed (incident 3bccab03). The extra event-loop
|
|
59
|
+
* turn lets control messages that were already queued run before the busy
|
|
60
|
+
* observation that precedes the transaction. The hooks also hold every mount
|
|
61
|
+
* a job reads or writes on the process-wide fence, so command admission for
|
|
62
|
+
* those mounts waits until the job ended (incident 10).
|
|
60
63
|
*/
|
|
61
64
|
declare function createWorkspaceHydrationLeaseHooks(send: (message: Extract<WorkerClientMessage, {
|
|
62
65
|
type: "heartbeat_lease";
|
|
63
|
-
}>) => void, options
|
|
66
|
+
}>) => void, options: {
|
|
67
|
+
fence: WorkspaceMountHoldFence;
|
|
64
68
|
yieldToEventLoop?: () => Promise<void>;
|
|
65
69
|
log?: (line: string) => void;
|
|
66
70
|
now?: () => number;
|
|
@@ -69,6 +73,7 @@ export declare const workerLifecycleTestHarness: {
|
|
|
69
73
|
boundedWorkerLifecycleProgress: typeof boundedWorkerLifecycleProgress;
|
|
70
74
|
workerServerHeartbeatAction: typeof workerServerHeartbeatAction;
|
|
71
75
|
createWorkspaceHydrationLeaseHooks: typeof createWorkspaceHydrationLeaseHooks;
|
|
76
|
+
workerCommandHasWorkspaceEffect: typeof workerCommandHasWorkspaceEffect;
|
|
72
77
|
};
|
|
73
78
|
type WorkspaceSyncTrigger = {
|
|
74
79
|
type: "connect" | "inbound_head" | "write" | "edit" | "shell_inline" | "process_terminal" | "process_cancel" | "periodic" | "manual" | "remediation" | "remediation_confirm" | "remediation_reset";
|
|
@@ -1017,12 +1022,12 @@ export declare function prepareBuiltInToolPathsForTarget(input: {
|
|
|
1017
1022
|
rootDir: string;
|
|
1018
1023
|
access: "read" | "write";
|
|
1019
1024
|
}): Promise<WorkerBuiltInToolPaths | undefined>;
|
|
1020
|
-
export declare function readWorkerTextFile(branchPath: string, filePath: string, offset?: number, limit?: number, builtInPaths?: WorkerBuiltInToolPaths): WorkerReadFileResult
|
|
1021
|
-
export declare function writeWorkerTextFile(branchPath: string, filePath: string, content: string, builtInPaths?: WorkerBuiltInToolPaths): WorkerWriteFileResult
|
|
1025
|
+
export declare function readWorkerTextFile(branchPath: string, filePath: string, offset?: number, limit?: number, builtInPaths?: WorkerBuiltInToolPaths): Promise<WorkerReadFileResult>;
|
|
1026
|
+
export declare function writeWorkerTextFile(branchPath: string, filePath: string, content: string, builtInPaths?: WorkerBuiltInToolPaths): Promise<WorkerWriteFileResult>;
|
|
1022
1027
|
export declare function editWorkerTextFile(branchPath: string, filePath: string, edits: Array<{
|
|
1023
1028
|
oldText: string;
|
|
1024
1029
|
newText: string;
|
|
1025
|
-
}>, builtInPaths?: WorkerBuiltInToolPaths): WorkerEditFileResult
|
|
1030
|
+
}>, builtInPaths?: WorkerBuiltInToolPaths): Promise<WorkerEditFileResult>;
|
|
1026
1031
|
declare function executeWriteFileOperation(input: {
|
|
1027
1032
|
message: Extract<WorkerServerMessage, {
|
|
1028
1033
|
type: "write";
|
|
@@ -1049,17 +1054,17 @@ export declare function grepWorkerFiles(branchPath: string, input: {
|
|
|
1049
1054
|
glob?: string;
|
|
1050
1055
|
caseSensitive?: boolean;
|
|
1051
1056
|
limit?: number;
|
|
1052
|
-
}, builtInPaths?: WorkerBuiltInToolPaths): WorkerGrepResult
|
|
1057
|
+
}, builtInPaths?: WorkerBuiltInToolPaths): Promise<WorkerGrepResult>;
|
|
1053
1058
|
export declare function findWorkerFiles(branchPath: string, input: {
|
|
1054
1059
|
pattern?: string;
|
|
1055
1060
|
path?: string;
|
|
1056
1061
|
entryType?: string;
|
|
1057
1062
|
limit?: number;
|
|
1058
|
-
}, builtInPaths?: WorkerBuiltInToolPaths): WorkerFindResult
|
|
1059
|
-
export declare function listWorkerDirectory(branchPath: string, inputPath?: string, inputLimit?: number, builtInPaths?: WorkerBuiltInToolPaths): WorkerLsResult
|
|
1060
|
-
export declare function listWorkerCodeDirectory(branchPath: string, inputPath: string): WorkerCodeListResult
|
|
1061
|
-
export declare function readWorkerCodeFile(branchPath: string, inputPath: string): WorkerCodeReadResult
|
|
1062
|
-
export declare function readWorkerImageFile(branchPath: string, filePath: string, builtInPaths?: WorkerBuiltInToolPaths): WorkerViewFileBytesResult
|
|
1063
|
+
}, builtInPaths?: WorkerBuiltInToolPaths): Promise<WorkerFindResult>;
|
|
1064
|
+
export declare function listWorkerDirectory(branchPath: string, inputPath?: string, inputLimit?: number, builtInPaths?: WorkerBuiltInToolPaths): Promise<WorkerLsResult>;
|
|
1065
|
+
export declare function listWorkerCodeDirectory(branchPath: string, inputPath: string): Promise<WorkerCodeListResult>;
|
|
1066
|
+
export declare function readWorkerCodeFile(branchPath: string, inputPath: string): Promise<WorkerCodeReadResult>;
|
|
1067
|
+
export declare function readWorkerImageFile(branchPath: string, filePath: string, builtInPaths?: WorkerBuiltInToolPaths): Promise<WorkerViewFileBytesResult>;
|
|
1063
1068
|
declare function executeOperation(input: {
|
|
1064
1069
|
message: WorkerOperationServerMessage;
|
|
1065
1070
|
resolvedTarget: ResolvedWorkerSessionTarget;
|
|
@@ -18,7 +18,20 @@ export declare const PROJECT_DELETED_CHECKOUTS_DIRECTORY = ".r5d-deleted";
|
|
|
18
18
|
* later collection walk through it, so staging refuses rather than continue.
|
|
19
19
|
*/
|
|
20
20
|
export declare function stageProjectCheckoutForDeletion(projectRoot: string, branchName: string, checkoutPath: string): string;
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Rename a whole project root out of the active namespace, into the
|
|
23
|
+
* namespace's garbage directory, so removing a deleted project never walks
|
|
24
|
+
* its checkouts on the worker event loop. Same guarantees as
|
|
25
|
+
* stageProjectCheckoutForDeletion: one same-filesystem rename, a real garbage
|
|
26
|
+
* directory, collection by the garbage collector later.
|
|
27
|
+
*/
|
|
28
|
+
export declare function stageProjectForDeletion(projectsRoot: string, projectRoot: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* True when `candidate` is lexically a direct entry of a project's garbage
|
|
31
|
+
* directory (`<namespace>/<project>/.r5d-deleted/<entry>`, a staged checkout)
|
|
32
|
+
* or of a namespace's garbage directory (`<namespace>/.r5d-deleted/<entry>`,
|
|
33
|
+
* a staged whole project) under `projectsRoot`.
|
|
34
|
+
*/
|
|
22
35
|
export declare function isStagedProjectCheckoutPath(projectsRoot: string, candidate: string): boolean;
|
|
23
36
|
/**
|
|
24
37
|
* Whether a lexically staged path is safe to remove right now: every ancestor
|
|
@@ -30,7 +43,11 @@ export declare function isStagedProjectCheckoutPath(projectsRoot: string, candid
|
|
|
30
43
|
export declare function ownedStagedPath(projectsRoot: string, candidate: string): {
|
|
31
44
|
kind: "directory" | "link";
|
|
32
45
|
} | null;
|
|
33
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Every owned staged checkout under `<projectsRoot>/<namespace>/<project>/.r5d-deleted/`
|
|
48
|
+
* and every owned staged project under `<projectsRoot>/<namespace>/.r5d-deleted/`;
|
|
49
|
+
* linked ancestors are not followed.
|
|
50
|
+
*/
|
|
34
51
|
export declare function discoverStagedProjectCheckouts(projectsRoot: string): string[];
|
|
35
52
|
/**
|
|
36
53
|
* Delete a staged tree in a child process so a large recursive removal never
|
|
@@ -160,29 +160,29 @@ export declare class ProjectWorkspaceStateStore {
|
|
|
160
160
|
desiredProjects: readonly ProjectWorkspaceDesiredProjectInput[];
|
|
161
161
|
locallyPendingCreatedBranches?: readonly LocallyPendingCreatedProjectBranch[];
|
|
162
162
|
preserveOnlyBranches?: readonly PreserveOnlyProjectBranch[];
|
|
163
|
-
}): ProjectWorkspaceStateView
|
|
163
|
+
}): Promise<ProjectWorkspaceStateView>;
|
|
164
164
|
/** Record a successful enabled reseed without letting disabled config consume it. */
|
|
165
|
-
recordEnabledRepositoryTransition(input: EnabledRepositoryTransition): ProjectWorkspaceStateView
|
|
165
|
+
recordEnabledRepositoryTransition(input: EnabledRepositoryTransition): Promise<ProjectWorkspaceStateView>;
|
|
166
166
|
/** Persist the create intent before invoking Git so a crash cannot orphan an untracked worktree. */
|
|
167
167
|
recordPendingCreatedBranch(input: LocallyPendingCreatedProjectBranch & {
|
|
168
168
|
branchId: string;
|
|
169
|
-
}): ProjectWorkspaceStateView
|
|
169
|
+
}): Promise<ProjectWorkspaceStateView>;
|
|
170
170
|
/** Roll back an intent only when Git failed before creating its branch. */
|
|
171
|
-
rollbackPendingCreatedBranch(input: LocallyPendingCreatedProjectBranch): ProjectWorkspaceStateView
|
|
171
|
+
rollbackPendingCreatedBranch(input: LocallyPendingCreatedProjectBranch): Promise<ProjectWorkspaceStateView>;
|
|
172
172
|
/** Clear a create intent after an authoritative desired config contains it. */
|
|
173
|
-
clearPendingCreatedBranch(input: LocallyPendingCreatedProjectBranch): ProjectWorkspaceStateView
|
|
173
|
+
clearPendingCreatedBranch(input: LocallyPendingCreatedProjectBranch): Promise<ProjectWorkspaceStateView>;
|
|
174
174
|
/** Persist a branch tombstone before mutating its linked worktree or local ref. */
|
|
175
|
-
beginBranchDeletion(input: LocallyPendingCreatedProjectBranch): ProjectWorkspaceStateView
|
|
175
|
+
beginBranchDeletion(input: LocallyPendingCreatedProjectBranch): Promise<ProjectWorkspaceStateView>;
|
|
176
176
|
recordTreePublication(input: {
|
|
177
177
|
tombstoneIds: readonly string[];
|
|
178
178
|
publishedHead: string;
|
|
179
|
-
}): ProjectWorkspaceStateView
|
|
179
|
+
}): Promise<ProjectWorkspaceStateView>;
|
|
180
180
|
recordMirrorRefDeletion(input: {
|
|
181
181
|
tombstoneId: string;
|
|
182
182
|
branchName: string;
|
|
183
|
-
}): ProjectWorkspaceStateView
|
|
183
|
+
}): Promise<ProjectWorkspaceStateView>;
|
|
184
184
|
}
|
|
185
185
|
export declare const projectWorkspaceStateTestHarness: {
|
|
186
|
-
writeEmptyStateAtDurableRootBoundary(stateRoot: string, afterStateRootFsync: () => void): void
|
|
186
|
+
writeEmptyStateAtDurableRootBoundary(stateRoot: string, afterStateRootFsync: () => void): Promise<void>;
|
|
187
187
|
};
|
|
188
188
|
export {};
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { type ProjectMirrorMount, type ProjectMirrorRefsTokens } from "./project-mirror-refs-token";
|
|
2
|
+
import type { WorkspaceFilesystemJobContext } from "./workspace-filesystem-job-types";
|
|
2
3
|
export declare const PROJECT_WORKTREE_SNAPSHOT_PREFIX = "r5d-project-worktrees-";
|
|
3
4
|
declare const PROJECT_WORKTREE_SNAPSHOT_MANIFEST_VERSION: 1;
|
|
5
|
+
/**
|
|
6
|
+
* Distinguishes this control thread's live snapshots from stale ones left by
|
|
7
|
+
* an earlier process that reused the PID. The control thread passes it to
|
|
8
|
+
* every snapshot job, so the executor thread never invents its own.
|
|
9
|
+
*/
|
|
10
|
+
export declare const PROJECT_WORKTREE_SNAPSHOT_OWNER_SESSION_ID: `${string}-${string}-${string}-${string}-${string}`;
|
|
4
11
|
/**
|
|
5
12
|
* `project` snapshots cover every configured branch and restore by replacing
|
|
6
13
|
* the whole project root. `branches` snapshots cover only the checkouts one
|
|
7
14
|
* reconciliation rewrites and restore those paths individually, so a rollback
|
|
8
15
|
* never touches a checkout the transaction did not.
|
|
9
16
|
*/
|
|
10
|
-
type ProjectWorktreeSnapshotScope = "project" | "branches";
|
|
11
|
-
type ProjectWorktreeSnapshotManifest = {
|
|
17
|
+
export type ProjectWorktreeSnapshotScope = "project" | "branches";
|
|
18
|
+
export type ProjectWorktreeSnapshotManifest = {
|
|
12
19
|
version: typeof PROJECT_WORKTREE_SNAPSHOT_MANIFEST_VERSION;
|
|
13
20
|
state: "building" | "prepared" | "restoring" | "consumed";
|
|
14
21
|
scope: ProjectWorktreeSnapshotScope;
|
|
@@ -21,6 +28,11 @@ type ProjectWorktreeSnapshotManifest = {
|
|
|
21
28
|
}>;
|
|
22
29
|
createdAt: string;
|
|
23
30
|
};
|
|
31
|
+
export type ProjectWorktreeSnapshot = {
|
|
32
|
+
root: string;
|
|
33
|
+
manifest: ProjectWorktreeSnapshotManifest;
|
|
34
|
+
paths: Map<string, string>;
|
|
35
|
+
};
|
|
24
36
|
export type ProjectWorktreeSnapshotProgress = {
|
|
25
37
|
operation: "snapshot" | "restore";
|
|
26
38
|
phase: "scanning" | "copying" | "moving" | "syncing" | "cleaning";
|
|
@@ -161,10 +173,10 @@ export declare function createOrRetryLinkedProjectBranch(input: {
|
|
|
161
173
|
branchName: string;
|
|
162
174
|
workingTree: ProjectWorktreeWorkingTreeMode;
|
|
163
175
|
pendingRetry: boolean;
|
|
164
|
-
}): {
|
|
176
|
+
}): Promise<{
|
|
165
177
|
branchPath: string;
|
|
166
178
|
baseCommitHash: string;
|
|
167
|
-
}
|
|
179
|
+
}>;
|
|
168
180
|
export type ProjectCheckoutState = "linked" | "missing" | "not_a_worktree";
|
|
169
181
|
export type ProjectCheckoutInventory = {
|
|
170
182
|
/** Branches whose checkout is this project's primary clone or one of its linked worktrees. */
|
|
@@ -216,10 +228,18 @@ export declare function deleteLinkedProjectBranch(input: {
|
|
|
216
228
|
*/
|
|
217
229
|
stagedForCollectionPath?: string;
|
|
218
230
|
};
|
|
231
|
+
/**
|
|
232
|
+
* Retire a whole project: one rename into the namespace garbage directory
|
|
233
|
+
* (the tree, primary Git objects included, is removed by the collector
|
|
234
|
+
* outside every lease), or nothing when the root is already gone.
|
|
235
|
+
*/
|
|
219
236
|
export declare function removeProjectWorktrees(input: {
|
|
237
|
+
projectsRoot: string;
|
|
220
238
|
projectRoot: string;
|
|
221
239
|
branchNames: readonly string[];
|
|
222
|
-
}):
|
|
240
|
+
}): {
|
|
241
|
+
stagedForCollectionPath?: string;
|
|
242
|
+
};
|
|
223
243
|
export declare function deleteProjectMirrorBranch(input: {
|
|
224
244
|
gitDirectory: string;
|
|
225
245
|
branchName: string;
|
|
@@ -335,6 +355,30 @@ export declare function reseedProjectHeadsFromMirror(input: ProjectMirrorHeadUpd
|
|
|
335
355
|
mirrorHead: string | null;
|
|
336
356
|
reseeded: boolean;
|
|
337
357
|
}>>;
|
|
358
|
+
/** Executor job body: `snapshotBranchTrees` with progress relayed to the control thread. */
|
|
359
|
+
export declare function runProjectSnapshotCreateJob(input: {
|
|
360
|
+
projectRoot: string;
|
|
361
|
+
branches: ProjectWorktreeBranch[];
|
|
362
|
+
temporaryRoot: string;
|
|
363
|
+
scope: ProjectWorktreeSnapshotScope;
|
|
364
|
+
ownerSessionId: string;
|
|
365
|
+
}, context: WorkspaceFilesystemJobContext): ProjectWorktreeSnapshot;
|
|
366
|
+
/** Executor job body: mirror captured checkouts back over reconciled worktrees. */
|
|
367
|
+
export declare function runProjectSnapshotApplyJob(input: {
|
|
368
|
+
mirrors: Array<{
|
|
369
|
+
snapshotPath: string;
|
|
370
|
+
targetPath: string;
|
|
371
|
+
}>;
|
|
372
|
+
}): null;
|
|
373
|
+
/** Executor job body: `persistReconciledProjectAndConsumeSnapshot`. */
|
|
374
|
+
export declare function runProjectSnapshotPersistJob(input: {
|
|
375
|
+
snapshot: ProjectWorktreeSnapshot;
|
|
376
|
+
durabilityPaths: string[] | null;
|
|
377
|
+
}): null;
|
|
378
|
+
/** Executor job body: `rollbackProjectWorktreeSnapshot`. */
|
|
379
|
+
export declare function runProjectSnapshotRollbackJob(input: {
|
|
380
|
+
snapshot: ProjectWorktreeSnapshot;
|
|
381
|
+
}): null;
|
|
338
382
|
export declare const projectWorktreesTestHarness: {
|
|
339
383
|
commandArgs: typeof gitCommandArgs;
|
|
340
384
|
configureRepository: typeof configureRepository;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Messages between the control thread and the recovery journal thread. */
|
|
2
|
+
export declare const WORKER_RECOVERY_JOURNAL_OPS: readonly ["admit", "readmitUnknownBranchDeletion", "record", "unknown", "unknownRun", "acknowledgeResult", "acknowledgeOutput", "acknowledgeTerminal", "cancelSession", "isRequestCancelled", "operations", "recoveryState", "terminals", "outputReplayBegin", "outputReplayNext", "outputReplayEnd"];
|
|
3
|
+
export type WorkerRecoveryJournalOp = (typeof WORKER_RECOVERY_JOURNAL_OPS)[number];
|
|
4
|
+
export declare function isWorkerRecoveryJournalOp(value: unknown): value is WorkerRecoveryJournalOp;
|
|
5
|
+
export type WorkerRecoveryJournalEntry = {
|
|
6
|
+
seq: number;
|
|
7
|
+
op: WorkerRecoveryJournalOp;
|
|
8
|
+
args: unknown[];
|
|
9
|
+
};
|
|
10
|
+
export type WorkerRecoveryJournalBatchRequest = {
|
|
11
|
+
type: "batch";
|
|
12
|
+
id: number;
|
|
13
|
+
entries: WorkerRecoveryJournalEntry[];
|
|
14
|
+
};
|
|
15
|
+
export type WorkerRecoveryJournalSerializedError = {
|
|
16
|
+
name: string;
|
|
17
|
+
message: string;
|
|
18
|
+
stack?: string;
|
|
19
|
+
};
|
|
20
|
+
export type WorkerRecoveryJournalThreadReply = {
|
|
21
|
+
type: "ready";
|
|
22
|
+
ledgerId: string;
|
|
23
|
+
} | {
|
|
24
|
+
type: "batch_result";
|
|
25
|
+
id: number;
|
|
26
|
+
results: Array<{
|
|
27
|
+
seq: number;
|
|
28
|
+
value: unknown;
|
|
29
|
+
}>;
|
|
30
|
+
} | {
|
|
31
|
+
type: "batch_failure";
|
|
32
|
+
id: number;
|
|
33
|
+
error: WorkerRecoveryJournalSerializedError;
|
|
34
|
+
};
|
|
35
|
+
export declare function serializeWorkerRecoveryJournalError(error: unknown): WorkerRecoveryJournalSerializedError;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MessagePort } from "node:worker_threads";
|
|
2
|
+
/**
|
|
3
|
+
* The recovery journal thread's loop. It owns the one WorkerRecoveryStore
|
|
4
|
+
* handle of the process and applies the control thread's entries strictly in
|
|
5
|
+
* sequence, one durable commit per batch. Everything the store guarantees
|
|
6
|
+
* (acceptance before effects, offsets, terminal lengths, unknown gating,
|
|
7
|
+
* acknowledgement retention, cancel snapshots) is unchanged; only the thread
|
|
8
|
+
* running it moved. `beforeBatch` exists for the stall fixture used by tests.
|
|
9
|
+
*/
|
|
10
|
+
export declare function serveRecoveryJournal(port: MessagePort, filename: string, hooks?: {
|
|
11
|
+
beforeBatch?: () => void;
|
|
12
|
+
}): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { WorkerOperationRecovery, WorkerOutputOffsets } from "./recovery-protocol";
|
|
2
|
+
/**
|
|
3
|
+
* The control-thread side of the worker's durable recovery journal.
|
|
4
|
+
*
|
|
5
|
+
* The SQLite store (WAL, `synchronous=FULL`) lives on the journal thread;
|
|
6
|
+
* this facade owns one ordered channel of store operations and the ordered
|
|
7
|
+
* delivery of the messages those operations make durable. Its invariants:
|
|
8
|
+
*
|
|
9
|
+
* - order: every entry is applied in append order, one durable commit per
|
|
10
|
+
* batch, so an `unknown` appended after a `record` applies after it, and an
|
|
11
|
+
* `admit` of a later request commits after the `record` of an earlier one;
|
|
12
|
+
* - commit before send: a keyed message is written to the socket only after
|
|
13
|
+
* the batch holding its `record` committed, in append order, so a terminal
|
|
14
|
+
* never leaves before the output recorded ahead of it and a response is
|
|
15
|
+
* never acknowledged before it is durable;
|
|
16
|
+
* - commit before effects: `admit` resolves after its commit; handlers run
|
|
17
|
+
* after it;
|
|
18
|
+
* - bounded: everything the facade retains (entries waiting for the thread,
|
|
19
|
+
* entries in the batch being committed, and committed messages not yet
|
|
20
|
+
* drained to the socket) is charged to one general budget of entries and
|
|
21
|
+
* bytes, reserved atomically before anything is queued and released only
|
|
22
|
+
* when the item is actually done; unknown marks, acknowledgements and
|
|
23
|
+
* handshake reads use a separate reserved control budget. Output producers
|
|
24
|
+
* and admissions wait for budget in bounded waiting rooms; a result that
|
|
25
|
+
* cannot be buffered is never sent early: its operation is marked unknown,
|
|
26
|
+
* which is exactly what a crash would have left;
|
|
27
|
+
* - fail closed: a thread exit, a failed batch, or an exhausted control budget
|
|
28
|
+
* fails every pending and future entry and reports once; the store is never
|
|
29
|
+
* re-opened in-process.
|
|
30
|
+
*/
|
|
31
|
+
type Message = {
|
|
32
|
+
type: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
};
|
|
35
|
+
export declare const WORKER_RECOVERY_JOURNAL_MAX_BATCH_ENTRIES = 256;
|
|
36
|
+
export declare const WORKER_RECOVERY_JOURNAL_MAX_PENDING_ENTRIES = 4096;
|
|
37
|
+
export declare const WORKER_RECOVERY_JOURNAL_MAX_PENDING_BYTES: number;
|
|
38
|
+
export declare const WORKER_RECOVERY_JOURNAL_MAX_CONTROL_ENTRIES = 2048;
|
|
39
|
+
export declare const WORKER_RECOVERY_JOURNAL_MAX_CONTROL_BYTES: number;
|
|
40
|
+
export declare const WORKER_RECOVERY_JOURNAL_MAX_ENTRY_BYTES: number;
|
|
41
|
+
export declare const WORKER_RECOVERY_JOURNAL_OUTPUT_CHUNK_CHARACTERS: number;
|
|
42
|
+
export declare const WORKER_RECOVERY_JOURNAL_MAX_WAITING_ADMISSIONS = 512;
|
|
43
|
+
export declare const WORKER_RECOVERY_JOURNAL_MAX_WAITING_OUTPUT = 1024;
|
|
44
|
+
export declare const WORKER_RECOVERY_JOURNAL_MAX_WAITING_BYTES: number;
|
|
45
|
+
export declare const WORKER_RECOVERY_JOURNAL_REPLAY_PAGE_FRAMES = 32;
|
|
46
|
+
export declare class WorkerRecoveryJournalError extends Error {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
}
|
|
49
|
+
/** The journal is intact but its bounded waiting room is full; the caller must not queue more. */
|
|
50
|
+
export declare class WorkerRecoveryJournalBusyError extends WorkerRecoveryJournalError {
|
|
51
|
+
readonly name = "WorkerRecoveryJournalBusyError";
|
|
52
|
+
}
|
|
53
|
+
export type WorkerRecoveryJournalAdmission = {
|
|
54
|
+
admission: "new" | "duplicate" | "unknown" | "conflict";
|
|
55
|
+
response: Message | undefined;
|
|
56
|
+
cancelled: boolean;
|
|
57
|
+
};
|
|
58
|
+
export type WorkerRecoveryJournalRecoveryState = {
|
|
59
|
+
operations: WorkerOperationRecovery[];
|
|
60
|
+
cursors: WorkerOutputOffsets[];
|
|
61
|
+
responses: Message[];
|
|
62
|
+
terminals: Message[];
|
|
63
|
+
};
|
|
64
|
+
export type WorkerRecoveryJournalStatus = {
|
|
65
|
+
/** Entries waiting for the thread plus entries in the batch being committed. */
|
|
66
|
+
pendingEntries: number;
|
|
67
|
+
/** General budget in use: pending entries and undelivered messages, in entries and bytes. */
|
|
68
|
+
retainedEntries: number;
|
|
69
|
+
retainedBytes: number;
|
|
70
|
+
controlEntries: number;
|
|
71
|
+
controlBytes: number;
|
|
72
|
+
waitingAdmissions: number;
|
|
73
|
+
waitingOutput: number;
|
|
74
|
+
/** Payload bytes held by requests and output chunks waiting for budget. */
|
|
75
|
+
waitingBytes: number;
|
|
76
|
+
/** Bytes of store replies (handshake state, output pages) the caller has not consumed yet. */
|
|
77
|
+
retainedReplyBytes: number;
|
|
78
|
+
committedSeq: number;
|
|
79
|
+
appendedSeq: number;
|
|
80
|
+
undelivered: number;
|
|
81
|
+
failed: string | null;
|
|
82
|
+
};
|
|
83
|
+
export type WorkerRecoveryJournalOptions = {
|
|
84
|
+
filename: string;
|
|
85
|
+
threadModulePath?: string;
|
|
86
|
+
/** Extra `workerData` for the thread module (test fixtures only). */
|
|
87
|
+
threadWorkerData?: Record<string, unknown>;
|
|
88
|
+
log?: (line: string) => void;
|
|
89
|
+
maxBatchEntries?: number;
|
|
90
|
+
maxPendingEntries?: number;
|
|
91
|
+
maxPendingBytes?: number;
|
|
92
|
+
maxControlEntries?: number;
|
|
93
|
+
maxControlBytes?: number;
|
|
94
|
+
maxEntryBytes?: number;
|
|
95
|
+
maxWaitingAdmissions?: number;
|
|
96
|
+
maxWaitingOutput?: number;
|
|
97
|
+
maxWaitingBytes?: number;
|
|
98
|
+
};
|
|
99
|
+
export declare class WorkerRecoveryJournal {
|
|
100
|
+
readonly ledgerId: string;
|
|
101
|
+
private readonly worker;
|
|
102
|
+
private readonly log;
|
|
103
|
+
private readonly maxBatchEntries;
|
|
104
|
+
private readonly maxEntryBytes;
|
|
105
|
+
private readonly maxWaitingAdmissions;
|
|
106
|
+
private readonly maxWaitingOutput;
|
|
107
|
+
private readonly maxWaitingBytes;
|
|
108
|
+
private waitingBytes;
|
|
109
|
+
private retainedReplyBytes;
|
|
110
|
+
private readonly general;
|
|
111
|
+
private readonly control;
|
|
112
|
+
private nextSeq;
|
|
113
|
+
private committedSeq;
|
|
114
|
+
private readonly pending;
|
|
115
|
+
private inFlight;
|
|
116
|
+
private nextBatchId;
|
|
117
|
+
private readonly waiters;
|
|
118
|
+
private readonly outbound;
|
|
119
|
+
private deliveryPaused;
|
|
120
|
+
private replayCoveredSeq;
|
|
121
|
+
private failure;
|
|
122
|
+
private closed;
|
|
123
|
+
private readonly pendingAdmissions;
|
|
124
|
+
private readonly activeOperations;
|
|
125
|
+
private readonly cancelledRequestIds;
|
|
126
|
+
private readonly unknownRequestIds;
|
|
127
|
+
private readonly unknownRunIds;
|
|
128
|
+
private readonly coalescedControl;
|
|
129
|
+
private readonly flushWaiters;
|
|
130
|
+
/** Delivers one committed message in order; returns false when the transport declined it (the pumps re-send). */
|
|
131
|
+
deliver: ((message: Message) => boolean) | undefined;
|
|
132
|
+
/** Invoked once when the journal fails closed. */
|
|
133
|
+
onFailure: ((error: Error) => void) | undefined;
|
|
134
|
+
private constructor();
|
|
135
|
+
/** Start the journal thread, open the store there (its crash recovery runs there) and return once it is ready. */
|
|
136
|
+
static open(options: WorkerRecoveryJournalOptions): Promise<WorkerRecoveryJournal>;
|
|
137
|
+
status(): WorkerRecoveryJournalStatus;
|
|
138
|
+
/**
|
|
139
|
+
* Durably admit an inbound operation; resolves after the commit. Waits for
|
|
140
|
+
* general budget in a bounded waiting room; when that room is full the
|
|
141
|
+
* admission is refused at once (WorkerRecoveryJournalBusyError) so the
|
|
142
|
+
* message is neither acknowledged nor retained.
|
|
143
|
+
*/
|
|
144
|
+
admit(message: Message & {
|
|
145
|
+
requestId: string;
|
|
146
|
+
}): Promise<WorkerRecoveryJournalAdmission>;
|
|
147
|
+
/**
|
|
148
|
+
* Re-admit an unknown branch deletion. Like `admit`, the request is a
|
|
149
|
+
* pending admission from before the await, so a session cancellation that
|
|
150
|
+
* lands while the disk is slow snapshots it and the handler's recheck after
|
|
151
|
+
* the await refuses to rerun the deletion.
|
|
152
|
+
*/
|
|
153
|
+
readmitUnknownBranchDeletion(requestId: string, sessionId: string | undefined): Promise<boolean>;
|
|
154
|
+
/**
|
|
155
|
+
* Queue an outbound message. Keyed messages are recorded (when the store
|
|
156
|
+
* records their type) and delivered in order after their commit; other
|
|
157
|
+
* messages are delivered at once. Returns false when the message was not
|
|
158
|
+
* queued: its operation is already unknown, or the budget cannot hold it,
|
|
159
|
+
* in which case the operation is marked unknown so it is neither
|
|
160
|
+
* acknowledged early nor sent later.
|
|
161
|
+
*/
|
|
162
|
+
send(message: Message): boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Queue an output frame, split into bounded records, waiting for budget so
|
|
165
|
+
* a stalled journal throttles the producer (and through the pipe, the
|
|
166
|
+
* child) instead of growing memory.
|
|
167
|
+
*/
|
|
168
|
+
sendOutput(frame: Message & {
|
|
169
|
+
type: "exec_output";
|
|
170
|
+
runId: string;
|
|
171
|
+
stream: "stdout" | "stderr";
|
|
172
|
+
data: string;
|
|
173
|
+
}): Promise<void>;
|
|
174
|
+
/**
|
|
175
|
+
* Reserve general budget atomically, waiting in the bounded room of `kind`
|
|
176
|
+
* when it does not fit now. The waiting room is bounded in count and in
|
|
177
|
+
* payload bytes (the message a waiting admission holds is charged before it
|
|
178
|
+
* waits), and an entry that could never fit the general budget is refused
|
|
179
|
+
* at once rather than parked at the head of the queue.
|
|
180
|
+
*/
|
|
181
|
+
private reserveGeneral;
|
|
182
|
+
/** Reserve for the head waiter inside this synchronous call, so no continuation can outrun the accounting. */
|
|
183
|
+
private wakeWaiters;
|
|
184
|
+
private enqueueRecord;
|
|
185
|
+
private refuse;
|
|
186
|
+
private trackRecorded;
|
|
187
|
+
/**
|
|
188
|
+
* Mark an operation unknown: never sent again (undelivered messages for it
|
|
189
|
+
* are dropped now, and the durable mark keeps replay from resurrecting it).
|
|
190
|
+
* Coalesced per id; uses the reserved control budget.
|
|
191
|
+
*/
|
|
192
|
+
unknown(requestId: string): void;
|
|
193
|
+
unknownRun(runId: string): void;
|
|
194
|
+
acknowledgeResult(requestId: string): void;
|
|
195
|
+
/** Monotonic per stream: a later acknowledgement replaces a pending earlier one. */
|
|
196
|
+
acknowledgeOutput(runId: string, stream: "stdout" | "stderr", offset: number): void;
|
|
197
|
+
acknowledgeTerminal(runId: string): void;
|
|
198
|
+
/**
|
|
199
|
+
* Snapshot every admitted and pending operation of the session as cancelled
|
|
200
|
+
* (the handlers check `isRequestCancelled` at their fences) and append the
|
|
201
|
+
* durable cancellation, which the thread applies after every admission
|
|
202
|
+
* appended before it. Resolves after the commit.
|
|
203
|
+
*/
|
|
204
|
+
cancelSession(sessionId: string): Promise<void>;
|
|
205
|
+
isRequestCancelled(requestId: string): boolean;
|
|
206
|
+
/** Every operation admitted or awaiting admission in this process that has not completed, for the runtime fence and lease expiry. */
|
|
207
|
+
activeAndPendingOperations(): WorkerOperationRecovery[];
|
|
208
|
+
/**
|
|
209
|
+
* The handshake read. One entry, so it sees exactly the entries appended
|
|
210
|
+
* before it and none after: the replay boundary. Callers pause delivery
|
|
211
|
+
* before appending it and resume after the handshake, so every keyed
|
|
212
|
+
* message committed before the mark is replayed once (from the store) and
|
|
213
|
+
* nothing committed after it can enter the replay of responses, operations
|
|
214
|
+
* or terminals; output pages that follow may re-send frames the pump would
|
|
215
|
+
* re-send anyway, which the server's offset cursors absorb.
|
|
216
|
+
*/
|
|
217
|
+
recoveryState(requests: string[]): Promise<WorkerRecoveryJournalRecoveryState>;
|
|
218
|
+
private retainedStateBytes;
|
|
219
|
+
/** Output replay, one bounded page at a time; a page is accounted until the consumer asks for the next one. */
|
|
220
|
+
outputReplay(offsets: WorkerOutputOffsets[] | undefined, pageFrames?: number): AsyncGenerator<Message[]>;
|
|
221
|
+
terminals(): Promise<Message[]>;
|
|
222
|
+
/** Delivery of committed keyed messages is held (in order) while a handshake replays from the store. */
|
|
223
|
+
pauseDelivery(): void;
|
|
224
|
+
resumeDelivery(): void;
|
|
225
|
+
/** Resolves once every entry appended so far is committed (or the journal failed). */
|
|
226
|
+
flush(): Promise<void>;
|
|
227
|
+
close(): void;
|
|
228
|
+
private assertOpen;
|
|
229
|
+
/** Control entries are tiny and idempotent per key: a pending one is updated in place instead of queued again. */
|
|
230
|
+
private appendControl;
|
|
231
|
+
/** A control-budget entry whose result the caller awaits; rejects when the budget is exhausted. */
|
|
232
|
+
private appendControlOrThrow;
|
|
233
|
+
private lastAppended;
|
|
234
|
+
/** Queue an entry whose budget was already reserved from `budget`. */
|
|
235
|
+
private append;
|
|
236
|
+
private dispatch;
|
|
237
|
+
private onReply;
|
|
238
|
+
private dropOutbound;
|
|
239
|
+
private releaseOutbound;
|
|
240
|
+
private deliverReady;
|
|
241
|
+
private fail;
|
|
242
|
+
private failEntries;
|
|
243
|
+
}
|
|
244
|
+
/** The packaged thread module lives next to this module with the same extension (.ts in source, .cjs/.mjs in the package). */
|
|
245
|
+
export declare function workerRecoveryJournalThreadModulePath(): string;
|
|
246
|
+
export {};
|