@ricsam/r5d-worker 0.0.137 → 0.0.139
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/README.md +4 -94
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/main.mjs +46 -7543
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/personal/action-scheduler.mjs +31 -0
- package/dist/mjs/personal/client.mjs +215 -0
- package/dist/mjs/personal/runtime.mjs +359 -0
- package/dist/mjs/personal/tcp.mjs +217 -0
- package/dist/mjs/runtime/adapter.mjs +42 -0
- package/dist/mjs/runtime/client.mjs +95 -0
- package/dist/mjs/runtime/daemon.mjs +131 -0
- package/dist/mjs/runtime/executor.mjs +436 -0
- package/dist/mjs/runtime/index.mjs +15 -0
- package/dist/mjs/runtime/protocol.mjs +139 -0
- package/dist/mjs/runtime/pty-host.mjs +156 -0
- package/dist/mjs/runtime/pty.mjs +127 -0
- package/dist/mjs/runtime/releases/artifacts.mjs +201 -0
- package/dist/mjs/runtime/releases/grants.mjs +93 -0
- package/dist/mjs/runtime/releases/main.mjs +101 -0
- package/dist/mjs/runtime/releases/manager.mjs +541 -0
- package/dist/mjs/runtime/releases/process.mjs +88 -0
- package/dist/mjs/runtime/releases/retirement.mjs +18 -0
- package/dist/mjs/runtime/releases/rpc-client.mjs +184 -0
- package/dist/mjs/runtime/releases/rpc-main.mjs +213 -0
- package/dist/mjs/runtime/releases/rpc-protocol.mjs +103 -0
- package/dist/mjs/runtime/storage.mjs +156 -0
- package/dist/mjs/runtime/workspace/artifacts.mjs +121 -0
- package/dist/mjs/runtime/workspace/authority.mjs +1355 -0
- package/dist/mjs/runtime/workspace/contracts.mjs +60 -0
- package/dist/mjs/runtime/workspace/file-write.mjs +23 -0
- package/dist/mjs/runtime/workspace/files.mjs +308 -0
- package/dist/mjs/runtime/workspace/http-body.mjs +45 -0
- package/dist/mjs/runtime/workspace/storage-client.mjs +59 -0
- package/dist/mjs/runtime/workspace/storage-wire.mjs +53 -0
- package/dist/types/main.d.ts +0 -1167
- package/dist/types/personal/action-scheduler.d.ts +23 -0
- package/dist/types/personal/client.d.ts +14 -0
- package/dist/types/personal/runtime.d.ts +121 -0
- package/dist/types/personal/tcp.d.ts +14 -0
- package/dist/types/runtime/client.d.ts +3 -1
- package/dist/types/runtime/executor.d.ts +6 -1
- package/dist/types/runtime/protocol.d.ts +176 -4
- package/dist/types/runtime/pty.d.ts +1 -0
- package/dist/types/runtime/releases/artifacts.d.ts +51 -0
- package/dist/types/runtime/releases/grants.d.ts +16 -0
- package/dist/types/runtime/releases/manager.d.ts +145 -0
- package/dist/types/runtime/releases/process.d.ts +14 -0
- package/dist/types/runtime/releases/retirement.d.ts +15 -0
- package/dist/types/runtime/releases/rpc-client.d.ts +52 -0
- package/dist/types/runtime/releases/rpc-main.d.ts +10 -0
- package/dist/types/runtime/releases/rpc-protocol.d.ts +1139 -0
- package/dist/types/runtime/workspace/artifacts.d.ts +30 -0
- package/dist/types/runtime/workspace/authority.d.ts +338 -0
- package/dist/types/runtime/workspace/contracts.d.ts +86 -0
- package/dist/types/runtime/workspace/file-write.d.ts +14 -0
- package/dist/types/runtime/workspace/files.d.ts +21 -0
- package/dist/types/runtime/workspace/http-body.d.ts +9 -0
- package/dist/types/runtime/workspace/storage-client.d.ts +23 -0
- package/dist/types/runtime/workspace/storage-wire.d.ts +138 -0
- package/package.json +8 -6
- package/dist/cjs/atomic-rename.cjs +0 -303
- package/dist/cjs/browser-upload.cjs +0 -73
- package/dist/cjs/cli-update.cjs +0 -108
- package/dist/cjs/command-launcher.cjs +0 -423
- package/dist/cjs/control-command-policy.cjs +0 -177
- package/dist/cjs/git-blob-hash.cjs +0 -41
- package/dist/cjs/git-identity.cjs +0 -39
- package/dist/cjs/git-process-environment.cjs +0 -133
- package/dist/cjs/inline-output.cjs +0 -62
- package/dist/cjs/main.cjs +0 -7517
- package/dist/cjs/managed-paths.cjs +0 -215
- package/dist/cjs/plan-lint.cjs +0 -165
- package/dist/cjs/plan-parser.cjs +0 -127
- package/dist/cjs/plan-paths.cjs +0 -53
- package/dist/cjs/port-forward-client.cjs +0 -206
- package/dist/cjs/process-tree.cjs +0 -117
- package/dist/cjs/project-checkout-garbage.cjs +0 -219
- package/dist/cjs/project-mirror-fetch-policy.cjs +0 -82
- package/dist/cjs/project-mirror-refs-token.cjs +0 -85
- package/dist/cjs/project-workspace-state.cjs +0 -972
- package/dist/cjs/project-worktrees.cjs +0 -1713
- package/dist/cjs/pty-output-coalescer.cjs +0 -87
- package/dist/cjs/recovery-journal-protocol.cjs +0 -56
- package/dist/cjs/recovery-journal-runtime.cjs +0 -133
- package/dist/cjs/recovery-journal-thread.cjs +0 -9
- package/dist/cjs/recovery-journal.cjs +0 -742
- package/dist/cjs/recovery-output-window.cjs +0 -77
- package/dist/cjs/recovery-protocol.cjs +0 -31
- package/dist/cjs/recovery-store.cjs +0 -252
- package/dist/cjs/registry-auth.cjs +0 -310
- package/dist/cjs/repository-transition-policy.cjs +0 -49
- package/dist/cjs/runtime-version.cjs +0 -88
- package/dist/cjs/session-file-mutations.cjs +0 -61
- package/dist/cjs/supervisor.cjs +0 -147
- package/dist/cjs/three-way-merge.cjs +0 -346
- package/dist/cjs/working-tree-mirror.cjs +0 -1872
- package/dist/cjs/workspace-automatic-sync-policy.cjs +0 -79
- package/dist/cjs/workspace-branch-incarnation-policy.cjs +0 -37
- package/dist/cjs/workspace-command-sync-policy.cjs +0 -112
- package/dist/cjs/workspace-command-targets.cjs +0 -63
- package/dist/cjs/workspace-filesystem-executor-thread.cjs +0 -36
- package/dist/cjs/workspace-filesystem-executor.cjs +0 -327
- package/dist/cjs/workspace-filesystem-job-types.cjs +0 -144
- package/dist/cjs/workspace-filesystem-jobs.cjs +0 -59
- package/dist/cjs/workspace-git-sync.cjs +0 -3595
- package/dist/cjs/workspace-hydration-ledger.cjs +0 -83
- package/dist/cjs/workspace-hydration-merge.cjs +0 -448
- package/dist/cjs/workspace-hydration-recovery-state.cjs +0 -53
- package/dist/cjs/workspace-incident-state.cjs +0 -37
- package/dist/cjs/workspace-merge-projection.cjs +0 -628
- package/dist/cjs/workspace-mount-boundary.cjs +0 -71
- package/dist/cjs/workspace-mount-hold-fence.cjs +0 -120
- package/dist/cjs/workspace-mutation-gate.cjs +0 -127
- package/dist/cjs/workspace-path-move.cjs +0 -195
- package/dist/cjs/workspace-preserve-only-policy.cjs +0 -36
- package/dist/cjs/workspace-project-config-policy.cjs +0 -53
- package/dist/cjs/workspace-projection-ledger.cjs +0 -81
- package/dist/cjs/workspace-publication-evidence.cjs +0 -46
- package/dist/cjs/workspace-sync-coalescer.cjs +0 -58
- package/dist/mjs/atomic-rename.mjs +0 -261
- package/dist/mjs/browser-upload.mjs +0 -42
- package/dist/mjs/command-launcher.mjs +0 -393
- package/dist/mjs/control-command-policy.mjs +0 -146
- package/dist/mjs/git-blob-hash.mjs +0 -16
- package/dist/mjs/git-identity.mjs +0 -15
- package/dist/mjs/git-process-environment.mjs +0 -105
- package/dist/mjs/inline-output.mjs +0 -37
- package/dist/mjs/managed-paths.mjs +0 -174
- package/dist/mjs/plan-lint.mjs +0 -129
- package/dist/mjs/plan-parser.mjs +0 -94
- package/dist/mjs/plan-paths.mjs +0 -25
- package/dist/mjs/port-forward-client.mjs +0 -172
- package/dist/mjs/project-checkout-garbage.mjs +0 -178
- package/dist/mjs/project-mirror-fetch-policy.mjs +0 -56
- package/dist/mjs/project-mirror-refs-token.mjs +0 -53
- package/dist/mjs/project-workspace-state.mjs +0 -937
- package/dist/mjs/project-worktrees.mjs +0 -1657
- package/dist/mjs/pty-output-coalescer.mjs +0 -61
- package/dist/mjs/recovery-journal-protocol.mjs +0 -30
- package/dist/mjs/recovery-journal-runtime.mjs +0 -112
- package/dist/mjs/recovery-journal-thread.mjs +0 -8
- package/dist/mjs/recovery-journal.mjs +0 -694
- package/dist/mjs/recovery-output-window.mjs +0 -53
- package/dist/mjs/recovery-protocol.mjs +0 -6
- package/dist/mjs/recovery-store.mjs +0 -217
- package/dist/mjs/registry-auth.mjs +0 -269
- package/dist/mjs/repository-transition-policy.mjs +0 -22
- package/dist/mjs/runtime-version.mjs +0 -54
- package/dist/mjs/session-file-mutations.mjs +0 -37
- package/dist/mjs/supervisor.mjs +0 -114
- package/dist/mjs/three-way-merge.mjs +0 -318
- package/dist/mjs/working-tree-mirror.mjs +0 -1817
- package/dist/mjs/workspace-automatic-sync-policy.mjs +0 -48
- package/dist/mjs/workspace-branch-incarnation-policy.mjs +0 -13
- package/dist/mjs/workspace-command-sync-policy.mjs +0 -84
- package/dist/mjs/workspace-command-targets.mjs +0 -37
- package/dist/mjs/workspace-filesystem-executor-thread.mjs +0 -38
- package/dist/mjs/workspace-filesystem-executor.mjs +0 -287
- package/dist/mjs/workspace-filesystem-job-types.mjs +0 -116
- package/dist/mjs/workspace-filesystem-jobs.mjs +0 -55
- package/dist/mjs/workspace-git-sync.mjs +0 -3553
- package/dist/mjs/workspace-hydration-ledger.mjs +0 -58
- package/dist/mjs/workspace-hydration-merge.mjs +0 -414
- package/dist/mjs/workspace-hydration-recovery-state.mjs +0 -29
- package/dist/mjs/workspace-incident-state.mjs +0 -12
- package/dist/mjs/workspace-merge-projection.mjs +0 -594
- package/dist/mjs/workspace-mount-boundary.mjs +0 -37
- package/dist/mjs/workspace-mount-hold-fence.mjs +0 -95
- package/dist/mjs/workspace-mutation-gate.mjs +0 -103
- package/dist/mjs/workspace-path-move.mjs +0 -160
- package/dist/mjs/workspace-preserve-only-policy.mjs +0 -11
- package/dist/mjs/workspace-project-config-policy.mjs +0 -27
- package/dist/mjs/workspace-projection-ledger.mjs +0 -57
- package/dist/mjs/workspace-publication-evidence.mjs +0 -21
- package/dist/mjs/workspace-sync-coalescer.mjs +0 -34
- package/dist/types/atomic-rename.d.ts +0 -78
- package/dist/types/browser-upload.d.ts +0 -7
- package/dist/types/command-launcher.d.ts +0 -104
- package/dist/types/control-command-policy.d.ts +0 -53
- package/dist/types/git-blob-hash.d.ts +0 -10
- package/dist/types/git-identity.d.ts +0 -9
- package/dist/types/git-process-environment.d.ts +0 -9
- package/dist/types/inline-output.d.ts +0 -3
- package/dist/types/managed-paths.d.ts +0 -19
- package/dist/types/plan-lint.d.ts +0 -16
- package/dist/types/plan-parser.d.ts +0 -38
- package/dist/types/plan-paths.d.ts +0 -36
- package/dist/types/port-forward-client.d.ts +0 -8
- package/dist/types/project-checkout-garbage.d.ts +0 -86
- package/dist/types/project-mirror-fetch-policy.d.ts +0 -84
- package/dist/types/project-mirror-refs-token.d.ts +0 -53
- package/dist/types/project-workspace-state.d.ts +0 -188
- package/dist/types/project-worktrees.d.ts +0 -397
- package/dist/types/pty-output-coalescer.d.ts +0 -20
- package/dist/types/recovery-journal-protocol.d.ts +0 -35
- package/dist/types/recovery-journal-runtime.d.ts +0 -12
- package/dist/types/recovery-journal-stall-fixture.d.ts +0 -1
- package/dist/types/recovery-journal-thread.d.ts +0 -1
- package/dist/types/recovery-journal.d.ts +0 -248
- package/dist/types/recovery-output-window.d.ts +0 -19
- package/dist/types/recovery-protocol.d.ts +0 -59
- package/dist/types/recovery-store.d.ts +0 -52
- package/dist/types/registry-auth.d.ts +0 -47
- package/dist/types/repository-transition-policy.d.ts +0 -26
- package/dist/types/runtime-version.d.ts +0 -1
- package/dist/types/session-file-mutations.d.ts +0 -22
- package/dist/types/supervisor-daemonized-fixture.d.ts +0 -1
- package/dist/types/supervisor-launcher-cleanup-fixture.d.ts +0 -1
- package/dist/types/supervisor-signal-fixture.d.ts +0 -1
- package/dist/types/supervisor.d.ts +0 -23
- package/dist/types/three-way-merge.d.ts +0 -77
- package/dist/types/working-tree-mirror.d.ts +0 -424
- package/dist/types/workspace-automatic-sync-policy.d.ts +0 -57
- package/dist/types/workspace-branch-incarnation-policy.d.ts +0 -14
- package/dist/types/workspace-command-sync-policy.d.ts +0 -57
- package/dist/types/workspace-command-targets.d.ts +0 -37
- package/dist/types/workspace-filesystem-executor-thread.d.ts +0 -1
- package/dist/types/workspace-filesystem-executor.d.ts +0 -123
- package/dist/types/workspace-filesystem-job-types.d.ts +0 -302
- package/dist/types/workspace-filesystem-jobs.d.ts +0 -7
- package/dist/types/workspace-git-sync.d.ts +0 -514
- package/dist/types/workspace-hydration-ledger.d.ts +0 -76
- package/dist/types/workspace-hydration-merge.d.ts +0 -103
- package/dist/types/workspace-hydration-recovery-state.d.ts +0 -10
- package/dist/types/workspace-incident-state.d.ts +0 -19
- package/dist/types/workspace-merge-projection.d.ts +0 -69
- package/dist/types/workspace-mount-boundary.d.ts +0 -10
- package/dist/types/workspace-mount-hold-fence.d.ts +0 -42
- package/dist/types/workspace-mutation-gate.d.ts +0 -42
- package/dist/types/workspace-path-move.d.ts +0 -21
- package/dist/types/workspace-preserve-only-policy.d.ts +0 -15
- package/dist/types/workspace-project-config-policy.d.ts +0 -48
- package/dist/types/workspace-projection-ledger.d.ts +0 -59
- package/dist/types/workspace-publication-evidence.d.ts +0 -17
- package/dist/types/workspace-sync-coalescer.d.ts +0 -28
- /package/dist/types/{credential-authority-lock-fixture.d.ts → runtime/releases/main.d.ts} +0 -0
|
@@ -1,95 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
class WorkspaceMutationGate {
|
|
2
|
-
activeMutations = 0;
|
|
3
|
-
syncActive = false;
|
|
4
|
-
waiters = [];
|
|
5
|
-
acquireMutation() {
|
|
6
|
-
return new Promise((resolve) => {
|
|
7
|
-
this.waiters.push({ kind: "mutation", resolve });
|
|
8
|
-
this.drain();
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
async runMutation(operation) {
|
|
12
|
-
const release = await this.acquireMutation();
|
|
13
|
-
try {
|
|
14
|
-
return await operation();
|
|
15
|
-
} finally {
|
|
16
|
-
release();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
async runSync(operation) {
|
|
20
|
-
const release = await new Promise((resolve) => {
|
|
21
|
-
this.waiters.push({ kind: "sync", resolve });
|
|
22
|
-
this.drain();
|
|
23
|
-
});
|
|
24
|
-
try {
|
|
25
|
-
return await operation();
|
|
26
|
-
} finally {
|
|
27
|
-
release();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/** A worktree create/delete lease; see the branch lane in the class comment. */
|
|
31
|
-
acquireBranchMutation() {
|
|
32
|
-
return new Promise((resolve) => {
|
|
33
|
-
this.waiters.push({ kind: "branch", resolve });
|
|
34
|
-
this.drain();
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
async runBranchMutation(operation) {
|
|
38
|
-
const release = await this.acquireBranchMutation();
|
|
39
|
-
try {
|
|
40
|
-
return await operation();
|
|
41
|
-
} finally {
|
|
42
|
-
release();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Operations a branch mutation requested now would wait for: the running
|
|
47
|
-
* sync, if any, plus branch mutations queued behind it. Zero means it runs
|
|
48
|
-
* at once. Ordinary waiters are not counted because the branch lane admits
|
|
49
|
-
* ahead of them.
|
|
50
|
-
*/
|
|
51
|
-
branchMutationQueuePosition() {
|
|
52
|
-
if (!this.syncActive) return 0;
|
|
53
|
-
return 1 + this.waiters.filter((waiter) => waiter.kind === "branch").length;
|
|
54
|
-
}
|
|
55
|
-
drain() {
|
|
56
|
-
if (this.syncActive || this.waiters.length === 0) return;
|
|
57
|
-
for (let index = 0; index < this.waiters.length; ) {
|
|
58
|
-
const waiter = this.waiters[index];
|
|
59
|
-
if (waiter?.kind !== "branch") {
|
|
60
|
-
index += 1;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
this.waiters.splice(index, 1);
|
|
64
|
-
this.activeMutations += 1;
|
|
65
|
-
waiter.resolve(this.releaseMutation());
|
|
66
|
-
}
|
|
67
|
-
const first = this.waiters[0];
|
|
68
|
-
if (first?.kind === "sync") {
|
|
69
|
-
if (this.activeMutations > 0) return;
|
|
70
|
-
this.waiters.shift();
|
|
71
|
-
this.syncActive = true;
|
|
72
|
-
first.resolve(this.releaseSync());
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
while (this.waiters[0]?.kind === "mutation") {
|
|
76
|
-
const waiter = this.waiters.shift();
|
|
77
|
-
if (!waiter || waiter.kind !== "mutation") break;
|
|
78
|
-
this.activeMutations += 1;
|
|
79
|
-
waiter.resolve(this.releaseMutation());
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
releaseMutation() {
|
|
83
|
-
let released = false;
|
|
84
|
-
return () => {
|
|
85
|
-
if (released) return;
|
|
86
|
-
released = true;
|
|
87
|
-
this.activeMutations -= 1;
|
|
88
|
-
this.drain();
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
releaseSync() {
|
|
92
|
-
let released = false;
|
|
93
|
-
return () => {
|
|
94
|
-
if (released) return;
|
|
95
|
-
released = true;
|
|
96
|
-
this.syncActive = false;
|
|
97
|
-
this.drain();
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
export {
|
|
102
|
-
WorkspaceMutationGate
|
|
103
|
-
};
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
|
-
import { projectWorktreeOperationInProgress } from "./project-worktrees.mjs";
|
|
5
|
-
import { validateManagedBranchName } from "./managed-paths.mjs";
|
|
6
|
-
import { assertWorkingTreeHasNoPortableGitMetadataAliases, mirrorWorkingTree } from "./working-tree-mirror.mjs";
|
|
7
|
-
import { assertManagedDirectoryPath } from "./workspace-mount-boundary.mjs";
|
|
8
|
-
function checkoutPathMovePublicationComplete(requiredMountIds, activeMountIds) {
|
|
9
|
-
return requiredMountIds.length > 0 && requiredMountIds.every((mountId) => activeMountIds.has(mountId));
|
|
10
|
-
}
|
|
11
|
-
function pathIsInside(root, candidate) {
|
|
12
|
-
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
13
|
-
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
14
|
-
}
|
|
15
|
-
function fsyncTree(targetPath) {
|
|
16
|
-
const stat = fs.lstatSync(targetPath);
|
|
17
|
-
if (stat.isSymbolicLink()) return;
|
|
18
|
-
if (stat.isDirectory()) {
|
|
19
|
-
for (const entry of fs.readdirSync(targetPath).sort()) fsyncTree(path.join(targetPath, entry));
|
|
20
|
-
}
|
|
21
|
-
if (!stat.isDirectory() && !stat.isFile()) return;
|
|
22
|
-
const descriptor = fs.openSync(targetPath, "r");
|
|
23
|
-
try {
|
|
24
|
-
fs.fsyncSync(descriptor);
|
|
25
|
-
} finally {
|
|
26
|
-
fs.closeSync(descriptor);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function fsyncDirectory(directoryPath) {
|
|
30
|
-
const descriptor = fs.openSync(directoryPath, "r");
|
|
31
|
-
try {
|
|
32
|
-
fs.fsyncSync(descriptor);
|
|
33
|
-
} finally {
|
|
34
|
-
fs.closeSync(descriptor);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function fsyncAncestorsToRoot(targetPath, durabilityRoot) {
|
|
38
|
-
const root = path.resolve(durabilityRoot);
|
|
39
|
-
let current = path.dirname(path.resolve(targetPath));
|
|
40
|
-
while (true) {
|
|
41
|
-
if (fs.existsSync(current)) {
|
|
42
|
-
const status = fs.lstatSync(current);
|
|
43
|
-
if (!status.isDirectory() || status.isSymbolicLink()) {
|
|
44
|
-
throw new Error(`Project checkout path move durability boundary is not a regular directory: ${current}`);
|
|
45
|
-
}
|
|
46
|
-
fsyncDirectory(current);
|
|
47
|
-
}
|
|
48
|
-
if (current === root) return;
|
|
49
|
-
const parent = path.dirname(current);
|
|
50
|
-
if (parent === current) throw new Error(`Project checkout path move escapes durability root ${root}`);
|
|
51
|
-
current = parent;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function stableTree(targetPath, durabilityRoot) {
|
|
55
|
-
fsyncTree(targetPath);
|
|
56
|
-
fsyncAncestorsToRoot(targetPath, durabilityRoot);
|
|
57
|
-
}
|
|
58
|
-
function directoryEntryExists(targetPath) {
|
|
59
|
-
try {
|
|
60
|
-
fs.lstatSync(targetPath);
|
|
61
|
-
return true;
|
|
62
|
-
} catch (error) {
|
|
63
|
-
if (error.code === "ENOENT") return false;
|
|
64
|
-
throw error;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function retiredCheckoutPath(projectsDurabilityRoot, oldProjectRoot) {
|
|
68
|
-
const storageRoot = path.join(projectsDurabilityRoot, ".r5d-retired-checkouts");
|
|
69
|
-
const digest = createHash("sha256").update(oldProjectRoot).digest("hex");
|
|
70
|
-
return { storageRoot, retiredRoot: path.join(storageRoot, digest) };
|
|
71
|
-
}
|
|
72
|
-
function removeRetiredCheckout(retiredRoot, storageRoot) {
|
|
73
|
-
if (!directoryEntryExists(retiredRoot)) return;
|
|
74
|
-
fs.rmSync(retiredRoot, { recursive: true, force: true });
|
|
75
|
-
fsyncDirectory(storageRoot);
|
|
76
|
-
}
|
|
77
|
-
function preserveProjectCheckoutPathMove(input) {
|
|
78
|
-
const oldProjectRoot = path.resolve(input.oldProjectRoot);
|
|
79
|
-
const newProjectRoot = path.resolve(input.newProjectRoot);
|
|
80
|
-
const projectsDurabilityRoot = path.resolve(input.projectsDurabilityRoot);
|
|
81
|
-
const outerWorkspaceRoot = path.resolve(input.outerWorkspaceRoot);
|
|
82
|
-
if (oldProjectRoot === newProjectRoot || pathIsInside(oldProjectRoot, newProjectRoot) || pathIsInside(newProjectRoot, oldProjectRoot)) {
|
|
83
|
-
throw new Error("Project checkout path move roots overlap");
|
|
84
|
-
}
|
|
85
|
-
const branchNames = input.branches.map(({ branchName }) => branchName);
|
|
86
|
-
if (branchNames.length === 0 || new Set(branchNames).size !== branchNames.length) {
|
|
87
|
-
throw new Error("Project checkout path move requires unique branches");
|
|
88
|
-
}
|
|
89
|
-
assertManagedDirectoryPath({
|
|
90
|
-
trustedRoot: projectsDurabilityRoot,
|
|
91
|
-
candidate: oldProjectRoot,
|
|
92
|
-
label: "Old project checkout root"
|
|
93
|
-
});
|
|
94
|
-
assertManagedDirectoryPath({
|
|
95
|
-
trustedRoot: projectsDurabilityRoot,
|
|
96
|
-
candidate: newProjectRoot,
|
|
97
|
-
label: "New project checkout root"
|
|
98
|
-
});
|
|
99
|
-
for (const branchName of branchNames) validateManagedBranchName(branchName);
|
|
100
|
-
if (directoryEntryExists(oldProjectRoot)) {
|
|
101
|
-
assertWorkingTreeHasNoPortableGitMetadataAliases(oldProjectRoot);
|
|
102
|
-
}
|
|
103
|
-
const { storageRoot: retirementStorageRoot, retiredRoot } = retiredCheckoutPath(projectsDurabilityRoot, oldProjectRoot);
|
|
104
|
-
assertManagedDirectoryPath({
|
|
105
|
-
trustedRoot: projectsDurabilityRoot,
|
|
106
|
-
candidate: retirementStorageRoot,
|
|
107
|
-
label: "Retired project checkout storage"
|
|
108
|
-
});
|
|
109
|
-
fs.mkdirSync(retirementStorageRoot, { recursive: true, mode: 448 });
|
|
110
|
-
assertManagedDirectoryPath({
|
|
111
|
-
trustedRoot: projectsDurabilityRoot,
|
|
112
|
-
candidate: retirementStorageRoot,
|
|
113
|
-
label: "Retired project checkout storage"
|
|
114
|
-
});
|
|
115
|
-
fsyncDirectory(retirementStorageRoot);
|
|
116
|
-
fsyncDirectory(projectsDurabilityRoot);
|
|
117
|
-
assertManagedDirectoryPath({
|
|
118
|
-
trustedRoot: projectsDurabilityRoot,
|
|
119
|
-
candidate: retiredRoot,
|
|
120
|
-
label: "Retired project checkout"
|
|
121
|
-
});
|
|
122
|
-
removeRetiredCheckout(retiredRoot, retirementStorageRoot);
|
|
123
|
-
if (!directoryEntryExists(oldProjectRoot)) return { preservedBranchNames: [] };
|
|
124
|
-
const preservedBranchNames = [];
|
|
125
|
-
for (const branch of input.branches) {
|
|
126
|
-
validateManagedBranchName(branch.branchName);
|
|
127
|
-
const oldBranchPath = path.join(oldProjectRoot, ...branch.branchName.split("/"));
|
|
128
|
-
if (!fs.existsSync(oldBranchPath)) continue;
|
|
129
|
-
const newBranchPath = path.join(newProjectRoot, ...branch.branchName.split("/"));
|
|
130
|
-
const outerBranchPath = path.resolve(outerWorkspaceRoot, ...branch.outerWorkspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
131
|
-
if (!pathIsInside(outerWorkspaceRoot, outerBranchPath)) {
|
|
132
|
-
throw new Error(`Project checkout path move escapes the outer workspace: ${branch.outerWorkspaceRelativePath}`);
|
|
133
|
-
}
|
|
134
|
-
assertManagedDirectoryPath({ trustedRoot: projectsDurabilityRoot, candidate: oldBranchPath, label: "Old project checkout branch" });
|
|
135
|
-
assertManagedDirectoryPath({ trustedRoot: projectsDurabilityRoot, candidate: newBranchPath, label: "New project checkout branch" });
|
|
136
|
-
assertManagedDirectoryPath({ trustedRoot: outerWorkspaceRoot, candidate: outerBranchPath, label: "Outer workspace branch" });
|
|
137
|
-
mirrorWorkingTree({ sourceRoot: oldBranchPath, targetRoot: outerBranchPath, sourceMode: "git", deletionMode: "all" });
|
|
138
|
-
mirrorWorkingTree({ sourceRoot: oldBranchPath, targetRoot: newBranchPath, sourceMode: "all", deletionMode: "all" });
|
|
139
|
-
assertManagedDirectoryPath({ trustedRoot: projectsDurabilityRoot, candidate: newBranchPath, label: "New project checkout branch" });
|
|
140
|
-
assertManagedDirectoryPath({ trustedRoot: outerWorkspaceRoot, candidate: outerBranchPath, label: "Outer workspace branch" });
|
|
141
|
-
stableTree(outerBranchPath, outerWorkspaceRoot);
|
|
142
|
-
stableTree(newBranchPath, projectsDurabilityRoot);
|
|
143
|
-
preservedBranchNames.push(branch.branchName);
|
|
144
|
-
}
|
|
145
|
-
for (const branchName of branchNames) {
|
|
146
|
-
const checkoutPath = path.join(oldProjectRoot, ...branchName.split("/"));
|
|
147
|
-
if (fs.existsSync(checkoutPath) && projectWorktreeOperationInProgress(checkoutPath)) {
|
|
148
|
-
throw new Error(`Project branch ${branchName} has an in-progress Git operation`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
fs.renameSync(oldProjectRoot, retiredRoot);
|
|
152
|
-
fsyncAncestorsToRoot(oldProjectRoot, projectsDurabilityRoot);
|
|
153
|
-
fsyncDirectory(retirementStorageRoot);
|
|
154
|
-
removeRetiredCheckout(retiredRoot, retirementStorageRoot);
|
|
155
|
-
return { preservedBranchNames: preservedBranchNames.sort() };
|
|
156
|
-
}
|
|
157
|
-
export {
|
|
158
|
-
checkoutPathMovePublicationComplete,
|
|
159
|
-
preserveProjectCheckoutPathMove
|
|
160
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
function creatorLocalProjectBranchIsAuthorized(input) {
|
|
2
|
-
return Boolean(input.pendingBranchId && (!input.preserveOnlyBranchId || input.pendingBranchId === input.preserveOnlyBranchId));
|
|
3
|
-
}
|
|
4
|
-
function workerProjectBranchDisposition(input) {
|
|
5
|
-
if (input.activeConfigured) return "active";
|
|
6
|
-
return input.locallyPendingCreated ? "creator_local" : "preserve_only";
|
|
7
|
-
}
|
|
8
|
-
export {
|
|
9
|
-
creatorLocalProjectBranchIsAuthorized,
|
|
10
|
-
workerProjectBranchDisposition
|
|
11
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
function deferredProjectConfigurationPendingBranches(projects) {
|
|
2
|
-
return projects.filter((project) => !project.executionDisabled).flatMap((project) => project.branches.map(({ branchName }) => ({ projectId: project.projectId, branchName }))).sort((left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName));
|
|
3
|
-
}
|
|
4
|
-
function busyProjectConfigurationChanges(input) {
|
|
5
|
-
const currentById = new Map(input.currentProjects.map((project) => [project.projectId, project]));
|
|
6
|
-
const incomingById = new Map(input.incomingProjects.map((project) => [project.projectId, project]));
|
|
7
|
-
const targets = [...input.activeTargets];
|
|
8
|
-
const busyProjectIds = targets.some((target) => target.type === "workspace" && target.rootProfile === "visible_projects") ? /* @__PURE__ */ new Set([...currentById.keys(), ...incomingById.keys()]) : new Set(targets.flatMap((target) => target.type === "project" ? [target.projectId] : []));
|
|
9
|
-
return [...busyProjectIds].flatMap((projectId) => {
|
|
10
|
-
const current = currentById.get(projectId);
|
|
11
|
-
const incoming = incomingById.get(projectId);
|
|
12
|
-
if (!current && !incoming) return [];
|
|
13
|
-
if (!current) return [{ projectId, reason: "added" }];
|
|
14
|
-
if (!incoming) return [{ projectId, reason: "removed" }];
|
|
15
|
-
if (!input.currentProjectReady(current)) return [{ projectId, reason: "initializing" }];
|
|
16
|
-
if (input.currentFingerprint(current) !== input.incomingFingerprint(incoming)) return [{ projectId, reason: "changed" }];
|
|
17
|
-
return [];
|
|
18
|
-
}).sort((left, right) => left.projectId.localeCompare(right.projectId));
|
|
19
|
-
}
|
|
20
|
-
function describeBusyProjectConfigurationChanges(changes) {
|
|
21
|
-
return changes.map(({ projectId, reason }) => `${projectId} (${reason})`).join(", ");
|
|
22
|
-
}
|
|
23
|
-
export {
|
|
24
|
-
busyProjectConfigurationChanges,
|
|
25
|
-
deferredProjectConfigurationPendingBranches,
|
|
26
|
-
describeBusyProjectConfigurationChanges
|
|
27
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const RETAINED_OUTCOMES = /* @__PURE__ */ new Set(["no_change", "updated", "published", "large_diff_blocked"]);
|
|
2
|
-
class WorkspaceProjectionLedger {
|
|
3
|
-
projectedTokens = /* @__PURE__ */ new Map();
|
|
4
|
-
lastCycle = null;
|
|
5
|
-
static recordKey(mount) {
|
|
6
|
-
return `${mount.hydrationIncarnationKey}\0${mount.id}`;
|
|
7
|
-
}
|
|
8
|
-
get lastCycleResult() {
|
|
9
|
-
return this.lastCycle;
|
|
10
|
-
}
|
|
11
|
-
lastProjectedToken(mount) {
|
|
12
|
-
return this.projectedTokens.get(WorkspaceProjectionLedger.recordKey(mount));
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Record a completed cycle. `projectedMutationTokens` names the token each
|
|
16
|
-
* active mount carried when the cycle projected it; a mount absent from it
|
|
17
|
-
* keeps its earlier record. Any outcome that leaves the outer clone without
|
|
18
|
-
* a trustworthy projection clears the ledger.
|
|
19
|
-
*/
|
|
20
|
-
recordCycle(input) {
|
|
21
|
-
if (!RETAINED_OUTCOMES.has(input.result.outcome)) {
|
|
22
|
-
this.reset();
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const mountsById = new Map(input.mounts.map((mount) => [mount.id, mount]));
|
|
26
|
-
for (const [mountId, token] of Object.entries(input.projectedMutationTokens ?? {})) {
|
|
27
|
-
const mount = mountsById.get(mountId);
|
|
28
|
-
if (mount) this.projectedTokens.set(WorkspaceProjectionLedger.recordKey(mount), token);
|
|
29
|
-
}
|
|
30
|
-
this.lastCycle = input.result;
|
|
31
|
-
}
|
|
32
|
-
reset() {
|
|
33
|
-
this.projectedTokens.clear();
|
|
34
|
-
this.lastCycle = null;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* The previous cycle's result when every request targets a configured
|
|
38
|
-
* mount and no mount's token moved since its recorded projection. A request
|
|
39
|
-
* without a target (a workspace-rooted command may touch any checkout), a
|
|
40
|
-
* mount without a token (a pending deletion), or pending deletion work all
|
|
41
|
-
* require a real cycle.
|
|
42
|
-
*/
|
|
43
|
-
answerFromLastCycle(input) {
|
|
44
|
-
if (!this.lastCycle || input.pendingDeletions || input.targetMountIds.length === 0) return null;
|
|
45
|
-
const mountIds = new Set(input.mounts.map(({ id }) => id));
|
|
46
|
-
if (!input.targetMountIds.every((mountId) => mountId !== null && mountIds.has(mountId))) return null;
|
|
47
|
-
for (const mount of input.mounts) {
|
|
48
|
-
if (!mount.mutationToken) return null;
|
|
49
|
-
const recorded = this.projectedTokens.get(WorkspaceProjectionLedger.recordKey(mount));
|
|
50
|
-
if (recorded === void 0 || recorded !== mount.mutationToken()) return null;
|
|
51
|
-
}
|
|
52
|
-
return this.lastCycle;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
export {
|
|
56
|
-
WorkspaceProjectionLedger
|
|
57
|
-
};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
function projectBranchMountId(projectId, branchName) {
|
|
2
|
-
return `project:${projectId}:${encodeURIComponent(branchName)}`;
|
|
3
|
-
}
|
|
4
|
-
function activeProjectBranchNames(projectId, branchNames, activeMountIds) {
|
|
5
|
-
const active = new Set(activeMountIds);
|
|
6
|
-
return [...branchNames].filter((branchName) => active.has(projectBranchMountId(projectId, branchName))).sort((left, right) => left.localeCompare(right));
|
|
7
|
-
}
|
|
8
|
-
function activeProjectBranchPublicationEvidence(projects, activeMountIds) {
|
|
9
|
-
const active = new Set(activeMountIds);
|
|
10
|
-
return projects.flatMap(
|
|
11
|
-
(project) => project.executionDisabled || project.mirrorWritesDisabled ? [] : project.branches.flatMap(
|
|
12
|
-
(branch) => active.has(projectBranchMountId(project.projectId, branch.branchName)) ? [{ branchId: branch.branchId, projectId: project.projectId, branchName: branch.branchName }] : []
|
|
13
|
-
)
|
|
14
|
-
).sort(
|
|
15
|
-
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName) || left.branchId.localeCompare(right.branchId)
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
export {
|
|
19
|
-
activeProjectBranchNames,
|
|
20
|
-
activeProjectBranchPublicationEvidence
|
|
21
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
class WorkspaceSyncCoalescer {
|
|
2
|
-
constructor(options) {
|
|
3
|
-
this.options = options;
|
|
4
|
-
}
|
|
5
|
-
options;
|
|
6
|
-
pendingBatch = null;
|
|
7
|
-
batchesStarted = 0;
|
|
8
|
-
/** Requests attached to the batch that has not started yet. */
|
|
9
|
-
get queuedRequestCount() {
|
|
10
|
-
return this.pendingBatch?.requests.length ?? 0;
|
|
11
|
-
}
|
|
12
|
-
/** Batches whose body has been entered, whether or not they ran a cycle. */
|
|
13
|
-
get startedBatchCount() {
|
|
14
|
-
return this.batchesStarted;
|
|
15
|
-
}
|
|
16
|
-
request(request) {
|
|
17
|
-
const queued = this.pendingBatch;
|
|
18
|
-
if (queued) {
|
|
19
|
-
queued.requests.push(request);
|
|
20
|
-
return queued.settlement;
|
|
21
|
-
}
|
|
22
|
-
const batch = { requests: [request], settlement: Promise.resolve() };
|
|
23
|
-
this.pendingBatch = batch;
|
|
24
|
-
batch.settlement = this.options.runExclusive(async () => {
|
|
25
|
-
if (this.pendingBatch === batch) this.pendingBatch = null;
|
|
26
|
-
this.batchesStarted += 1;
|
|
27
|
-
return await this.options.runBatch(batch.requests);
|
|
28
|
-
});
|
|
29
|
-
return batch.settlement;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
export {
|
|
33
|
-
WorkspaceSyncCoalescer
|
|
34
|
-
};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Atomic directory-entry primitives the working-tree mirror needs beyond
|
|
3
|
-
* `rename(2)`:
|
|
4
|
-
*
|
|
5
|
-
* - exchange: swap two existing entries in one step
|
|
6
|
-
* (`renameat2(RENAME_EXCHANGE)` on Linux, `renamex_np(RENAME_SWAP)` on
|
|
7
|
-
* macOS). After the swap the first path names what the second held and
|
|
8
|
-
* vice versa, so a replacement can inspect exactly the entry it displaced
|
|
9
|
-
* instead of guessing from a stat taken before the rename.
|
|
10
|
-
* - no-replace rename: move an entry only while nothing is at the
|
|
11
|
-
* destination (`RENAME_NOREPLACE` / `RENAME_EXCL`, or `link(2)` plus
|
|
12
|
-
* `unlink(2)` for a regular file where the flag is unavailable).
|
|
13
|
-
*
|
|
14
|
-
* Both are reached through `bun:ffi` against the C library already loaded in
|
|
15
|
-
* the process; nothing is spawned and nothing is compiled. Availability is
|
|
16
|
-
* never assumed: a filesystem can reject a flag (`EINVAL` on NFS), a runtime
|
|
17
|
-
* can lack FFI, and some filesystems accept the exchange flag and then
|
|
18
|
-
* perform a plain replacing rename (observed on a Docker Desktop virtiofs
|
|
19
|
-
* bind mount), which would lose the displaced entry. `atomicRenamePrimitivesFor`
|
|
20
|
-
* therefore probes each filesystem once with two scratch files and hands out
|
|
21
|
-
* primitives whose flags were seen to work there; the caller falls back to a
|
|
22
|
-
* protocol built from plain POSIX calls for anything that was not.
|
|
23
|
-
*/
|
|
24
|
-
export type AtomicExchangeBackend = "renameat2" | "renamex_np" | "syscall" | "unavailable";
|
|
25
|
-
export type AtomicNoReplaceBackend = "renameat2" | "renamex_np" | "syscall" | "link";
|
|
26
|
-
export type AtomicRenameSupport = {
|
|
27
|
-
platform: NodeJS.Platform;
|
|
28
|
-
exchange: AtomicExchangeBackend;
|
|
29
|
-
noReplace: AtomicNoReplaceBackend;
|
|
30
|
-
/** Why a native primitive is not used, when it is not. */
|
|
31
|
-
detail?: string;
|
|
32
|
-
};
|
|
33
|
-
/** Failure of one primitive, carrying the POSIX error name the way `fs` errors do. */
|
|
34
|
-
export declare class AtomicRenameError extends Error {
|
|
35
|
-
readonly syscall: string;
|
|
36
|
-
readonly code: string;
|
|
37
|
-
readonly errno: number;
|
|
38
|
-
readonly path: string;
|
|
39
|
-
readonly dest: string;
|
|
40
|
-
constructor(syscall: string, code: string, errno: number, path: string, dest: string);
|
|
41
|
-
}
|
|
42
|
-
export declare function isAtomicRenameUnsupported(error: unknown): boolean;
|
|
43
|
-
/** What this process can do natively, before any filesystem probe. */
|
|
44
|
-
export declare function atomicRenameSupport(): AtomicRenameSupport;
|
|
45
|
-
/**
|
|
46
|
-
* Test seam: pretend a primitive is unavailable so the portable fallback
|
|
47
|
-
* protocols run on a platform that has the native ones. Pass null to clear.
|
|
48
|
-
*/
|
|
49
|
-
export declare function overrideAtomicRenameSupportForTests(value: Partial<Pick<AtomicRenameSupport, "exchange" | "noReplace">> | null): void;
|
|
50
|
-
/** Test seam: forget probe results so the next `atomicRenamePrimitivesFor` probes again. */
|
|
51
|
-
export declare function resetAtomicRenameProbesForTests(): void;
|
|
52
|
-
/**
|
|
53
|
-
* Atomically exchange the directory entries `a` and `b` (any kinds, same
|
|
54
|
-
* filesystem) with the process-level primitive, unprobed. Throws
|
|
55
|
-
* AtomicRenameError `ENOENT` when either is missing and an unsupported code
|
|
56
|
-
* (see `isAtomicRenameUnsupported`) when the kernel or filesystem cannot
|
|
57
|
-
* exchange. Prefer `atomicRenamePrimitivesFor` for anything that matters.
|
|
58
|
-
*/
|
|
59
|
-
export declare function exchangePaths(a: string, b: string): void;
|
|
60
|
-
/** Move `from` to `to` only if nothing is at `to` (`EEXIST` otherwise), with the process-level primitive, unprobed. */
|
|
61
|
-
export declare function renameNoReplace(from: string, to: string): void;
|
|
62
|
-
/** Primitives bound to what a probe of one filesystem showed to work there. */
|
|
63
|
-
export type AtomicRenamePrimitives = {
|
|
64
|
-
support: AtomicRenameSupport;
|
|
65
|
-
exchange: (a: string, b: string) => void;
|
|
66
|
-
renameNoReplace: (from: string, to: string) => void;
|
|
67
|
-
};
|
|
68
|
-
/**
|
|
69
|
-
* Probe the filesystem holding `directory` (which must be writable) once per
|
|
70
|
-
* device: the exchange flag must actually swap two scratch files (inode
|
|
71
|
-
* numbers exchanged, both still present), and the native no-replace flag
|
|
72
|
-
* must actually refuse an existing destination. A flag that fails, or that
|
|
73
|
-
* succeeds without doing what it promises, is reported as unavailable and
|
|
74
|
-
* the returned primitives use the fallbacks instead.
|
|
75
|
-
*/
|
|
76
|
-
export declare function probeAtomicRenameSupport(directory: string): AtomicRenameSupport;
|
|
77
|
-
/** Primitives for the filesystem holding `directory`, probed on first use per device (see `probeAtomicRenameSupport`). */
|
|
78
|
-
export declare function atomicRenamePrimitivesFor(directory: string): AtomicRenamePrimitives;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type BrowserUploadChunk, type BrowserUploadReadInput } from "@ricsam/r5d-api/browser-upload";
|
|
2
|
-
/** Unlike code-view paths, uploads may read any regular file accessible to this worker. */
|
|
3
|
-
export declare function readBrowserUploadChunk(input: BrowserUploadReadInput, context: {
|
|
4
|
-
cwd: string;
|
|
5
|
-
rootDir: string;
|
|
6
|
-
sessionId: string;
|
|
7
|
-
}): Promise<BrowserUploadChunk>;
|