@openclaw/fs-safe 0.14.0 → 0.16.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 +88 -0
- package/README.md +38 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -1
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +10 -11
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +11 -11
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +7 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +79 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +1 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-mode-owner.js +5 -5
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +127 -0
- package/dist/file-cleanup.d.ts +19 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +78 -0
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +5 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +36 -7
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/move-path.js +1 -1
- package/dist/native-binding.d.ts +13 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +5 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +27 -7
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +174 -29
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +26 -14
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +209 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +3 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +121 -72
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +10 -5
- package/dist/pinned-mutation-observation.d.ts +0 -1
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +0 -19
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +25 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +24 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +187 -0
- package/dist/pinned-write-types.d.ts +5 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +35 -145
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -9
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +56 -72
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +8 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +17 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +46 -17
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +24 -15
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +11 -35
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +7 -23
- package/dist/root-write-publication.js +1 -1
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +3 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +4 -3
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/docs/advanced.md +4 -2
- package/docs/archive.md +97 -46
- package/docs/atomic.md +85 -8
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +37 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/file-store.md +19 -0
- package/docs/install.md +31 -7
- package/docs/json-store.md +5 -0
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +32 -12
- package/docs/native.md +38 -7
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +50 -14
- package/docs/public-api.md +3 -2
- package/docs/root.md +56 -3
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +114 -8
- package/docs/staged-file.md +12 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +76 -6
- package/package.json +19 -16
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import syncFs, {} from "node:fs";
|
|
2
2
|
import fs, {} from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
5
|
+
import { hasErrorCode, removeOwnedPath } from "./file-cleanup.js";
|
|
5
6
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
6
7
|
import { FsSafeError } from "./errors.js";
|
|
7
8
|
import { sameFileIdentityForCleanup, sha256Hex } from "./file-identity.js";
|
|
@@ -9,6 +10,22 @@ import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
|
|
|
9
10
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
10
11
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
11
12
|
const PUBLISHED_READ_FLAGS = resolveReadOpenFlags();
|
|
13
|
+
function describeFailure(error) {
|
|
14
|
+
try {
|
|
15
|
+
return String(error);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return "<unprintable failure>";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function isErrorValue(error) {
|
|
22
|
+
try {
|
|
23
|
+
return error instanceof Error;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
12
29
|
export async function removePathIfIdentityUnchanged(targetPath, identity) {
|
|
13
30
|
const parentGuard = await createAsyncDirectoryGuard(path.dirname(targetPath), { bigint: true });
|
|
14
31
|
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
@@ -34,65 +51,24 @@ function missingOwnedFile(pathname, cause) {
|
|
|
34
51
|
cause,
|
|
35
52
|
});
|
|
36
53
|
}
|
|
37
|
-
function cleanupFailure(
|
|
38
|
-
if (
|
|
39
|
-
return new Error(`Atomic file replace failed (${
|
|
54
|
+
function cleanupFailure(originalFailure, cleanupError) {
|
|
55
|
+
if (originalFailure) {
|
|
56
|
+
return new Error(`Atomic file replace failed (${describeFailure(originalFailure.error)}); cleanup also failed (${describeFailure(cleanupError)})`, { cause: originalFailure.error });
|
|
40
57
|
}
|
|
41
|
-
return cleanupError
|
|
58
|
+
return isErrorValue(cleanupError) ? cleanupError : new Error(describeFailure(cleanupError));
|
|
42
59
|
}
|
|
43
60
|
async function cleanupOwnedPath(params) {
|
|
44
|
-
if (!params.identity)
|
|
45
|
-
return true;
|
|
46
61
|
try {
|
|
47
|
-
|
|
48
|
-
? syncFs.lstatSync(params.pathname, { bigint: true })
|
|
49
|
-
: await params.fsModule.lstat(params.pathname, { bigint: true });
|
|
50
|
-
if (current.isSymbolicLink() ||
|
|
51
|
-
!current.isFile() ||
|
|
52
|
-
current.nlink !== 1n ||
|
|
53
|
-
!sameFileIdentityForCleanup(current, params.identity)) {
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
await params.fsModule.unlink(params.pathname);
|
|
62
|
+
await removeOwnedPath(params);
|
|
57
63
|
return true;
|
|
58
64
|
}
|
|
59
65
|
catch (cleanupError) {
|
|
60
|
-
if (cleanupError.code === "ENOENT")
|
|
61
|
-
return true;
|
|
62
66
|
if (params.throwOnCleanupError) {
|
|
63
|
-
throw cleanupFailure(params.
|
|
67
|
+
throw cleanupFailure(params.originalFailure, cleanupError);
|
|
64
68
|
}
|
|
65
69
|
return false;
|
|
66
70
|
}
|
|
67
71
|
}
|
|
68
|
-
// Borrowed handle: the caller retains it until this best-effort cleanup finishes.
|
|
69
|
-
export async function cleanupPinnedFilePath(params) {
|
|
70
|
-
if (!params.identity)
|
|
71
|
-
return;
|
|
72
|
-
try {
|
|
73
|
-
const guard = params.parentGuard;
|
|
74
|
-
if ([guard.stat.dev, guard.stat.ino].some((value) => typeof value === "number" && !Number.isSafeInteger(value)))
|
|
75
|
-
return;
|
|
76
|
-
await assertAsyncDirectoryGuard(guard);
|
|
77
|
-
const parent = syncFs.lstatSync(guard.dir, { bigint: true });
|
|
78
|
-
if (parent.isSymbolicLink() || !parent.isDirectory() ||
|
|
79
|
-
!sameFileIdentityForCleanup(parent, guard.stat))
|
|
80
|
-
return;
|
|
81
|
-
const opened = syncFs.fstatSync(params.handle.fd, { bigint: true });
|
|
82
|
-
if (!opened.isFile() || opened.nlink !== 1n ||
|
|
83
|
-
!sameFileIdentityForCleanup(opened, params.identity))
|
|
84
|
-
return;
|
|
85
|
-
await cleanupOwnedPath({
|
|
86
|
-
fsModule: fs,
|
|
87
|
-
pathname: params.pathname,
|
|
88
|
-
identity: params.identity,
|
|
89
|
-
throwOnCleanupError: false,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
// Unverifiable authority must preserve the path and the original write failure.
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
72
|
function cleanupOwnedPathSync(params) {
|
|
97
73
|
if (!params.identity)
|
|
98
74
|
return true;
|
|
@@ -108,10 +84,10 @@ function cleanupOwnedPathSync(params) {
|
|
|
108
84
|
return true;
|
|
109
85
|
}
|
|
110
86
|
catch (cleanupError) {
|
|
111
|
-
if (cleanupError
|
|
87
|
+
if (hasErrorCode(cleanupError, "ENOENT"))
|
|
112
88
|
return true;
|
|
113
89
|
if (params.throwOnCleanupError) {
|
|
114
|
-
throw cleanupFailure(params.
|
|
90
|
+
throw cleanupFailure(params.originalFailure, cleanupError);
|
|
115
91
|
}
|
|
116
92
|
return false;
|
|
117
93
|
}
|
|
@@ -202,7 +178,9 @@ export class AsyncAtomicTempOwner {
|
|
|
202
178
|
if (sha256Hex(await published.readFile()) !== expectedHash) {
|
|
203
179
|
throw new FsSafeError("path-mismatch", `Atomic replace published content changed: ${pathname}`);
|
|
204
180
|
}
|
|
205
|
-
|
|
181
|
+
const previousHandle = this.#handle;
|
|
182
|
+
this.#handle = undefined;
|
|
183
|
+
await previousHandle?.close();
|
|
206
184
|
this.#handle = published;
|
|
207
185
|
this.#identity = identity;
|
|
208
186
|
published = undefined;
|
|
@@ -216,7 +194,7 @@ export class AsyncAtomicTempOwner {
|
|
|
216
194
|
this.#unregister();
|
|
217
195
|
}
|
|
218
196
|
async finish(params) {
|
|
219
|
-
let
|
|
197
|
+
let deferredFailure;
|
|
220
198
|
let cleanupComplete = !this.#exists;
|
|
221
199
|
if (this.#exists) {
|
|
222
200
|
try {
|
|
@@ -224,28 +202,32 @@ export class AsyncAtomicTempOwner {
|
|
|
224
202
|
fsModule: params.fsModule,
|
|
225
203
|
pathname: this.pathname,
|
|
226
204
|
identity: this.#identity,
|
|
227
|
-
|
|
205
|
+
originalFailure: params.originalFailure,
|
|
228
206
|
throwOnCleanupError: params.throwOnCleanupError,
|
|
229
207
|
});
|
|
230
208
|
}
|
|
231
209
|
catch (error) {
|
|
232
|
-
|
|
210
|
+
deferredFailure = { error };
|
|
233
211
|
}
|
|
234
212
|
}
|
|
235
213
|
if (cleanupComplete)
|
|
236
214
|
this.#unregister();
|
|
215
|
+
const handle = this.#handle;
|
|
216
|
+
this.#handle = undefined;
|
|
237
217
|
try {
|
|
238
|
-
await
|
|
218
|
+
await handle?.close();
|
|
239
219
|
}
|
|
240
220
|
catch (closeError) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
221
|
+
deferredFailure = {
|
|
222
|
+
error: deferredFailure
|
|
223
|
+
? new AggregateError([deferredFailure.error, closeError], "Atomic temp cleanup and close failed")
|
|
224
|
+
: params.originalFailure
|
|
225
|
+
? new AggregateError([params.originalFailure.error, closeError], "Atomic file replace and close failed")
|
|
226
|
+
: closeError,
|
|
227
|
+
};
|
|
246
228
|
}
|
|
247
|
-
if (
|
|
248
|
-
throw
|
|
229
|
+
if (deferredFailure)
|
|
230
|
+
throw deferredFailure.error;
|
|
249
231
|
}
|
|
250
232
|
}
|
|
251
233
|
export class SyncAtomicTempOwner {
|
|
@@ -353,7 +335,7 @@ export class SyncAtomicTempOwner {
|
|
|
353
335
|
this.#unregister();
|
|
354
336
|
}
|
|
355
337
|
finish(params) {
|
|
356
|
-
let
|
|
338
|
+
let deferredFailure;
|
|
357
339
|
let cleanupComplete = !this.#exists;
|
|
358
340
|
if (this.#exists) {
|
|
359
341
|
try {
|
|
@@ -361,12 +343,12 @@ export class SyncAtomicTempOwner {
|
|
|
361
343
|
fsModule: params.fsModule,
|
|
362
344
|
pathname: this.pathname,
|
|
363
345
|
identity: this.#identity,
|
|
364
|
-
|
|
346
|
+
originalFailure: params.originalFailure,
|
|
365
347
|
throwOnCleanupError: params.throwOnCleanupError,
|
|
366
348
|
});
|
|
367
349
|
}
|
|
368
350
|
catch (error) {
|
|
369
|
-
|
|
351
|
+
deferredFailure = { error };
|
|
370
352
|
}
|
|
371
353
|
}
|
|
372
354
|
if (cleanupComplete)
|
|
@@ -378,14 +360,16 @@ export class SyncAtomicTempOwner {
|
|
|
378
360
|
params.fsModule.closeSync(fd);
|
|
379
361
|
}
|
|
380
362
|
catch (closeError) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
363
|
+
deferredFailure = {
|
|
364
|
+
error: deferredFailure
|
|
365
|
+
? new AggregateError([deferredFailure.error, closeError], "Atomic temp cleanup and close failed")
|
|
366
|
+
: params.originalFailure
|
|
367
|
+
? new AggregateError([params.originalFailure.error, closeError], "Atomic file replace and close failed")
|
|
368
|
+
: closeError,
|
|
369
|
+
};
|
|
386
370
|
}
|
|
387
371
|
}
|
|
388
|
-
if (
|
|
389
|
-
throw
|
|
372
|
+
if (deferredFailure)
|
|
373
|
+
throw deferredFailure.error;
|
|
390
374
|
}
|
|
391
375
|
}
|
package/dist/replace-file.js
CHANGED
|
@@ -171,7 +171,7 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
|
|
|
171
171
|
const expectedHash = atomicExpectedContentHash(renameIdentity, options.content);
|
|
172
172
|
const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
|
|
173
173
|
const tempOwner = new AsyncAtomicTempOwner(tempPath);
|
|
174
|
-
let
|
|
174
|
+
let originalFailure;
|
|
175
175
|
try {
|
|
176
176
|
await fsModule.mkdir(fsModule === fs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
|
|
177
177
|
await applyDirectoryMode({ fsModule, dirPath: dir, mode: dirMode });
|
|
@@ -229,13 +229,13 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
|
|
|
229
229
|
return result;
|
|
230
230
|
}
|
|
231
231
|
catch (error) {
|
|
232
|
-
|
|
232
|
+
originalFailure = { error };
|
|
233
233
|
throw error;
|
|
234
234
|
}
|
|
235
235
|
finally {
|
|
236
236
|
await tempOwner.finish({
|
|
237
237
|
fsModule,
|
|
238
|
-
|
|
238
|
+
originalFailure,
|
|
239
239
|
throwOnCleanupError: options.throwOnCleanupError === true,
|
|
240
240
|
});
|
|
241
241
|
}
|
|
@@ -271,7 +271,7 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
|
|
|
271
271
|
}
|
|
272
272
|
const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
|
|
273
273
|
const tempOwner = new SyncAtomicTempOwner(tempPath);
|
|
274
|
-
let
|
|
274
|
+
let originalFailure;
|
|
275
275
|
try {
|
|
276
276
|
fsModule.mkdirSync(fsModule === syncFs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
|
|
277
277
|
applyDirectoryModeSync({ fsModule, dirPath: dir, mode: dirMode, fchmodSync });
|
|
@@ -327,13 +327,13 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
|
|
|
327
327
|
return result;
|
|
328
328
|
}
|
|
329
329
|
catch (error) {
|
|
330
|
-
|
|
330
|
+
originalFailure = { error };
|
|
331
331
|
throw error;
|
|
332
332
|
}
|
|
333
333
|
finally {
|
|
334
334
|
tempOwner.finish({
|
|
335
335
|
fsModule,
|
|
336
|
-
|
|
336
|
+
originalFailure,
|
|
337
337
|
throwOnCleanupError: options.throwOnCleanupError === true,
|
|
338
338
|
});
|
|
339
339
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BigIntStats } from "node:fs";
|
|
2
|
+
import type { NativeBinding, NativeOwnedTreeRemovalResult } from "./native-binding.js";
|
|
3
|
+
type RenameBinding = NativeBinding & Required<Pick<NativeBinding, "renameNoReplaceWithIdentity">>;
|
|
4
|
+
export type RetainedReplacementParent = Readonly<{
|
|
5
|
+
path: string;
|
|
6
|
+
realPath: string;
|
|
7
|
+
identity: Pick<BigIntStats, "dev" | "ino">;
|
|
8
|
+
}>;
|
|
9
|
+
export declare class RetainedDirectoryReplacement {
|
|
10
|
+
#private;
|
|
11
|
+
private constructor();
|
|
12
|
+
static requireBinding(): RenameBinding;
|
|
13
|
+
static retain(params: {
|
|
14
|
+
targetParent: RetainedReplacementParent;
|
|
15
|
+
stagedParent: RetainedReplacementParent;
|
|
16
|
+
originalTarget?: Readonly<{
|
|
17
|
+
path: string;
|
|
18
|
+
identity: Pick<BigIntStats, "dev" | "ino">;
|
|
19
|
+
}>;
|
|
20
|
+
}, binding?: RenameBinding): RetainedDirectoryReplacement;
|
|
21
|
+
renameNoReplace(sourceParent: "staged" | "target", sourceBasename: string, targetBasename: string, expectedSource: Pick<BigIntStats, "dev" | "ino">): void;
|
|
22
|
+
removeOriginal(backupBasename: string): Promise<NativeOwnedTreeRemovalResult["outcome"]>;
|
|
23
|
+
close(): void;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=retained-directory-replacement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retained-directory-replacement.d.ts","sourceRoot":"","sources":["../src/retained-directory-replacement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,OAAO,KAAK,EAAE,aAAa,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAGvF,KAAK,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,EAC9D,6BAA6B,CAAC,CAAC,CAAC;AAIlC,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;CAC5C,CAAC,CAAC;AAsHH,qBAAa,4BAA4B;;IAQvC,OAAO,eAUN;IAED,MAAM,CAAC,cAAc,IAAI,aAAa,CAQrC;IAED,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE;QACpB,YAAY,EAAE,yBAAyB,CAAC;QACxC,YAAY,EAAE,yBAAyB,CAAC;QACxC,cAAc,CAAC,EAAE,QAAQ,CAAC;YACxB,IAAI,EAAE,MAAM,CAAC;YACb,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;SAC5C,CAAC,CAAC;KACJ,EAAE,OAAO,gBAAgD,GAAG,4BAA4B,CAiDxF;IAED,eAAe,CACb,YAAY,EAAE,QAAQ,GAAG,QAAQ,EACjC,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAC/C,IAAI,CAYN;IAEK,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC,CA2B7F;IAED,KAAK,IAAI,IAAI,CAQZ;CACF"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import { assertDirectoryIdentitySync } from "./directory-guard.js";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { getNativeBinding } from "./native.js";
|
|
5
|
+
function helperUnavailable(message, cause) {
|
|
6
|
+
return new FsSafeError("helper-unavailable", message, {
|
|
7
|
+
...(cause instanceof Error ? { cause } : {}),
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
function copyOperationalCode(target, source) {
|
|
11
|
+
const code = source?.code;
|
|
12
|
+
if (typeof code === "string") {
|
|
13
|
+
Object.defineProperty(target, "code", { configurable: true, value: code });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function primaryWithCloseFailure(error, closeError, message) {
|
|
17
|
+
const cause = new AggregateError([error, closeError], message);
|
|
18
|
+
if (error instanceof FsSafeError) {
|
|
19
|
+
return new FsSafeError(error.code, message, {
|
|
20
|
+
cause,
|
|
21
|
+
...(error.details ? { details: error.details } : {}),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const wrapped = new Error(message, { cause });
|
|
25
|
+
copyOperationalCode(wrapped, error);
|
|
26
|
+
return wrapped;
|
|
27
|
+
}
|
|
28
|
+
function supportsRename(binding) {
|
|
29
|
+
return typeof binding?.renameNoReplaceWithIdentity === "function";
|
|
30
|
+
}
|
|
31
|
+
function supportsCleanup(binding) {
|
|
32
|
+
return typeof binding.ownedTreeRemovalAvailable === "function" &&
|
|
33
|
+
typeof binding.removeOwnedTree === "function";
|
|
34
|
+
}
|
|
35
|
+
function closeDescriptors(descriptors) {
|
|
36
|
+
const errors = [];
|
|
37
|
+
const closed = new Set();
|
|
38
|
+
for (const fd of descriptors) {
|
|
39
|
+
if (fd === undefined || closed.has(fd))
|
|
40
|
+
continue;
|
|
41
|
+
closed.add(fd);
|
|
42
|
+
try {
|
|
43
|
+
fsSync.closeSync(fd);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
errors.push(error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (errors.length === 1)
|
|
50
|
+
throw errors[0];
|
|
51
|
+
if (errors.length > 1) {
|
|
52
|
+
throw new AggregateError(errors, "retained directory replacement descriptors failed to close");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const retainedDirectoryOpenFlags = fsSync.constants.O_RDONLY |
|
|
56
|
+
fsSync.constants.O_DIRECTORY |
|
|
57
|
+
fsSync.constants.O_NOFOLLOW |
|
|
58
|
+
fsSync.constants.O_NONBLOCK;
|
|
59
|
+
function openRetainedDirectory(label, expected) {
|
|
60
|
+
let fd;
|
|
61
|
+
try {
|
|
62
|
+
fd = fsSync.openSync(expected.path, retainedDirectoryOpenFlags);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const code = error?.code;
|
|
66
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
67
|
+
const purpose = label === "original target"
|
|
68
|
+
? "original target for bounded cleanup"
|
|
69
|
+
: label;
|
|
70
|
+
throw helperUnavailable(`atomic directory replacement cannot retain the ${purpose}`, error);
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const descriptor = fsSync.fstatSync(fd, { bigint: true });
|
|
76
|
+
if (!descriptor.isDirectory()) {
|
|
77
|
+
throw new FsSafeError("not-file", `directory replacement ${label} must be a directory`);
|
|
78
|
+
}
|
|
79
|
+
if (descriptor.dev !== expected.identity.dev || descriptor.ino !== expected.identity.ino) {
|
|
80
|
+
throw new FsSafeError("path-mismatch", `directory replacement ${label} changed during admission`);
|
|
81
|
+
}
|
|
82
|
+
assertDirectoryIdentitySync(expected.path, {
|
|
83
|
+
dev: expected.identity.dev,
|
|
84
|
+
ino: expected.identity.ino,
|
|
85
|
+
...(expected.realPath === undefined ? {} : { realPath: expected.realPath }),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
try {
|
|
90
|
+
fsSync.closeSync(fd);
|
|
91
|
+
}
|
|
92
|
+
catch (closeError) {
|
|
93
|
+
throw primaryWithCloseFailure(error, closeError, `directory replacement ${label} admission and close failed`);
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
return fd;
|
|
98
|
+
}
|
|
99
|
+
export class RetainedDirectoryReplacement {
|
|
100
|
+
#binding;
|
|
101
|
+
#originalDirectoryFd;
|
|
102
|
+
#stagedParentFd;
|
|
103
|
+
#targetParentFd;
|
|
104
|
+
#cleanupUsed = false;
|
|
105
|
+
#closed = false;
|
|
106
|
+
constructor(binding, targetParentFd, stagedParentFd, originalDirectoryFd) {
|
|
107
|
+
this.#binding = binding;
|
|
108
|
+
this.#targetParentFd = targetParentFd;
|
|
109
|
+
this.#stagedParentFd = stagedParentFd;
|
|
110
|
+
this.#originalDirectoryFd = originalDirectoryFd;
|
|
111
|
+
}
|
|
112
|
+
static requireBinding() {
|
|
113
|
+
const binding = getNativeBinding();
|
|
114
|
+
if (!supportsRename(binding)) {
|
|
115
|
+
throw helperUnavailable("atomic directory replacement requires native identity-fenced no-replace rename support");
|
|
116
|
+
}
|
|
117
|
+
return binding;
|
|
118
|
+
}
|
|
119
|
+
static retain(params, binding = RetainedDirectoryReplacement.requireBinding()) {
|
|
120
|
+
let targetParentFd;
|
|
121
|
+
let stagedParentFd;
|
|
122
|
+
let originalDirectoryFd;
|
|
123
|
+
try {
|
|
124
|
+
targetParentFd = openRetainedDirectory("target parent", params.targetParent);
|
|
125
|
+
stagedParentFd = params.stagedParent.path === params.targetParent.path
|
|
126
|
+
? targetParentFd
|
|
127
|
+
: openRetainedDirectory("staged parent", params.stagedParent);
|
|
128
|
+
if (params.originalTarget) {
|
|
129
|
+
if (!supportsCleanup(binding)) {
|
|
130
|
+
throw helperUnavailable("replacing an existing directory requires native bounded owned-tree cleanup");
|
|
131
|
+
}
|
|
132
|
+
let available = false;
|
|
133
|
+
try {
|
|
134
|
+
available = binding.ownedTreeRemovalAvailable(targetParentFd) === true;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw helperUnavailable("native owned-tree cleanup capability probe failed", error);
|
|
138
|
+
}
|
|
139
|
+
if (!available) {
|
|
140
|
+
throw helperUnavailable("native owned-tree cleanup is unavailable for the target parent");
|
|
141
|
+
}
|
|
142
|
+
originalDirectoryFd = openRetainedDirectory("original target", {
|
|
143
|
+
path: params.originalTarget.path,
|
|
144
|
+
identity: params.originalTarget.identity,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return new RetainedDirectoryReplacement(binding, targetParentFd, stagedParentFd, originalDirectoryFd);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
try {
|
|
151
|
+
closeDescriptors([originalDirectoryFd, stagedParentFd, targetParentFd]);
|
|
152
|
+
}
|
|
153
|
+
catch (closeError) {
|
|
154
|
+
throw primaryWithCloseFailure(error, closeError, "retained directory replacement admission and descriptor close failed");
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
renameNoReplace(sourceParent, sourceBasename, targetBasename, expectedSource) {
|
|
160
|
+
if (this.#closed) {
|
|
161
|
+
throw new FsSafeError("path-mismatch", "retained directory rename authority is unavailable");
|
|
162
|
+
}
|
|
163
|
+
this.#binding.renameNoReplaceWithIdentity(sourceParent === "staged" ? this.#stagedParentFd : this.#targetParentFd, sourceBasename, this.#targetParentFd, targetBasename, expectedSource.dev, expectedSource.ino);
|
|
164
|
+
}
|
|
165
|
+
async removeOriginal(backupBasename) {
|
|
166
|
+
if (this.#closed || this.#cleanupUsed || this.#originalDirectoryFd === undefined ||
|
|
167
|
+
!supportsCleanup(this.#binding)) {
|
|
168
|
+
throw new FsSafeError("path-mismatch", "retained owned-tree cleanup authority is unavailable");
|
|
169
|
+
}
|
|
170
|
+
this.#cleanupUsed = true;
|
|
171
|
+
const result = await this.#binding.removeOwnedTree(this.#targetParentFd, backupBasename, this.#originalDirectoryFd);
|
|
172
|
+
if (result.errorCode) {
|
|
173
|
+
if (result.errorCode === "path-mismatch") {
|
|
174
|
+
throw new FsSafeError("path-mismatch", result.errorMessage ?? "native owned-tree cleanup lost retained identity");
|
|
175
|
+
}
|
|
176
|
+
throw Object.assign(new Error(result.errorMessage ?? "native owned-tree cleanup failed"), { code: result.errorCode });
|
|
177
|
+
}
|
|
178
|
+
if (result.outcome !== "removed" && result.outcome !== "preserved") {
|
|
179
|
+
throw helperUnavailable("native owned-tree cleanup returned an invalid result");
|
|
180
|
+
}
|
|
181
|
+
return result.outcome;
|
|
182
|
+
}
|
|
183
|
+
close() {
|
|
184
|
+
if (this.#closed)
|
|
185
|
+
return;
|
|
186
|
+
this.#closed = true;
|
|
187
|
+
closeDescriptors([
|
|
188
|
+
this.#originalDirectoryFd,
|
|
189
|
+
this.#stagedParentFd,
|
|
190
|
+
this.#targetParentFd,
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
}
|
package/dist/root-boundary.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-boundary.d.ts","sourceRoot":"","sources":["../src/root-boundary.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB,CAAC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,GAAG,UAAU,CAAC;CACjC,CAAC,CAAC;AAwHH;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,EAAE,CACrB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,QAAQ,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,KAC7C,IAAI,CAAC;CACX,GAAG,gBAAgB,GAAG,SAAS,CAiE/B"}
|
|
1
|
+
{"version":3,"file":"root-boundary.d.ts","sourceRoot":"","sources":["../src/root-boundary.ts"],"names":[],"mappings":"AAKA,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAG/E;AAED,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB,CAAC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,GAAG,UAAU,CAAC;CACjC,CAAC,CAAC;AAwHH;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,EAAE,CACrB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,QAAQ,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,KAC7C,IAAI,CAAC;CACX,GAAG,gBAAgB,GAAG,SAAS,CAiE/B"}
|
package/dist/root-boundary.js
CHANGED
|
@@ -2,6 +2,10 @@ import path from "node:path";
|
|
|
2
2
|
import { inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
3
3
|
import { isPathRelativeEscape } from "./path.js";
|
|
4
4
|
import { realpathSync } from "./realpath.js";
|
|
5
|
+
export function sameNormalizedPathSpelling(left, right) {
|
|
6
|
+
// Preserve case on Windows, where individual directories can be case-sensitive.
|
|
7
|
+
return path.resolve(left) === path.resolve(right);
|
|
8
|
+
}
|
|
5
9
|
function windowsComparablePath(value, normalize) {
|
|
6
10
|
const original = normalize
|
|
7
11
|
? path.win32.normalize(value.replaceAll("/", "\\"))
|
package/dist/root-context.d.ts
CHANGED
|
@@ -41,12 +41,4 @@ export declare function resolvePathInRoot(root: RootContext, relativePath: strin
|
|
|
41
41
|
rootWithSep: string;
|
|
42
42
|
resolved: string;
|
|
43
43
|
}>;
|
|
44
|
-
export declare function resolvePathWithinRoot(params: {
|
|
45
|
-
rootDir: string;
|
|
46
|
-
relativePath: string;
|
|
47
|
-
}): Promise<{
|
|
48
|
-
rootReal: string;
|
|
49
|
-
rootWithSep: string;
|
|
50
|
-
resolved: string;
|
|
51
|
-
}>;
|
|
52
44
|
//# sourceMappingURL=root-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAG/C,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC/B,MAAM,sBAAsB,CAAC;AAuB9B,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC7C,YAAY,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAQF,eAAO,MAAM,iBAAiB,UAAW,MAAM,WACM,CAAC;AAEtD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAOtE;AAED,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAKzE;AAMD,wBAAsB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBtF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC9E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAyBhF;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,IAAI,CAmBN;AAED,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAYhD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;IACR,cAAc,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,GACA,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuEtE
|
|
1
|
+
{"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAG/C,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC/B,MAAM,sBAAsB,CAAC;AAuB9B,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC7C,YAAY,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAQF,eAAO,MAAM,iBAAiB,UAAW,MAAM,WACM,CAAC;AAEtD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAOtE;AAED,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAKzE;AAMD,wBAAsB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBtF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC9E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAyBhF;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,IAAI,CAmBN;AAED,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAYhD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;IACR,cAAc,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,GACA,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuEtE"}
|
package/dist/root-context.js
CHANGED
|
@@ -234,6 +234,3 @@ export async function resolvePathInRoot(root, relativePath, options) {
|
|
|
234
234
|
}
|
|
235
235
|
return { rootReal: root.rootReal, rootWithSep: root.rootWithSep, resolved };
|
|
236
236
|
}
|
|
237
|
-
export async function resolvePathWithinRoot(params) {
|
|
238
|
-
return await resolvePathInRoot(await resolveRootContext(params.rootDir), params.relativePath);
|
|
239
|
-
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { PinnedWriteInput } from "./pinned-write.js";
|
|
2
2
|
import type { RootCreateOptions, RootCreateStreamOptions, RootWriteOptions } from "./root-options.js";
|
|
3
|
-
|
|
3
|
+
declare const exclusiveSidecarCreate: unique symbol;
|
|
4
|
+
export declare const sidecarExclusiveCreate: Readonly<{
|
|
5
|
+
[exclusiveSidecarCreate]: true;
|
|
6
|
+
}>;
|
|
7
|
+
export type RootWriteParams = RootWriteOptions & RootCreateOptions & RootCreateStreamOptions & {
|
|
4
8
|
relativePath: string;
|
|
5
9
|
data: string | Buffer | AsyncIterable<Uint8Array>;
|
|
10
|
+
strictFileSync?: boolean;
|
|
11
|
+
[exclusiveSidecarCreate]?: true;
|
|
6
12
|
};
|
|
7
13
|
export declare function rethrowCreateInputError(error: unknown): never;
|
|
8
14
|
export declare function createInputOptions(data: RootWriteParams["data"], options: RootCreateOptions & RootCreateStreamOptions, defaultMaxBytes?: number): RootCreateOptions & RootCreateStreamOptions;
|
|
9
15
|
export declare function rootWriteInput(params: RootWriteParams): PinnedWriteInput;
|
|
16
|
+
export {};
|
|
10
17
|
//# sourceMappingURL=root-create-input.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-create-input.d.ts","sourceRoot":"","sources":["../src/root-create-input.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"root-create-input.d.ts","sourceRoot":"","sources":["../src/root-create-input.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAItG,QAAA,MAAM,sBAAsB,eAAmC,CAAC;AAChE,eAAO,MAAM,sBAAsB;;EAA6D,CAAC;AAEjG,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,uBAAuB,GAAG;IAC7F,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,CAAC,sBAAsB,CAAC,CAAC,EAAE,IAAI,CAAC;CACjC,CAAC;AAQF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAG7D;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAC7B,OAAO,EAAE,iBAAiB,GAAG,uBAAuB,EACpD,eAAe,CAAC,EAAE,MAAM,GACvB,iBAAiB,GAAG,uBAAuB,CAkB7C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CA8CxE"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
3
|
import { MutationAuthorityError } from "./mutation-authority.js";
|
|
4
|
+
// Lock records need exclusive creation, not private-before-visible publication.
|
|
5
|
+
// Keep this composition hook off the public Root options and package exports.
|
|
6
|
+
const exclusiveSidecarCreate = Symbol("exclusiveSidecarCreate");
|
|
7
|
+
export const sidecarExclusiveCreate = Object.freeze({ [exclusiveSidecarCreate]: true });
|
|
4
8
|
class CreateInputError extends FsSafeError {
|
|
5
9
|
rejection;
|
|
6
10
|
constructor(rejection) {
|
|
@@ -16,24 +20,33 @@ export function rethrowCreateInputError(error) {
|
|
|
16
20
|
export function createInputOptions(data, options, defaultMaxBytes) {
|
|
17
21
|
if (typeof data === "string" || Buffer.isBuffer(data))
|
|
18
22
|
return options;
|
|
19
|
-
options
|
|
23
|
+
const { signal } = options;
|
|
24
|
+
signal?.throwIfAborted();
|
|
20
25
|
if (options.encoding !== undefined || options.renameIdentity !== undefined) {
|
|
21
26
|
throw new TypeError("streamed create does not accept encoding or renameIdentity");
|
|
22
27
|
}
|
|
23
28
|
const maxBytes = normalizeMaxBytes(options.maxBytes, { defaultValue: defaultMaxBytes });
|
|
29
|
+
const { assertBeforeMutation } = options;
|
|
24
30
|
return {
|
|
25
31
|
...options,
|
|
26
32
|
maxBytes,
|
|
33
|
+
signal,
|
|
27
34
|
assertBeforeMutation: () => {
|
|
28
|
-
|
|
29
|
-
return
|
|
35
|
+
signal?.throwIfAborted();
|
|
36
|
+
return assertBeforeMutation == null ? undefined : Reflect.apply(assertBeforeMutation, options, []);
|
|
30
37
|
},
|
|
31
38
|
};
|
|
32
39
|
}
|
|
33
40
|
export function rootWriteInput(params) {
|
|
41
|
+
if (params.atomic !== undefined && typeof params.atomic !== "boolean") {
|
|
42
|
+
throw new TypeError("atomic must be a boolean");
|
|
43
|
+
}
|
|
34
44
|
const data = params.data;
|
|
35
45
|
if (typeof data === "string" || Buffer.isBuffer(data)) {
|
|
36
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
kind: "buffer", data, encoding: params.encoding,
|
|
48
|
+
stageBeforePublish: params[exclusiveSidecarCreate] ? false : params.atomic === true ? true : undefined,
|
|
49
|
+
};
|
|
37
50
|
}
|
|
38
51
|
return {
|
|
39
52
|
kind: "stream",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { PinnedWriteMutationAdmission } from "./pinned-write.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type RootContext } from "./root-context.js";
|
|
3
3
|
/**
|
|
4
4
|
* Complete the common Root.mkdir case with one exact existing parent. This is
|
|
5
5
|
* optional evidence: every uncertain probe returns to the ordered root walk.
|
|
6
6
|
*/
|
|
7
7
|
export declare function tryMkdirAtExactParent(root: RootContext, targetPath: string, mutationAdmission: PinnedWriteMutationAdmission): Promise<boolean>;
|
|
8
|
-
export declare function prepareRootWriteTarget(root: RootContext, targetPath: string, assertBeforeMutation?: () => void, mutationAdmission?: PinnedWriteMutationAdmission): Promise<string>;
|
|
8
|
+
export declare function prepareRootWriteTarget(root: RootContext, targetPath: string, assertBeforeMutation?: () => void, mutationAdmission?: PinnedWriteMutationAdmission, privateMode?: boolean): Promise<string>;
|
|
9
9
|
export declare function mkdirPathFallback(root: RootContext, resolved: {
|
|
10
10
|
rootReal: string;
|
|
11
11
|
resolved: string;
|
|
12
|
-
}, assertBeforeMutation?: () => void, rejectSymlinks?: boolean, mutationAdmission?: PinnedWriteMutationAdmission): Promise<void>;
|
|
12
|
+
}, assertBeforeMutation?: () => void, rejectSymlinks?: boolean, mutationAdmission?: PinnedWriteMutationAdmission, privateMode?: boolean): Promise<void>;
|
|
13
13
|
//# sourceMappingURL=root-directory-creation.d.ts.map
|