@openclaw/fs-safe 0.5.6 → 0.7.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/CHANGELOG.md +89 -0
- package/README.md +23 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-crc32.d.ts +2 -0
- package/dist/archive-crc32.d.ts.map +1 -0
- package/dist/archive-crc32.js +14 -0
- package/dist/archive-deadline.d.ts +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +44 -8
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +1 -0
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +3 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +26 -16
- package/dist/archive-kind.js +2 -2
- package/dist/archive-limits.d.ts +11 -3
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +24 -0
- package/dist/archive-native.d.ts +3 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +31 -9
- package/dist/archive-policy.d.ts +2 -0
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +9 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +88 -41
- package/dist/archive-staging.d.ts +3 -0
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +91 -43
- package/dist/archive-tar-admission.d.ts +7 -0
- package/dist/archive-tar-admission.d.ts.map +1 -0
- package/dist/archive-tar-admission.js +43 -0
- package/dist/archive-tar-gnu.d.ts +2 -0
- package/dist/archive-tar-gnu.d.ts.map +1 -0
- package/dist/archive-tar-gnu.js +20 -0
- package/dist/archive-tar-header.d.ts +8 -0
- package/dist/archive-tar-header.d.ts.map +1 -0
- package/dist/archive-tar-header.js +47 -0
- package/dist/archive-tar-meta.d.ts +29 -1
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +135 -19
- package/dist/archive-tar-pax.d.ts +8 -0
- package/dist/archive-tar-pax.d.ts.map +1 -0
- package/dist/archive-tar-pax.js +100 -0
- package/dist/archive-tar-runtime.d.ts +4 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +3 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -2
- package/dist/archive-zip-admission.d.ts +7 -0
- package/dist/archive-zip-admission.d.ts.map +1 -0
- package/dist/archive-zip-admission.js +60 -0
- package/dist/archive-zip-count.d.ts +2 -0
- package/dist/archive-zip-count.d.ts.map +1 -0
- package/dist/archive-zip-count.js +140 -0
- package/dist/archive-zip-directory.d.ts +8 -0
- package/dist/archive-zip-directory.d.ts.map +1 -0
- package/dist/archive-zip-directory.js +224 -0
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +1 -14
- package/dist/archive-zip-names.d.ts +12 -0
- package/dist/archive-zip-names.d.ts.map +1 -0
- package/dist/archive-zip-names.js +99 -0
- package/dist/archive-zip-preflight.d.ts +1 -1
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +8 -145
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +63 -42
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +2 -4
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +3 -10
- package/dist/byte-budget.d.ts +5 -0
- package/dist/byte-budget.d.ts.map +1 -0
- package/dist/byte-budget.js +9 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +25 -19
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +26 -9
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +9 -5
- package/dist/file-store-limit.d.ts +2 -0
- package/dist/file-store-limit.d.ts.map +1 -0
- package/dist/file-store-limit.js +8 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +38 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +26 -26
- package/dist/json-durable-queue-directory.d.ts +2 -0
- package/dist/json-durable-queue-directory.d.ts.map +1 -0
- package/dist/json-durable-queue-directory.js +20 -0
- package/dist/json-durable-queue-ownership.d.ts +14 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -0
- package/dist/json-durable-queue-ownership.js +168 -0
- package/dist/json-durable-queue-retirement.d.ts +9 -0
- package/dist/json-durable-queue-retirement.d.ts.map +1 -0
- package/dist/json-durable-queue-retirement.js +126 -0
- package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
- package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
- package/dist/json-durable-queue-transfer-lock.js +19 -0
- package/dist/json-durable-queue.d.ts +1 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +90 -57
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +27 -8
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +4 -2
- package/dist/native-binding.d.ts +15 -3
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +4 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +22 -6
- package/dist/native-pinned-write-windows.d.ts +8 -0
- package/dist/native-pinned-write-windows.d.ts.map +1 -0
- package/dist/native-pinned-write-windows.js +92 -0
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +136 -127
- package/dist/native-staged-file.d.ts +24 -0
- package/dist/native-staged-file.d.ts.map +1 -0
- package/dist/native-staged-file.js +337 -0
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +4 -4
- package/dist/opened-realpath.d.ts +2 -0
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +12 -7
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +11 -110
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +4 -2
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -1
- package/dist/permission-exec.d.ts +19 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +57 -11
- package/dist/permissions-public.d.ts +1 -1
- package/dist/permissions-public.d.ts.map +1 -1
- package/dist/permissions-windows.d.ts +3 -0
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +16 -5
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +27 -48
- package/dist/pinned-write.d.ts +6 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +26 -37
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +3 -2
- package/dist/private-temp-workspace.d.ts +3 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +81 -56
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -4
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +6 -4
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +136 -82
- package/dist/replace-file-copy-fallback.d.ts +3 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +21 -36
- package/dist/replace-file-copy-source.d.ts +21 -0
- package/dist/replace-file-copy-source.d.ts.map +1 -0
- package/dist/replace-file-copy-source.js +112 -0
- package/dist/replace-file-descriptor.d.ts +13 -3
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +32 -7
- package/dist/replace-file-rename-policy.d.ts +7 -0
- package/dist/replace-file-rename-policy.d.ts.map +1 -0
- package/dist/replace-file-rename-policy.js +30 -0
- package/dist/replace-file-temp-owner.d.ts +46 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -0
- package/dist/replace-file-temp-owner.js +346 -0
- package/dist/replace-file.d.ts +6 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +72 -58
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +118 -98
- package/dist/root-paths.d.ts +11 -14
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +36 -27
- package/dist/root-write-verification.d.ts +11 -0
- package/dist/root-write-verification.d.ts.map +1 -0
- package/dist/root-write-verification.js +91 -0
- package/dist/secret-file.d.ts +1 -6
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +49 -120
- package/dist/secret-read-async.d.ts +1 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +51 -72
- package/dist/secret-read-policy.d.ts +13 -0
- package/dist/secret-read-policy.d.ts.map +1 -0
- package/dist/secret-read-policy.js +28 -0
- package/dist/secret.d.ts +2 -1
- package/dist/secret.d.ts.map +1 -1
- package/dist/secret.js +2 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +42 -29
- package/dist/sibling-staged-file.d.ts +15 -0
- package/dist/sibling-staged-file.d.ts.map +1 -0
- package/dist/sibling-staged-file.js +148 -0
- package/dist/sibling-temp.d.ts +3 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +30 -74
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +48 -27
- package/dist/sidecar-lock-handle.d.ts +6 -2
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +17 -3
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +29 -0
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +29 -7
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +28 -16
- package/dist/staged-directory.d.ts +16 -0
- package/dist/staged-directory.d.ts.map +1 -0
- package/dist/staged-directory.js +60 -0
- package/dist/staged-file-types.d.ts +56 -0
- package/dist/staged-file-types.d.ts.map +1 -0
- package/dist/staged-file-types.js +1 -0
- package/dist/staged-file.d.ts +10 -0
- package/dist/staged-file.d.ts.map +1 -0
- package/dist/staged-file.js +15 -0
- package/dist/strict-file-identity.d.ts +6 -0
- package/dist/strict-file-identity.d.ts.map +1 -0
- package/dist/strict-file-identity.js +48 -0
- package/dist/suppressed-error.d.ts +6 -0
- package/dist/suppressed-error.d.ts.map +1 -0
- package/dist/suppressed-error.js +15 -0
- package/dist/temp-cleanup.d.ts +2 -0
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +25 -10
- package/dist/temp-workspace-owner.d.ts +23 -0
- package/dist/temp-workspace-owner.d.ts.map +1 -0
- package/dist/temp-workspace-owner.js +320 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +5 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/windows-owner.d.ts +3 -0
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +10 -2
- package/docs/advanced.md +19 -2
- package/docs/archive.md +250 -35
- package/docs/atomic.md +11 -2
- package/docs/config.md +7 -0
- package/docs/contributing.md +45 -7
- package/docs/durability.md +18 -5
- package/docs/errors.md +16 -1
- package/docs/file-store.md +2 -0
- package/docs/index.md +3 -1
- package/docs/install.md +20 -8
- package/docs/json.md +8 -4
- package/docs/migrating-to-0.5.md +7 -7
- package/docs/migrating-to-0.6.md +43 -0
- package/docs/native-helper.md +30 -8
- package/docs/native.md +73 -17
- package/docs/output.md +10 -0
- package/docs/path-scope.md +28 -2
- package/docs/permissions.md +13 -2
- package/docs/public-api.md +6 -3
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +1 -1
- package/docs/regular-file.md +9 -2
- package/docs/root.md +3 -1
- package/docs/secret-file.md +12 -0
- package/docs/secure-file.md +21 -3
- package/docs/security-model.md +33 -1
- package/docs/sidecar-lock.md +15 -1
- package/docs/staged-file.md +178 -0
- package/docs/store.md +13 -0
- package/docs/temp.md +128 -14
- package/docs/testing.md +1 -1
- package/docs/writing.md +19 -0
- package/package.json +16 -9
- package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
- package/dist/native/darwin-x64/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
- package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-policy.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"sidecar-lock-policy.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAQvE,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAkBpF;AAED,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAQjG;AASD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGnF;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAGT;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,CAQnB"}
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
+
function assertFiniteNonNegative(value, label) {
|
|
3
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
4
|
+
throw new RangeError(`${label} must be a finite non-negative number`);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function validateSidecarLockRetryOptions(retry) {
|
|
8
|
+
if (retry.retries !== undefined && (!Number.isSafeInteger(retry.retries) || retry.retries < 0)) {
|
|
9
|
+
throw new RangeError("lock retry.retries must be a non-negative safe integer");
|
|
10
|
+
}
|
|
11
|
+
if (retry.factor !== undefined)
|
|
12
|
+
assertFiniteNonNegative(retry.factor, "lock retry.factor");
|
|
13
|
+
if (retry.minTimeout !== undefined) {
|
|
14
|
+
assertFiniteNonNegative(retry.minTimeout, "lock retry.minTimeout");
|
|
15
|
+
}
|
|
16
|
+
if (retry.maxTimeout !== undefined) {
|
|
17
|
+
assertFiniteNonNegative(retry.maxTimeout, "lock retry.maxTimeout");
|
|
18
|
+
}
|
|
19
|
+
if (retry.minTimeout !== undefined &&
|
|
20
|
+
retry.maxTimeout !== undefined &&
|
|
21
|
+
retry.minTimeout > retry.maxTimeout) {
|
|
22
|
+
throw new RangeError("lock retry.minTimeout must not exceed retry.maxTimeout");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function validateSidecarLockTimeoutMs(timeoutMs) {
|
|
26
|
+
if (timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY)
|
|
27
|
+
return;
|
|
28
|
+
assertFiniteNonNegative(timeoutMs, "lock timeoutMs");
|
|
29
|
+
}
|
|
2
30
|
export function computeSidecarLockDelayMs(retry, attempt) {
|
|
31
|
+
validateSidecarLockRetryOptions(retry);
|
|
3
32
|
const minTimeout = retry.minTimeout ?? 50;
|
|
4
33
|
const maxTimeout = retry.maxTimeout ?? 1000;
|
|
5
34
|
const factor = retry.factor ?? 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-reclaim.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-reclaim.ts"],"names":[],"mappings":"AACA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"sidecar-lock-reclaim.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-reclaim.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { readFileDescriptorBoundedSync, readFileHandleBounded } from "./bounded-read.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { sameFileIdentity } from "./file-identity.js";
|
|
8
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
8
9
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
9
10
|
const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
|
|
10
11
|
const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
|
|
@@ -59,7 +60,25 @@ export async function readSidecarLockSnapshot(lockPath, options = {}) {
|
|
|
59
60
|
let handle;
|
|
60
61
|
try {
|
|
61
62
|
if (options.lockRoot) {
|
|
62
|
-
const
|
|
63
|
+
const lockRoot = options.lockRoot;
|
|
64
|
+
const relative = relativeSidecarLockPath(lockRoot, lockPath);
|
|
65
|
+
const opened = await lockRoot.open(relative).catch(async (error) => {
|
|
66
|
+
if (error instanceof FsSafeError && error.code === "path-mismatch") {
|
|
67
|
+
// An owner can unlink after open. Prove absence through the same root;
|
|
68
|
+
// acquisition retries create-only, so a later replacement stays guarded.
|
|
69
|
+
try {
|
|
70
|
+
await lockRoot.stat(relative);
|
|
71
|
+
}
|
|
72
|
+
catch (probeError) {
|
|
73
|
+
if (probeError instanceof FsSafeError && probeError.code === "not-found") {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
});
|
|
80
|
+
if (!opened)
|
|
81
|
+
return null;
|
|
63
82
|
try {
|
|
64
83
|
const raw = (await readFileHandleBounded(opened.handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
|
|
65
84
|
return {
|
|
@@ -132,11 +151,14 @@ export function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}
|
|
|
132
151
|
}
|
|
133
152
|
return null;
|
|
134
153
|
}
|
|
135
|
-
|
|
136
|
-
? fsSync.constants.O_NOFOLLOW
|
|
137
|
-
: 0;
|
|
138
|
-
fd = fsSync.openSync(lockPath, fsSync.constants.O_RDONLY | noFollow);
|
|
154
|
+
fd = fsSync.openSync(lockPath, resolveReadOpenFlags());
|
|
139
155
|
const opened = fsSync.fstatSync(fd);
|
|
156
|
+
if (!opened.isFile()) {
|
|
157
|
+
if (options.rejectNonFile) {
|
|
158
|
+
throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
140
162
|
const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
|
|
141
163
|
const after = fsSync.lstatSync(lockPath);
|
|
142
164
|
if (!sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
|
|
@@ -191,10 +213,10 @@ export async function removeSidecarLockIfUnchanged(lockPath, observed, options =
|
|
|
191
213
|
return false;
|
|
192
214
|
}
|
|
193
215
|
if (options.lockRoot) {
|
|
194
|
-
await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath))
|
|
216
|
+
await options.lockRoot.remove(relativeSidecarLockPath(options.lockRoot, lockPath));
|
|
195
217
|
}
|
|
196
218
|
else {
|
|
197
|
-
await fs.rm(lockPath, { force: true })
|
|
219
|
+
await fs.rm(lockPath, { force: true });
|
|
198
220
|
}
|
|
199
221
|
return true;
|
|
200
222
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAsLjC,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAS3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAYL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
|
package/dist/sidecar-lock.js
CHANGED
|
@@ -3,6 +3,7 @@ import { sameFileIdentity } from "./file-identity.js";
|
|
|
3
3
|
import { removeSidecarLockIfUnchanged, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
4
4
|
import { acquireSidecarLock } from "./sidecar-lock-acquire.js";
|
|
5
5
|
import { createHeldSidecarLockHandle } from "./sidecar-lock-handle.js";
|
|
6
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
6
7
|
const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
|
|
7
8
|
const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
|
|
8
9
|
const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
|
|
@@ -121,23 +122,18 @@ async function releaseHeldLock(state, normalizedTargetPath, held, options = {})
|
|
|
121
122
|
if (current !== held) {
|
|
122
123
|
return false;
|
|
123
124
|
}
|
|
125
|
+
if (held.releasePromise) {
|
|
126
|
+
await held.releasePromise;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
124
129
|
if (options.force) {
|
|
125
130
|
held.refCount = 0;
|
|
126
131
|
}
|
|
127
|
-
else {
|
|
132
|
+
else if (!options.retry && held.refCount > 0) {
|
|
128
133
|
held.refCount -= 1;
|
|
129
|
-
if (held.refCount > 0) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (held.releasePromise) {
|
|
134
|
-
await held.releasePromise.catch(() => undefined);
|
|
135
|
-
return true;
|
|
136
134
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
clearInterval(held.compromiseTimer);
|
|
140
|
-
held.compromiseTimer = undefined;
|
|
135
|
+
if (held.refCount > 0) {
|
|
136
|
+
return false;
|
|
141
137
|
}
|
|
142
138
|
held.releasePromise = (async () => {
|
|
143
139
|
await held.handle.close().catch(() => undefined);
|
|
@@ -145,6 +141,13 @@ async function releaseHeldLock(state, normalizedTargetPath, held, options = {})
|
|
|
145
141
|
lockRoot: held.lockRoot,
|
|
146
142
|
parsePayload: held.parsePayload,
|
|
147
143
|
});
|
|
144
|
+
if (state.held.get(normalizedTargetPath) === held) {
|
|
145
|
+
state.held.delete(normalizedTargetPath);
|
|
146
|
+
}
|
|
147
|
+
if (held.compromiseTimer) {
|
|
148
|
+
clearInterval(held.compromiseTimer);
|
|
149
|
+
held.compromiseTimer = undefined;
|
|
150
|
+
}
|
|
148
151
|
})();
|
|
149
152
|
try {
|
|
150
153
|
await held.releasePromise;
|
|
@@ -158,7 +161,7 @@ function handleForHeldLock(state, normalizedTargetPath, held) {
|
|
|
158
161
|
return createHeldSidecarLockHandle({
|
|
159
162
|
normalizedTargetPath,
|
|
160
163
|
held,
|
|
161
|
-
release: async () => await releaseHeldLock(state, normalizedTargetPath, held),
|
|
164
|
+
release: async (options) => await releaseHeldLock(state, normalizedTargetPath, held, { retry: options?.retry }),
|
|
162
165
|
});
|
|
163
166
|
}
|
|
164
167
|
export function createSidecarLockManager(key) {
|
|
@@ -179,12 +182,21 @@ export function createSidecarLockManager(key) {
|
|
|
179
182
|
}
|
|
180
183
|
async function withLock(options, fn) {
|
|
181
184
|
const lock = await acquire(options);
|
|
185
|
+
let result;
|
|
182
186
|
try {
|
|
183
|
-
|
|
187
|
+
result = await fn();
|
|
184
188
|
}
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
catch (bodyError) {
|
|
190
|
+
try {
|
|
191
|
+
await lock.release();
|
|
192
|
+
}
|
|
193
|
+
catch (releaseError) {
|
|
194
|
+
throw createSuppressedError(releaseError, bodyError, "file lock callback and release both failed");
|
|
195
|
+
}
|
|
196
|
+
throw bodyError;
|
|
187
197
|
}
|
|
198
|
+
await lock.release();
|
|
199
|
+
return result;
|
|
188
200
|
}
|
|
189
201
|
async function drain() {
|
|
190
202
|
for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DirectoryReceipt } from "./directory-durability.js";
|
|
2
|
+
import type { FileIdentityStat } from "./file-identity.js";
|
|
3
|
+
import type { StagedFileReceipt } from "./staged-file-types.js";
|
|
4
|
+
type DirectorySnapshot = StagedFileReceipt["directory"];
|
|
5
|
+
export declare function exactIdentityMatches(expected: FileIdentityStat, actual: Readonly<{
|
|
6
|
+
dev: bigint;
|
|
7
|
+
ino: bigint;
|
|
8
|
+
}>): boolean;
|
|
9
|
+
export declare function describeStagedDirectory(fd: number, pathname: string): DirectorySnapshot;
|
|
10
|
+
export declare function assertStagedDirectoryCurrent(receipt: DirectorySnapshot): void;
|
|
11
|
+
export declare function openStagedDirectory(directory: string | DirectoryReceipt): {
|
|
12
|
+
fd: number;
|
|
13
|
+
receipt: DirectorySnapshot;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=staged-directory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,KAAK,iBAAiB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAExD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,OAAO,CAKT;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAYvF;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAQ7E;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,iBAAiB,CAAC;CAC5B,CA+BA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
export function exactIdentityMatches(expected, actual) {
|
|
5
|
+
return ["dev", "ino"].every((key) => {
|
|
6
|
+
const value = expected[key];
|
|
7
|
+
return (typeof value === "bigint" || Number.isSafeInteger(value)) && BigInt(value) === actual[key];
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function describeStagedDirectory(fd, pathname) {
|
|
11
|
+
const identity = fs.fstatSync(fd, { bigint: true });
|
|
12
|
+
if (!identity.isDirectory()) {
|
|
13
|
+
throw new FsSafeError("not-file", "staging parent must be a directory");
|
|
14
|
+
}
|
|
15
|
+
const receipt = Object.freeze({
|
|
16
|
+
path: path.resolve(pathname),
|
|
17
|
+
realPath: fs.realpathSync(pathname),
|
|
18
|
+
identity: Object.freeze({ dev: identity.dev, ino: identity.ino }),
|
|
19
|
+
});
|
|
20
|
+
assertStagedDirectoryCurrent(receipt);
|
|
21
|
+
return receipt;
|
|
22
|
+
}
|
|
23
|
+
export function assertStagedDirectoryCurrent(receipt) {
|
|
24
|
+
const current = fs.lstatSync(receipt.path, { bigint: true });
|
|
25
|
+
if (!current.isDirectory() || !exactIdentityMatches(receipt.identity, current) ||
|
|
26
|
+
fs.realpathSync(receipt.path) !== receipt.realPath) {
|
|
27
|
+
throw new FsSafeError("path-mismatch", "staging directory pathname changed");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function openStagedDirectory(directory) {
|
|
31
|
+
// Copy supplied facts before any asynchronous work; receipts are not authority.
|
|
32
|
+
const pathname = path.resolve(typeof directory === "string" ? directory : directory.path);
|
|
33
|
+
const expected = typeof directory === "string" ? undefined : {
|
|
34
|
+
realPath: directory.realPath, dev: directory.identity.dev, ino: directory.identity.ino,
|
|
35
|
+
};
|
|
36
|
+
const before = fs.lstatSync(pathname, { bigint: true });
|
|
37
|
+
if (!before.isDirectory()) {
|
|
38
|
+
throw new FsSafeError("not-file", "staging parent must be a real directory");
|
|
39
|
+
}
|
|
40
|
+
if (expected && (!exactIdentityMatches(expected, before) || fs.realpathSync(pathname) !== expected.realPath)) {
|
|
41
|
+
throw new FsSafeError("path-mismatch", "stale staging directory receipt");
|
|
42
|
+
}
|
|
43
|
+
const fd = fs.openSync(pathname, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
44
|
+
try {
|
|
45
|
+
const receipt = describeStagedDirectory(fd, pathname);
|
|
46
|
+
if (!exactIdentityMatches(before, receipt.identity)) {
|
|
47
|
+
throw new FsSafeError("path-mismatch", "staging directory changed while opening");
|
|
48
|
+
}
|
|
49
|
+
return { fd, receipt };
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
try {
|
|
53
|
+
fs.closeSync(fd);
|
|
54
|
+
}
|
|
55
|
+
catch (closeError) {
|
|
56
|
+
throw new AggregateError([error, closeError], "staging directory admission and close failed");
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type StagedFileReceipt = Readonly<{
|
|
2
|
+
directory: Readonly<{
|
|
3
|
+
path: string;
|
|
4
|
+
realPath: string;
|
|
5
|
+
identity: Readonly<{
|
|
6
|
+
dev: bigint;
|
|
7
|
+
ino: bigint;
|
|
8
|
+
}>;
|
|
9
|
+
}>;
|
|
10
|
+
temporaryBasename: string;
|
|
11
|
+
/** Preparation-time metadata of the private stage, not a final-file fingerprint. */
|
|
12
|
+
identity: Readonly<{
|
|
13
|
+
dev: bigint;
|
|
14
|
+
ino: bigint;
|
|
15
|
+
mode: number;
|
|
16
|
+
nlink: bigint;
|
|
17
|
+
size: bigint;
|
|
18
|
+
uid: number;
|
|
19
|
+
gid: number;
|
|
20
|
+
mtimeNs: bigint;
|
|
21
|
+
ctimeNs: bigint;
|
|
22
|
+
}>;
|
|
23
|
+
}>;
|
|
24
|
+
export type PublishedFileReceipt = Readonly<{
|
|
25
|
+
status: "published";
|
|
26
|
+
staged: StagedFileReceipt;
|
|
27
|
+
basename: string;
|
|
28
|
+
overwrite: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
export type StagedFilePublication = Readonly<{
|
|
31
|
+
status: "not-published";
|
|
32
|
+
}> | PublishedFileReceipt | Readonly<{
|
|
33
|
+
status: "indeterminate";
|
|
34
|
+
basename: string;
|
|
35
|
+
overwrite: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
export type StagedFileCleanupReceipt = Readonly<{
|
|
38
|
+
temporaryBasename: string;
|
|
39
|
+
publication: StagedFilePublication;
|
|
40
|
+
status: "removed" | "name-absent" | "preserved" | "failed" | "not-needed";
|
|
41
|
+
resources: "closed" | "close-failed";
|
|
42
|
+
}>;
|
|
43
|
+
export type StagedFileFailureDetails = Readonly<{
|
|
44
|
+
phase: "prepare" | "publish" | "cleanup";
|
|
45
|
+
publication: StagedFilePublication;
|
|
46
|
+
cleanup?: StagedFileCleanupReceipt;
|
|
47
|
+
}>;
|
|
48
|
+
export interface StagedFile extends AsyncDisposable {
|
|
49
|
+
readonly receipt: StagedFileReceipt;
|
|
50
|
+
assertCurrent(): Promise<void>;
|
|
51
|
+
publish(basename: string, options: {
|
|
52
|
+
overwrite: boolean;
|
|
53
|
+
}): Promise<PublishedFileReceipt>;
|
|
54
|
+
cleanup(): Promise<StagedFileCleanupReceipt>;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=staged-file-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staged-file-types.d.ts","sourceRoot":"","sources":["../src/staged-file-types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,SAAS,EAAE,QAAQ,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,QAAQ,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAClD,CAAC,CAAC;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oFAAoF;IACpF,QAAQ,EAAE,QAAQ,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAC7B,QAAQ,CAAC;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAC,GACrC,oBAAoB,GACpB,QAAQ,CAAC;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEhF,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,qBAAqB,CAAC;IACnC,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,YAAY,CAAC;IAC1E,SAAS,EAAE,QAAQ,GAAG,cAAc,CAAC;CACtC,CAAC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IACzC,WAAW,EAAE,qBAAqB,CAAC;IACnC,OAAO,CAAC,EAAE,wBAAwB,CAAC;CACpC,CAAC,CAAC;AAEH,MAAM,WAAW,UAAW,SAAQ,eAAe;IACjD,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC1F,OAAO,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAAC;CAC9C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DirectoryReceipt } from "./directory-durability.js";
|
|
2
|
+
import type { StagedFile } from "./staged-file-types.js";
|
|
3
|
+
export type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileFailureDetails, StagedFilePublication, StagedFileReceipt, } from "./staged-file-types.js";
|
|
4
|
+
export declare function stageFileInDirectory(options: {
|
|
5
|
+
directory: string | DirectoryReceipt;
|
|
6
|
+
content: string | Uint8Array;
|
|
7
|
+
/** Published mode; the unpublished stage stays at 0600. Defaults to 0600. */
|
|
8
|
+
mode?: number;
|
|
9
|
+
}): Promise<StagedFile>;
|
|
10
|
+
//# sourceMappingURL=staged-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staged-file.d.ts","sourceRoot":"","sources":["../src/staged-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAKlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,YAAY,EACV,oBAAoB,EAAE,UAAU,EAAE,wBAAwB,EAAE,wBAAwB,EACpF,qBAAqB,EAAE,iBAAiB,GACzC,MAAM,wBAAwB,CAAC;AAEhC,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACrC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,UAAU,CAAC,CAUtB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { assertNativeStaging, createNativeStage } from "./native-staged-file.js";
|
|
3
|
+
import { requireNativeBinding } from "./native.js";
|
|
4
|
+
import { openStagedDirectory } from "./staged-directory.js";
|
|
5
|
+
export async function stageFileInDirectory(options) {
|
|
6
|
+
if (process.platform !== "linux" && process.platform !== "darwin") {
|
|
7
|
+
throw new FsSafeError("unsupported-platform", "retained-directory staging requires Linux or macOS");
|
|
8
|
+
}
|
|
9
|
+
const binding = requireNativeBinding();
|
|
10
|
+
assertNativeStaging(binding);
|
|
11
|
+
const input = { kind: "buffer", data: Buffer.from(options.content) };
|
|
12
|
+
const mode = options.mode ?? 0o600;
|
|
13
|
+
const parent = openStagedDirectory(options.directory);
|
|
14
|
+
return await createNativeStage(binding, parent.fd, parent.receipt, input, mode);
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BigIntStats } from "node:fs";
|
|
2
|
+
type ExactFileIdentity = Pick<BigIntStats, "dev" | "ino">;
|
|
3
|
+
export declare function inspectFileIdentity<T extends ExactFileIdentity>(inspect: () => Promise<T>, expected?: ExactFileIdentity, platform?: NodeJS.Platform): Promise<T>;
|
|
4
|
+
export declare function inspectFileIdentitySync<T extends ExactFileIdentity>(inspect: () => T, expected?: ExactFileIdentity, platform?: NodeJS.Platform): T;
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=strict-file-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strict-file-identity.d.ts","sourceRoot":"","sources":["../src/strict-file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG3C,KAAK,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AA6B1D,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,EACnE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,iBAAiB,EACjE,OAAO,EAAE,MAAM,CAAC,EAChB,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,CAAC,CAOH"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
function identityMismatch() {
|
|
3
|
+
return new FsSafeError("path-mismatch", "file identity changed or could not be verified");
|
|
4
|
+
}
|
|
5
|
+
function identityCheck(expected, platform) {
|
|
6
|
+
const known = {};
|
|
7
|
+
const check = (stat) => {
|
|
8
|
+
let complete = true;
|
|
9
|
+
for (const field of ["dev", "ino"]) {
|
|
10
|
+
const value = stat[field];
|
|
11
|
+
// Numeric receipts cannot recover identity bits already lost to rounding.
|
|
12
|
+
if (typeof value !== "bigint")
|
|
13
|
+
throw identityMismatch();
|
|
14
|
+
if (platform === "win32" && value === 0n) {
|
|
15
|
+
complete = false;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
if (known[field] !== undefined && known[field] !== value)
|
|
19
|
+
throw identityMismatch();
|
|
20
|
+
known[field] = value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return complete;
|
|
24
|
+
};
|
|
25
|
+
if (expected && !check(expected))
|
|
26
|
+
throw identityMismatch();
|
|
27
|
+
return check;
|
|
28
|
+
}
|
|
29
|
+
// Retry only unknown Windows identities, retaining every known component so a
|
|
30
|
+
// later observation cannot erase a definite mismatch. Never reopen the file.
|
|
31
|
+
export async function inspectFileIdentity(inspect, expected, platform = process.platform) {
|
|
32
|
+
const check = identityCheck(expected, platform);
|
|
33
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
34
|
+
const stat = await inspect();
|
|
35
|
+
if (check(stat))
|
|
36
|
+
return stat;
|
|
37
|
+
}
|
|
38
|
+
throw identityMismatch();
|
|
39
|
+
}
|
|
40
|
+
export function inspectFileIdentitySync(inspect, expected, platform = process.platform) {
|
|
41
|
+
const check = identityCheck(expected, platform);
|
|
42
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
43
|
+
const stat = inspect();
|
|
44
|
+
if (check(stat))
|
|
45
|
+
return stat;
|
|
46
|
+
}
|
|
47
|
+
throw identityMismatch();
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suppressed-error.d.ts","sourceRoot":"","sources":["../src/suppressed-error.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAQF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,MAAM,GACd,mBAAmB,CAgBrB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function createSuppressedError(error, suppressed, message) {
|
|
2
|
+
const constructor = globalThis.SuppressedError;
|
|
3
|
+
if (constructor) {
|
|
4
|
+
return new constructor(error, suppressed, message);
|
|
5
|
+
}
|
|
6
|
+
const combined = new Error(message, {
|
|
7
|
+
cause: error instanceof Error ? error : undefined,
|
|
8
|
+
});
|
|
9
|
+
combined.name = "SuppressedError";
|
|
10
|
+
Object.defineProperties(combined, {
|
|
11
|
+
error: { configurable: true, value: error },
|
|
12
|
+
suppressed: { configurable: true, value: suppressed },
|
|
13
|
+
});
|
|
14
|
+
return combined;
|
|
15
|
+
}
|
package/dist/temp-cleanup.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export type TempPathRegistration = (() => void) & {
|
|
|
6
6
|
export declare function registerTempPathForExit(tempPath: string, options?: {
|
|
7
7
|
recursive?: boolean;
|
|
8
8
|
identity?: FileIdentityStat;
|
|
9
|
+
singleLinkFile?: boolean;
|
|
10
|
+
cleanupSync?: () => unknown;
|
|
9
11
|
}): TempPathRegistration;
|
|
10
12
|
export declare function __cleanupRegisteredTempPathsForTest(): void;
|
|
11
13
|
export declare function __cleanupRegisteredTempPathForTest(tempPath: string): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temp-cleanup.d.ts","sourceRoot":"","sources":["../src/temp-cleanup.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAChD,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/C,CAAC;
|
|
1
|
+
{"version":3,"file":"temp-cleanup.d.ts","sourceRoot":"","sources":["../src/temp-cleanup.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAChD,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/C,CAAC;AAkDF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B,GACA,oBAAoB,CA2BtB;AAED,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAUzE"}
|
package/dist/temp-cleanup.js
CHANGED
|
@@ -7,18 +7,32 @@ function pathStillMatchesReceipt(entry) {
|
|
|
7
7
|
return false;
|
|
8
8
|
}
|
|
9
9
|
try {
|
|
10
|
-
|
|
10
|
+
const current = fsSync.lstatSync(entry.path, { bigint: true });
|
|
11
|
+
return (!entry.singleLinkFile || (current.isFile() && current.nlink === 1n)) &&
|
|
12
|
+
sameFileIdentityForCleanup(current, entry.identity);
|
|
11
13
|
}
|
|
12
14
|
catch (error) {
|
|
13
|
-
return error.code === "ENOENT";
|
|
15
|
+
return !entry.singleLinkFile && error.code === "ENOENT";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function removeRegisteredPathSync(entry) {
|
|
19
|
+
if (entry.singleLinkFile)
|
|
20
|
+
fsSync.unlinkSync(entry.path);
|
|
21
|
+
else
|
|
22
|
+
fsSync.rmSync(entry.path, { force: true, recursive: entry.recursive });
|
|
23
|
+
}
|
|
24
|
+
function cleanupEntrySync(entry) {
|
|
25
|
+
if (entry.cleanupSync) {
|
|
26
|
+
entry.cleanupSync();
|
|
27
|
+
}
|
|
28
|
+
else if (pathStillMatchesReceipt(entry)) {
|
|
29
|
+
removeRegisteredPathSync(entry);
|
|
14
30
|
}
|
|
15
31
|
}
|
|
16
32
|
function cleanupRegisteredTempPathsSync() {
|
|
17
33
|
for (const entry of tempCleanupEntries.values()) {
|
|
18
34
|
try {
|
|
19
|
-
|
|
20
|
-
fsSync.rmSync(entry.path, { force: true, recursive: entry.recursive });
|
|
21
|
-
}
|
|
35
|
+
cleanupEntrySync(entry);
|
|
22
36
|
}
|
|
23
37
|
catch {
|
|
24
38
|
// Process-exit cleanup is best-effort.
|
|
@@ -35,8 +49,10 @@ export function registerTempPathForExit(tempPath, options) {
|
|
|
35
49
|
path: tempPath,
|
|
36
50
|
recursive: options?.recursive === true,
|
|
37
51
|
identity: options?.identity,
|
|
52
|
+
singleLinkFile: options?.singleLinkFile,
|
|
53
|
+
cleanupSync: options?.cleanupSync,
|
|
38
54
|
};
|
|
39
|
-
if (!entry.identity) {
|
|
55
|
+
if (!entry.identity && !entry.cleanupSync) {
|
|
40
56
|
try {
|
|
41
57
|
entry.identity = fsSync.lstatSync(tempPath, { bigint: true });
|
|
42
58
|
}
|
|
@@ -46,7 +62,8 @@ export function registerTempPathForExit(tempPath, options) {
|
|
|
46
62
|
}
|
|
47
63
|
tempCleanupEntries.set(tempPath, entry);
|
|
48
64
|
const unregister = (() => {
|
|
49
|
-
tempCleanupEntries.
|
|
65
|
+
if (tempCleanupEntries.get(tempPath) === entry)
|
|
66
|
+
tempCleanupEntries.delete(tempPath);
|
|
50
67
|
});
|
|
51
68
|
unregister.setIdentity = (identity) => {
|
|
52
69
|
entry.identity = identity;
|
|
@@ -62,9 +79,7 @@ export function __cleanupRegisteredTempPathForTest(tempPath) {
|
|
|
62
79
|
return;
|
|
63
80
|
}
|
|
64
81
|
try {
|
|
65
|
-
|
|
66
|
-
fsSync.rmSync(entry.path, { force: true, recursive: entry.recursive });
|
|
67
|
-
}
|
|
82
|
+
cleanupEntrySync(entry);
|
|
68
83
|
}
|
|
69
84
|
finally {
|
|
70
85
|
tempCleanupEntries.delete(tempPath);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type FileIdentityStat } from "./file-identity.js";
|
|
2
|
+
import { type NativeBinding } from "./native.js";
|
|
3
|
+
import { openStagedDirectory } from "./staged-directory.js";
|
|
4
|
+
export type TempWorkspaceCleanupResult = "removed" | "missing" | "identity-mismatch" | "indeterminate";
|
|
5
|
+
export type TempWorkspaceCleanupSafety = "compatible" | "require-bounded";
|
|
6
|
+
type RetainedDirectory = ReturnType<typeof openStagedDirectory>;
|
|
7
|
+
export declare class TempWorkspaceCleanupCapability {
|
|
8
|
+
#private;
|
|
9
|
+
readonly binding: NativeBinding | undefined;
|
|
10
|
+
readonly parent: RetainedDirectory | undefined;
|
|
11
|
+
constructor(root: string, safety: TempWorkspaceCleanupSafety);
|
|
12
|
+
get canRemoveOwnedTree(): boolean;
|
|
13
|
+
assertCurrent(): void;
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
16
|
+
export declare class TempWorkspaceCleanupOwner {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(dir: string, identity: FileIdentityStat, capability: TempWorkspaceCleanupCapability);
|
|
19
|
+
cleanup(): Promise<TempWorkspaceCleanupResult>;
|
|
20
|
+
cleanupSync(): TempWorkspaceCleanupResult;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=temp-workspace-owner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temp-workspace-owner.d.ts","sourceRoot":"","sources":["../src/temp-workspace-owner.ts"],"names":[],"mappings":"AAMA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,EAAgC,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAG1F,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,GAAG,mBAAmB,GAAG,eAAe,CAAC;AACvG,MAAM,MAAM,0BAA0B,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAE1E,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAoBhE,qBAAa,8BAA8B;;IACzC,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAI/C,YAAY,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,0BAA0B,EAiC3D;IAED,IAAI,kBAAkB,IAAI,OAAO,CAEhC;IAED,aAAa,IAAI,IAAI,CASpB;IAED,KAAK,IAAI,IAAI,CAIZ;CACF;AAED,qBAAa,yBAAyB;;IAWpC,YAAY,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,UAAU,EAAE,8BAA8B,EAe9F;IAuLD,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAM7C;IAED,WAAW,IAAI,0BAA0B,CAexC;CACF"}
|