@openclaw/fs-safe 0.11.0 → 0.13.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 +119 -0
- package/README.md +15 -8
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +32 -11
- package/dist/advanced.d.ts +3 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +3 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -3
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +13 -9
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +6 -2
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +15 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -19
- package/dist/archive-plan.js +1 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +55 -46
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +43 -10
- package/dist/archive-tar-inspect.d.ts.map +1 -1
- package/dist/archive-tar-inspect.js +4 -1
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +2 -2
- package/dist/archive-zip-directory.d.ts +3 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +13 -2
- package/dist/archive-zip-loader.d.ts +3 -2
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +30 -4
- package/dist/archive-zip-manifest.d.ts +5 -0
- package/dist/archive-zip-manifest.d.ts.map +1 -0
- package/dist/archive-zip-manifest.js +22 -0
- package/dist/archive-zip-names.d.ts +6 -1
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +50 -17
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +3 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/bounded-read.js +2 -2
- package/dist/darwin-acl.d.ts +4 -0
- package/dist/darwin-acl.d.ts.map +1 -0
- package/dist/darwin-acl.js +24 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +8 -2
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -3
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +67 -23
- package/dist/directory-entry-path.d.ts +3 -0
- package/dist/directory-entry-path.d.ts.map +1 -0
- package/dist/directory-entry-path.js +21 -0
- package/dist/directory-guard.d.ts +17 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +134 -48
- package/dist/directory-mode-node.d.ts +12 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +102 -4
- package/dist/effective-uid.d.ts +2 -0
- package/dist/effective-uid.d.ts.map +1 -0
- package/dist/effective-uid.js +25 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +98 -27
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +12 -2
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +36 -6
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +29 -9
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +6 -2
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -65
- package/dist/file-store-copy-source.d.ts +5 -0
- package/dist/file-store-copy-source.d.ts.map +1 -0
- package/dist/file-store-copy-source.js +31 -0
- package/dist/file-store-path.d.ts.map +1 -1
- package/dist/file-store-path.js +4 -1
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +9 -1
- package/dist/file-store-sync-directory.d.ts +16 -0
- package/dist/file-store-sync-directory.d.ts.map +1 -0
- package/dist/file-store-sync-directory.js +349 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +11 -28
- package/dist/filename.d.ts +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +85 -23
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -2
- package/dist/guarded-mkdir.d.ts +10 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +217 -33
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +16 -5
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +73 -10
- package/dist/install-path.d.ts +6 -0
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +67 -19
- package/dist/json-durable-queue-ownership.d.ts +8 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +134 -43
- package/dist/json-durable-queue-paths.d.ts +11 -0
- package/dist/json-durable-queue-paths.d.ts.map +1 -0
- package/dist/json-durable-queue-paths.js +42 -0
- package/dist/json-durable-queue-read.d.ts +6 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -0
- package/dist/json-durable-queue-read.js +61 -0
- package/dist/json-durable-queue.d.ts +1 -1
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +83 -134
- package/dist/json-store.d.ts.map +1 -1
- package/dist/json-store.js +5 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +54 -22
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +4 -0
- package/dist/local-file-descriptor.d.ts +17 -0
- package/dist/local-file-descriptor.d.ts.map +1 -0
- package/dist/local-file-descriptor.js +84 -0
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -7
- package/dist/move-path-cleanup.d.ts +2 -0
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +44 -18
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +31 -6
- package/dist/native-binding.d.ts +21 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-directory-observation.d.ts +17 -0
- package/dist/native-directory-observation.d.ts.map +1 -0
- package/dist/native-directory-observation.js +37 -0
- package/dist/native-parent-admission.d.ts +31 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +124 -0
- package/dist/native-pinned-write-windows.d.ts +0 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +0 -3
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +309 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +15 -5
- package/dist/overwrite-file-handle.d.ts.map +1 -1
- package/dist/overwrite-file-handle.js +5 -1
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -0
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +7 -2
- package/dist/path-prefix.d.ts +7 -0
- package/dist/path-prefix.d.ts.map +1 -0
- package/dist/path-prefix.js +82 -0
- package/dist/path-scope-lexical.d.ts +14 -0
- package/dist/path-scope-lexical.d.ts.map +1 -0
- package/dist/path-scope-lexical.js +37 -0
- package/dist/path-segment-route.d.ts +7 -0
- package/dist/path-segment-route.d.ts.map +1 -0
- package/dist/path-segment-route.js +24 -0
- package/dist/path-suffix-aliases.d.ts +10 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -0
- package/dist/path-suffix-aliases.js +386 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +35 -6
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +14 -3
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +37 -8
- package/dist/pinned-mutation-admission.d.ts +25 -0
- package/dist/pinned-mutation-admission.d.ts.map +1 -0
- package/dist/pinned-mutation-admission.js +425 -0
- package/dist/pinned-mutation-observation.d.ts +34 -0
- package/dist/pinned-mutation-observation.d.ts.map +1 -0
- package/dist/pinned-mutation-observation.js +142 -0
- package/dist/pinned-mutation-shared-route.d.ts +24 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
- package/dist/pinned-mutation-shared-route.js +70 -0
- package/dist/pinned-open.d.ts +6 -0
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +28 -10
- package/dist/pinned-write-types.d.ts +75 -0
- package/dist/pinned-write-types.d.ts.map +1 -0
- package/dist/pinned-write-types.js +1 -0
- package/dist/pinned-write.d.ts +7 -33
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +152 -17
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +2 -0
- package/dist/private-producer-handoff.d.ts +16 -0
- package/dist/private-producer-handoff.d.ts.map +1 -0
- package/dist/private-producer-handoff.js +272 -0
- package/dist/private-temp-workspace.d.ts +2 -39
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +183 -77
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +33 -29
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +56 -45
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +12 -5
- package/dist/replace-file-temp-owner.d.ts +1 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +12 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +24 -24
- package/dist/root-boundary.d.ts +29 -0
- package/dist/root-boundary.d.ts.map +1 -0
- package/dist/root-boundary.js +182 -0
- package/dist/root-context.d.ts +12 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +89 -16
- package/dist/root-directory-creation.d.ts +13 -0
- package/dist/root-directory-creation.d.ts.map +1 -0
- package/dist/root-directory-creation.js +212 -0
- package/dist/root-directory-list.d.ts +16 -4
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +182 -33
- package/dist/root-directory.d.ts +23 -0
- package/dist/root-directory.d.ts.map +1 -0
- package/dist/root-directory.js +141 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +12 -1
- package/dist/root-file-final-admission.d.ts +21 -0
- package/dist/root-file-final-admission.d.ts.map +1 -0
- package/dist/root-file-final-admission.js +83 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +103 -24
- package/dist/root-impl.d.ts +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +316 -325
- package/dist/root-move-noreplace.d.ts +14 -0
- package/dist/root-move-noreplace.d.ts.map +1 -0
- package/dist/root-move-noreplace.js +202 -0
- package/dist/root-move-preflight.d.ts +8 -0
- package/dist/root-move-preflight.d.ts.map +1 -0
- package/dist/root-move-preflight.js +16 -0
- package/dist/root-observed-path.d.ts +9 -0
- package/dist/root-observed-path.d.ts.map +1 -0
- package/dist/root-observed-path.js +95 -0
- package/dist/root-path-errors.d.ts +12 -0
- package/dist/root-path-errors.d.ts.map +1 -0
- package/dist/root-path-errors.js +13 -0
- package/dist/root-path-existing.d.ts +2 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +63 -22
- package/dist/root-path-observation.d.ts +63 -0
- package/dist/root-path-observation.d.ts.map +1 -0
- package/dist/root-path-observation.js +180 -0
- package/dist/root-path-stat.d.ts +5 -0
- package/dist/root-path-stat.d.ts.map +1 -0
- package/dist/root-path-stat.js +101 -0
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +23 -4
- package/dist/root-path.d.ts +13 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +263 -73
- package/dist/root-paths-lexical.d.ts +9 -0
- package/dist/root-paths-lexical.d.ts.map +1 -0
- package/dist/root-paths-lexical.js +22 -0
- package/dist/root-paths.d.ts +3 -29
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +89 -178
- package/dist/root-read-admission.d.ts +26 -0
- package/dist/root-read-admission.d.ts.map +1 -0
- package/dist/root-read-admission.js +94 -0
- package/dist/root-remove-identity.d.ts +15 -0
- package/dist/root-remove-identity.d.ts.map +1 -0
- package/dist/root-remove-identity.js +89 -0
- package/dist/root-remove-receipt.d.ts +17 -0
- package/dist/root-remove-receipt.d.ts.map +1 -0
- package/dist/root-remove-receipt.js +37 -0
- package/dist/root-remove.d.ts +2 -1
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +172 -10
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +2 -1
- package/dist/root-write-admission.d.ts +68 -0
- package/dist/root-write-admission.d.ts.map +1 -0
- package/dist/root-write-admission.js +322 -0
- package/dist/root-write-compatibility.d.ts +13 -0
- package/dist/root-write-compatibility.d.ts.map +1 -0
- package/dist/root-write-compatibility.js +74 -0
- package/dist/root-write-complete-parent.d.ts +42 -0
- package/dist/root-write-complete-parent.d.ts.map +1 -0
- package/dist/root-write-complete-parent.js +195 -0
- package/dist/root-write-mode.d.ts +2 -0
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +20 -7
- package/dist/root-write-publication.d.ts +24 -0
- package/dist/root-write-publication.d.ts.map +1 -0
- package/dist/root-write-publication.js +85 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +18 -7
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +3 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +62 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +18 -13
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +5 -1
- package/dist/secure-file-windows.d.ts +10 -0
- package/dist/secure-file-windows.d.ts.map +1 -0
- package/dist/secure-file-windows.js +186 -0
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +98 -10
- package/dist/secure-temp-dir.d.ts +6 -3
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +121 -101
- package/dist/secure-temp-repair.d.ts +35 -0
- package/dist/secure-temp-repair.d.ts.map +1 -0
- package/dist/secure-temp-repair.js +106 -0
- package/dist/sibling-staged-file.d.ts +3 -1
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +181 -50
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +27 -12
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +45 -15
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +4 -2
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -3
- package/dist/staged-directory.d.ts +14 -5
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +54 -3
- package/dist/standalone-publication-path.d.ts +2 -0
- package/dist/standalone-publication-path.d.ts.map +1 -0
- package/dist/standalone-publication-path.js +6 -0
- package/dist/stat-observation.d.ts +11 -0
- package/dist/stat-observation.d.ts.map +1 -0
- package/dist/stat-observation.js +64 -0
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +39 -2
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +7 -2
- package/dist/temp-target.d.ts +2 -0
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +133 -4
- package/dist/temp-workspace-admission.d.ts +22 -0
- package/dist/temp-workspace-admission.d.ts.map +1 -0
- package/dist/temp-workspace-admission.js +374 -0
- package/dist/temp-workspace-child-admission.d.ts +13 -0
- package/dist/temp-workspace-child-admission.d.ts.map +1 -0
- package/dist/temp-workspace-child-admission.js +95 -0
- package/dist/temp-workspace-descriptor.d.ts +48 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -0
- package/dist/temp-workspace-descriptor.js +363 -0
- package/dist/temp-workspace-identity.d.ts +15 -0
- package/dist/temp-workspace-identity.d.ts.map +1 -0
- package/dist/temp-workspace-identity.js +41 -0
- package/dist/temp-workspace-owner.d.ts +7 -6
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +121 -61
- package/dist/temp-workspace-permissions.d.ts +4 -0
- package/dist/temp-workspace-permissions.d.ts.map +1 -0
- package/dist/temp-workspace-permissions.js +32 -0
- package/dist/temp-workspace-types.d.ts +40 -0
- package/dist/temp-workspace-types.d.ts.map +1 -0
- package/dist/temp-workspace-types.js +1 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/temp.js +1 -1
- package/dist/test-hooks.d.ts +7 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/text-atomic.d.ts.map +1 -1
- package/dist/text-atomic.js +3 -1
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +54 -8
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +20 -16
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +5 -0
- package/dist/windows-path-alias.d.ts +39 -0
- package/dist/windows-path-alias.d.ts.map +1 -0
- package/dist/windows-path-alias.js +153 -0
- package/docs/advanced.md +28 -1
- package/docs/archive.md +29 -2
- package/docs/atomic.md +27 -3
- package/docs/contributing.md +4 -1
- package/docs/copy.md +4 -1
- package/docs/durability.md +18 -2
- package/docs/errors.md +14 -6
- package/docs/file-store.md +30 -3
- package/docs/filename.md +21 -7
- package/docs/guest.md +5 -0
- package/docs/install-path.md +61 -15
- package/docs/install.md +12 -8
- package/docs/json-store.md +5 -1
- package/docs/json.md +11 -0
- package/docs/mutation-policy-proof.md +65 -0
- package/docs/native-helper.md +28 -9
- package/docs/native.md +46 -10
- package/docs/output.md +33 -11
- package/docs/path-prefix.md +64 -0
- package/docs/path-suffix-aliases.md +159 -0
- package/docs/path.md +12 -1
- package/docs/permissions.md +26 -1
- package/docs/private-file-store.md +14 -0
- package/docs/public-api.md +14 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +8 -1
- package/docs/root.md +21 -4
- package/docs/secret-file.md +3 -0
- package/docs/secure-file.md +21 -16
- package/docs/security-model.md +76 -9
- package/docs/sidecar-lock.md +28 -1
- package/docs/store.md +27 -2
- package/docs/temp.md +262 -34
- package/docs/test-hooks.md +14 -0
- package/docs/writing.md +38 -8
- package/package.json +10 -10
package/dist/file-hash.js
CHANGED
|
@@ -6,6 +6,7 @@ import { FsSafeError } from "./errors.js";
|
|
|
6
6
|
import { getNativeBinding } from "./native.js";
|
|
7
7
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
8
8
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
9
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
9
10
|
export async function hashFileHandle(handle, native = getNativeBinding(), { maxBytes = Infinity, signal } = {}) {
|
|
10
11
|
signal?.throwIfAborted();
|
|
11
12
|
const stat = fsSync.fstatSync(handle.fd);
|
|
@@ -38,7 +39,7 @@ export async function hashFileHandle(handle, native = getNativeBinding(), { maxB
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
const hash = createHash("sha256");
|
|
41
|
-
|
|
42
|
+
let buffer = Buffer.allocUnsafe(Math.min(256 * 1024, Math.max(1, stat.size + 1), maxBytes + 1));
|
|
42
43
|
let position = 0;
|
|
43
44
|
while (true) {
|
|
44
45
|
signal?.throwIfAborted();
|
|
@@ -53,6 +54,10 @@ export async function hashFileHandle(handle, native = getNativeBinding(), { maxB
|
|
|
53
54
|
}
|
|
54
55
|
hash.update(buffer.subarray(0, bytesRead));
|
|
55
56
|
position += bytesRead;
|
|
57
|
+
// A full small buffer can mean growth or a virtual file with an unhelpful size.
|
|
58
|
+
if (bytesRead === buffer.length && buffer.length < 64 * 1024) {
|
|
59
|
+
buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1));
|
|
60
|
+
}
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
function hashPathIdentity(filePath, afterOpen = false) {
|
|
@@ -84,6 +89,7 @@ function rethrowHashOpenError(error) {
|
|
|
84
89
|
throw error;
|
|
85
90
|
}
|
|
86
91
|
async function hashPath(filePath, options) {
|
|
92
|
+
assertNoWindowsPathAlias(filePath, "filesystem", "SHA-256 path uses a Windows filesystem namespace alias");
|
|
87
93
|
const before = await inspectFileIdentity(() => hashPathIdentity(filePath));
|
|
88
94
|
let handle;
|
|
89
95
|
try {
|
|
@@ -120,7 +126,7 @@ function hashDescriptorSync(fd, { maxBytes = Infinity, signal }) {
|
|
|
120
126
|
throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
|
|
121
127
|
}
|
|
122
128
|
const hash = createHash("sha256");
|
|
123
|
-
|
|
129
|
+
let buffer = Buffer.allocUnsafe(Math.min(256 * 1024, Math.max(1, stat.size + 1), maxBytes + 1));
|
|
124
130
|
let position = 0;
|
|
125
131
|
while (true) {
|
|
126
132
|
signal?.throwIfAborted();
|
|
@@ -135,9 +141,13 @@ function hashDescriptorSync(fd, { maxBytes = Infinity, signal }) {
|
|
|
135
141
|
}
|
|
136
142
|
hash.update(buffer.subarray(0, bytesRead));
|
|
137
143
|
position += bytesRead;
|
|
144
|
+
if (bytesRead === buffer.length && buffer.length < 64 * 1024) {
|
|
145
|
+
buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1));
|
|
146
|
+
}
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
function hashPathSync(filePath, options) {
|
|
150
|
+
assertNoWindowsPathAlias(filePath, "filesystem", "SHA-256 path uses a Windows filesystem namespace alias");
|
|
141
151
|
const before = inspectFileIdentitySync(() => hashPathIdentity(filePath));
|
|
142
152
|
let fd;
|
|
143
153
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAWnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAWnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAQjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AA8JF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CA4NpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
|
package/dist/file-lock-sync.js
CHANGED
|
@@ -5,8 +5,10 @@ import { readSidecarLockSnapshotSync, readSidecarLockRawSnapshotSync, parseSidec
|
|
|
5
5
|
import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, sidecarLockPayloadCreatedAtMs, validateSidecarLockStaleMs, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
6
6
|
import { getFsSafeLockConfig } from "./lock-config.js";
|
|
7
7
|
import { sleepSync } from "./timing.js";
|
|
8
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
8
9
|
import { realpathSync } from "./realpath.js";
|
|
9
10
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
11
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
10
12
|
const SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncSidecarLocks");
|
|
11
13
|
const SYNC_CLEANUP_REGISTERED_KEY = Symbol.for("fsSafe.syncSidecarLockCleanupRegistered");
|
|
12
14
|
const SYNC_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.syncSidecarLockCleanupHandler");
|
|
@@ -97,26 +99,36 @@ function canonicalLockParentSync(parent) {
|
|
|
97
99
|
}
|
|
98
100
|
function normalizeTargetPath(targetPath) {
|
|
99
101
|
const resolved = path.resolve(targetPath);
|
|
102
|
+
assertNoWindowsPathAlias(resolved);
|
|
100
103
|
fs.mkdirSync(recursiveMkdirPath(path.dirname(resolved)), { recursive: true });
|
|
104
|
+
let parent;
|
|
101
105
|
try {
|
|
102
|
-
|
|
106
|
+
parent = canonicalLockParentSync(path.dirname(resolved));
|
|
103
107
|
}
|
|
104
108
|
catch {
|
|
105
109
|
return resolved;
|
|
106
110
|
}
|
|
111
|
+
assertNoWindowsPathAlias(parent);
|
|
112
|
+
const normalized = path.join(parent, path.basename(resolved));
|
|
113
|
+
assertNoWindowsPathAlias(normalized);
|
|
114
|
+
return normalized;
|
|
107
115
|
}
|
|
108
116
|
function boundedLockPath(lockPath, lockRoot) {
|
|
109
117
|
const resolved = path.resolve(lockPath);
|
|
118
|
+
assertNoWindowsPathAlias(resolved);
|
|
110
119
|
if (!lockRoot)
|
|
111
120
|
return resolved;
|
|
112
121
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
113
122
|
const parent = path.dirname(resolved);
|
|
114
123
|
const parentReal = canonicalLockParentSync(parent);
|
|
124
|
+
assertNoWindowsPathAlias(parentReal);
|
|
115
125
|
const parentRelative = path.relative(lockRoot.rootReal, parentReal);
|
|
116
126
|
if (parentRelative === ".." || parentRelative.startsWith(`..${path.sep}`) || path.isAbsolute(parentRelative)) {
|
|
117
127
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
118
128
|
}
|
|
119
|
-
|
|
129
|
+
const bounded = path.join(parentReal, path.basename(resolved));
|
|
130
|
+
assertNoWindowsPathAlias(bounded);
|
|
131
|
+
return bounded;
|
|
120
132
|
}
|
|
121
133
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
122
134
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -145,8 +157,12 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
145
157
|
const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
|
|
146
158
|
validateSidecarLockStaleMs(staleMs);
|
|
147
159
|
validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
|
|
160
|
+
const explicitLockPath = options.lockPath;
|
|
161
|
+
assertNoWindowsPathAlias(targetPath);
|
|
162
|
+
if (explicitLockPath !== undefined)
|
|
163
|
+
assertNoWindowsPathAlias(explicitLockPath);
|
|
148
164
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
149
|
-
const lockPath = boundedLockPath(
|
|
165
|
+
const lockPath = boundedLockPath(explicitLockPath ?? `${normalizedTargetPath}.lock`, options.lockRoot);
|
|
150
166
|
const heldLocks = getSyncHeldLocks();
|
|
151
167
|
const held = heldLocks.get(normalizedTargetPath);
|
|
152
168
|
if (held &&
|
|
@@ -256,10 +272,24 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
256
272
|
}
|
|
257
273
|
catch (error) {
|
|
258
274
|
if (fd !== undefined) {
|
|
259
|
-
const failed = { payload: null
|
|
260
|
-
|
|
275
|
+
const failed = { payload: null };
|
|
276
|
+
try {
|
|
277
|
+
failed.stat = fs.fstatSync(fd);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// Missing identity leaves the sidecar in place, but must not skip close.
|
|
281
|
+
}
|
|
282
|
+
const failedFd = fd;
|
|
283
|
+
// A close error may still free the number; consume ownership before closing.
|
|
261
284
|
fd = undefined;
|
|
262
|
-
|
|
285
|
+
try {
|
|
286
|
+
fs.closeSync(failedFd);
|
|
287
|
+
if (failed.stat)
|
|
288
|
+
removeSidecarLockIfUnchangedSync(lockPath, failed);
|
|
289
|
+
}
|
|
290
|
+
catch (cleanupError) {
|
|
291
|
+
throw createSuppressedError(error, cleanupError, "file lock acquisition and cleanup both failed");
|
|
292
|
+
}
|
|
263
293
|
}
|
|
264
294
|
if (lockFileCreateDenied && retryLockFileDenial(error))
|
|
265
295
|
continue;
|
package/dist/file-lock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock.d.ts","sourceRoot":"","sources":["../src/file-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC9B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAC3D,MAAM,MAAM,qBAAqB,GAAG,wBAAwB,CAAC;AAE7D,MAAM,MAAM,sBAAsB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CACjF,yBAAyB,CAAC,QAAQ,CAAC,EACnC,YAAY,GAAG,SAAS,CACzB,GAAG;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AACrD,YAAY,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACxC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3B,QAAQ,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClD,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAAC;IACd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,KAAK,IAAI,IAAI,CAAC;IACd,WAAW,IAAI,iBAAiB,EAAE,CAAC;CACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"file-lock.d.ts","sourceRoot":"","sources":["../src/file-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC9B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAC3D,MAAM,MAAM,qBAAqB,GAAG,wBAAwB,CAAC;AAE7D,MAAM,MAAM,sBAAsB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CACjF,yBAAyB,CAAC,QAAQ,CAAC,EACnC,YAAY,GAAG,SAAS,CACzB,GAAG;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AACrD,YAAY,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACxC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3B,QAAQ,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClD,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAAC;IACd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,KAAK,IAAI,IAAI,CAAC;IACd,WAAW,IAAI,iBAAiB,EAAE,CAAC;CACpC,CAAC;AAiCF,wBAAsB,eAAe,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACxC,OAAO,CAAC,cAAc,CAAC,CAGzB;AAED,wBAAsB,YAAY,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAGZ;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAmBlE;AAED,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAEzF"}
|
package/dist/file-lock.js
CHANGED
|
@@ -5,13 +5,29 @@ function resolveFileLockManagerKey(targetPath, managerKey) {
|
|
|
5
5
|
return managerKey ?? `fs-safe.file-lock:${targetPath}`;
|
|
6
6
|
}
|
|
7
7
|
function withLockDefaults(options) {
|
|
8
|
+
const { managerKey: _managerKey, lockPath } = options;
|
|
9
|
+
const acquireOptions = {};
|
|
10
|
+
for (const key of Reflect.ownKeys(options)) {
|
|
11
|
+
// Node 22 object-rest can read excluded accessors; exclude before copying.
|
|
12
|
+
if (key === "managerKey" || key === "lockPath")
|
|
13
|
+
continue;
|
|
14
|
+
if (!Object.getOwnPropertyDescriptor(options, key)?.enumerable)
|
|
15
|
+
continue;
|
|
16
|
+
Object.defineProperty(acquireOptions, key, {
|
|
17
|
+
value: Reflect.get(options, key),
|
|
18
|
+
enumerable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
8
23
|
const defaults = getFsSafeLockConfig();
|
|
9
24
|
return {
|
|
10
|
-
...
|
|
11
|
-
retry:
|
|
12
|
-
staleMs:
|
|
13
|
-
staleRecovery:
|
|
14
|
-
timeoutMs:
|
|
25
|
+
...acquireOptions,
|
|
26
|
+
retry: acquireOptions.retry ?? defaults.retry,
|
|
27
|
+
staleMs: acquireOptions.staleMs ?? defaults.staleMs ?? 30_000,
|
|
28
|
+
staleRecovery: acquireOptions.staleRecovery ?? defaults.staleRecovery,
|
|
29
|
+
timeoutMs: acquireOptions.timeoutMs ?? defaults.timeoutMs,
|
|
30
|
+
...(lockPath === undefined ? {} : { lockPath }),
|
|
15
31
|
};
|
|
16
32
|
}
|
|
17
33
|
export async function acquireFileLock(targetPath, options) {
|
|
@@ -26,12 +42,16 @@ export function createFileLockManager(key) {
|
|
|
26
42
|
const manager = createSidecarLockManager(key);
|
|
27
43
|
return {
|
|
28
44
|
acquire: async (targetPath, options) => {
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
return await manager.acquire({
|
|
46
|
+
...withLockDefaults(options),
|
|
47
|
+
targetPath,
|
|
48
|
+
});
|
|
31
49
|
},
|
|
32
50
|
withLock: async (targetPath, options, fn) => {
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
return await manager.withLock({
|
|
52
|
+
...withLockDefaults(options),
|
|
53
|
+
targetPath,
|
|
54
|
+
}, fn);
|
|
35
55
|
},
|
|
36
56
|
drain: manager.drain,
|
|
37
57
|
reset: manager.reset,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type FailureKind = "identity" | "resolution" | `open:${string}` | `exclusive-create:${string}` | `unlinked:${string}` | `changed:${string}`;
|
|
1
|
+
type FailureKind = "identity" | "resolution" | `open:${string}` | `exclusive-create:${string}` | `unlinked:${string}` | `changed:${string}` | `stat-leaf-missing:${string}`;
|
|
2
2
|
export declare function recordFileObservationFailure(error: unknown, kind: FailureKind): void;
|
|
3
3
|
export declare function isFileObservationFailure(error: unknown, kind: FailureKind): boolean;
|
|
4
4
|
export declare function fileObservation(): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-observation.d.ts","sourceRoot":"","sources":["../src/file-observation.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,MAAM,EAAE,GAAG,oBAAoB,MAAM,EAAE,GAAG,YAAY,MAAM,EAAE,GAAG,WAAW,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"file-observation.d.ts","sourceRoot":"","sources":["../src/file-observation.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,MAAM,EAAE,GAAG,oBAAoB,MAAM,EAAE,GAAG,YAAY,MAAM,EAAE,GAAG,WAAW,MAAM,EAAE,GAAG,qBAAqB,MAAM,EAAE,CAAC;AAG5K,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAMpF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAEnF;AAGD,wBAAgB,eAAe;IAG3B,GAAG,CAAC,CAAC,aAAa,MAAM,CAAC,GAAG,CAAC;IAC7B,GAAG,QAAQ,OAAO,QAAQ,WAAW,GAAG,OAAO;EAElD"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
1
2
|
import type { Readable } from "node:stream";
|
|
2
|
-
import {
|
|
3
|
+
import type { SyncDirectoryGuard } from "./directory-guard.js";
|
|
3
4
|
import { type Root } from "./root.js";
|
|
4
5
|
import { prepareSecretFileWrite } from "./secret-file.js";
|
|
5
|
-
export type SyncParentGuard = SyncDirectoryGuard
|
|
6
|
+
export type SyncParentGuard = SyncDirectoryGuard & {
|
|
7
|
+
/** Exact authority retained separately from the legacy numeric durability receipt. */
|
|
8
|
+
readonly exactStat: BigIntStats;
|
|
9
|
+
};
|
|
6
10
|
export declare function ensureParentInRoot(scopedRoot: Root, relativePath: string, mode: number): Promise<void>;
|
|
7
11
|
export declare function openWritableStoreRoot(params: {
|
|
8
12
|
rootDir: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAO/D,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAK1D,MAAM,MAAM,eAAe,GAAG,kBAAkB,GAAG;IACjD,sFAAsF;IACtF,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;CACjC,CAAC;AAOF,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,IAAI,EAChB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,GACnD,OAAO,CAAC,IAAI,CAAC,CAUf;AAsCD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAwC1D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAMrE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,eAAe,CASlB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC;CAC1C,GAAG,eAAe,CAUlB"}
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import syncFs from "node:fs";
|
|
1
|
+
import syncFs, {} from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createByteLimitTransform } from "./bounded-read-stream.js";
|
|
5
5
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
6
6
|
import { pipeline } from "node:stream/promises";
|
|
7
|
-
import { assertSyncDirectoryGuard as assertDirectoryGuardSync, createSyncDirectoryGuard, } from "./directory-guard.js";
|
|
8
7
|
import { FsSafeError } from "./errors.js";
|
|
9
|
-
import {
|
|
10
|
-
import { isPathInside
|
|
8
|
+
import { assertSyncStoreDirectoryReceipt, ensureSyncStoreDirectory, } from "./file-store-sync-directory.js";
|
|
9
|
+
import { isPathInside } from "./path.js";
|
|
11
10
|
import { resolveOpenedFileRealPathForHandle, root } from "./root.js";
|
|
12
11
|
import { ensureTrailingSep } from "./root-context.js";
|
|
13
12
|
import { RootHandle } from "./root-impl.js";
|
|
14
13
|
import { prepareSecretFileWrite } from "./secret-file.js";
|
|
15
14
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
16
|
-
import { realpathSync } from "./realpath.js";
|
|
17
15
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
16
|
+
import { assertNoWindowsPathAlias, pathForWindowsFilesystem } from "./windows-path-alias.js";
|
|
18
17
|
function parentRelativePath(relativePath) {
|
|
19
18
|
const parent = path.posix.dirname(relativePath);
|
|
20
19
|
return parent === "." ? "" : parent;
|
|
@@ -28,6 +27,7 @@ export async function ensureParentInRoot(scopedRoot, relativePath, mode) {
|
|
|
28
27
|
await chmodDirectoryInRootBestEffort(scopedRoot, parent, mode).catch(() => undefined);
|
|
29
28
|
}
|
|
30
29
|
export async function openWritableStoreRoot(params) {
|
|
30
|
+
assertNoWindowsPathAlias(params.rootDir, "filesystem", "store root uses a Windows filesystem namespace alias");
|
|
31
31
|
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
32
32
|
await fs.mkdir(recursiveMkdirPath(params.rootDir), { recursive: true, mode: params.dirMode });
|
|
33
33
|
await fs.chmod(params.rootDir, params.dirMode).catch(() => undefined);
|
|
@@ -38,6 +38,7 @@ export async function openPrivateStoreLockRoot(params) {
|
|
|
38
38
|
// Bind to the admitted parent, never resolve a replacement into a fresh capability.
|
|
39
39
|
return new RootHandle({
|
|
40
40
|
rootDir: parentGuard.dir,
|
|
41
|
+
rootGuard: { dir: parentGuard.realPath, realPath: parentGuard.realPath, stat: parentGuard.stat },
|
|
41
42
|
rootReal: parentGuard.realPath,
|
|
42
43
|
rootWithSep: ensureTrailingSep(parentGuard.realPath),
|
|
43
44
|
rootIdentity: { dev: parentGuard.stat.dev, ino: parentGuard.stat.ino },
|
|
@@ -116,27 +117,15 @@ export async function writeStreamToTempSource(params) {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
export function assertSyncDirectoryGuard(guard) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
throw new FsSafeError("path-mismatch", "store directory changed during write", {
|
|
125
|
-
cause: error,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
throw error;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function chmodDirectorySyncBestEffort(dir, mode) {
|
|
132
|
-
try {
|
|
133
|
-
syncFs.chmodSync(dir, mode);
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
// Best-effort on platforms that do not enforce POSIX modes.
|
|
137
|
-
}
|
|
120
|
+
assertSyncStoreDirectoryReceipt({
|
|
121
|
+
dir: guard.dir,
|
|
122
|
+
realPath: guard.realPath,
|
|
123
|
+
exactStat: guard.exactStat,
|
|
124
|
+
});
|
|
138
125
|
}
|
|
139
126
|
export function ensureParentSync(params) {
|
|
127
|
+
assertNoWindowsPathAlias(params.rootDir, "filesystem", "store root uses a Windows filesystem namespace alias");
|
|
128
|
+
assertNoWindowsPathAlias(params.filePath, "filesystem", "store path uses a Windows filesystem namespace alias");
|
|
140
129
|
return ensureStoreDirectorySync({
|
|
141
130
|
rootDir: params.rootDir,
|
|
142
131
|
targetDir: path.dirname(path.resolve(params.filePath)),
|
|
@@ -145,47 +134,13 @@ export function ensureParentSync(params) {
|
|
|
145
134
|
});
|
|
146
135
|
}
|
|
147
136
|
export function ensureStoreDirectorySync(params) {
|
|
148
|
-
const
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const rootStat = syncFs.lstatSync(rootDir);
|
|
156
|
-
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
157
|
-
throw new FsSafeError("not-file", `${params.messagePrefix} root must be a directory: ${rootDir}`);
|
|
158
|
-
}
|
|
159
|
-
const rootReal = realpathSync(rootDir);
|
|
160
|
-
chmodDirectorySyncBestEffort(rootDir, params.mode);
|
|
161
|
-
let current = rootDir;
|
|
162
|
-
for (const segment of path.relative(rootDir, dir).split(path.sep).filter(Boolean)) {
|
|
163
|
-
current = path.join(current, segment);
|
|
164
|
-
try {
|
|
165
|
-
const stat = syncFs.lstatSync(current);
|
|
166
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
167
|
-
throw new FsSafeError("not-file", `${params.messagePrefix} directory component must be a directory: ${current}`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch (error) {
|
|
171
|
-
if (error.code !== "ENOENT") {
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
174
|
-
syncFs.mkdirSync(current, { mode: params.mode });
|
|
175
|
-
}
|
|
176
|
-
const currentRootStat = syncFs.lstatSync(rootDir);
|
|
177
|
-
const currentRootReal = realpathSync(rootDir);
|
|
178
|
-
const currentReal = realpathSync(current);
|
|
179
|
-
if (currentRootStat.isSymbolicLink() ||
|
|
180
|
-
!currentRootStat.isDirectory() ||
|
|
181
|
-
!sameFileIdentity(rootStat, currentRootStat) ||
|
|
182
|
-
currentRootReal !== rootReal ||
|
|
183
|
-
!isPathInside(rootReal, currentReal)) {
|
|
184
|
-
throw new FsSafeError("outside-workspace", `${params.messagePrefix} directory escapes root`);
|
|
185
|
-
}
|
|
186
|
-
chmodDirectorySyncBestEffort(current, params.mode);
|
|
187
|
-
}
|
|
188
|
-
const guard = createSyncDirectoryGuard(dir);
|
|
137
|
+
const receipt = ensureSyncStoreDirectory(params);
|
|
138
|
+
const guard = {
|
|
139
|
+
dir: receipt.dir,
|
|
140
|
+
realPath: receipt.realPath,
|
|
141
|
+
stat: syncFs.lstatSync(pathForWindowsFilesystem(receipt.dir)),
|
|
142
|
+
exactStat: receipt.exactStat,
|
|
143
|
+
};
|
|
189
144
|
assertSyncDirectoryGuard(guard);
|
|
190
145
|
return guard;
|
|
191
146
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store-copy-source.d.ts","sourceRoot":"","sources":["../src/file-store-copy-source.ts"],"names":[],"mappings":"AAMA,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BlB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import { assertFileStoreMaxBytes } from "./file-store-limit.js";
|
|
4
|
+
import { readRegularFile } from "./regular-file.js";
|
|
5
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
6
|
+
export async function readFileStoreCopySource(params) {
|
|
7
|
+
assertNoWindowsPathAlias(params.sourcePath, "filesystem", "source path uses a Windows filesystem namespace alias");
|
|
8
|
+
const sourceStat = fs.lstatSync(params.sourcePath);
|
|
9
|
+
if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
|
|
10
|
+
throw new FsSafeError("not-file", "source path is not a file");
|
|
11
|
+
}
|
|
12
|
+
assertFileStoreMaxBytes(sourceStat.size, params.maxBytes);
|
|
13
|
+
try {
|
|
14
|
+
return (await readRegularFile({ filePath: params.sourcePath, maxBytes: params.maxBytes }))
|
|
15
|
+
.buffer;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19
|
+
if (message.includes("regular file") || message.includes("not a regular file")) {
|
|
20
|
+
throw new FsSafeError("not-file", "source path is not a file", {
|
|
21
|
+
cause: error instanceof Error ? error : undefined,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (params.maxBytes !== undefined && message.includes(`exceeds ${params.maxBytes} bytes`)) {
|
|
25
|
+
throw new FsSafeError("too-large", `file exceeds maximum size of ${params.maxBytes} bytes`, {
|
|
26
|
+
cause: error instanceof Error ? error : undefined,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-path.d.ts","sourceRoot":"","sources":["../src/file-store-path.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAe/D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"file-store-path.d.ts","sourceRoot":"","sources":["../src/file-store-path.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAe/D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAY9E"}
|
package/dist/file-store-path.js
CHANGED
|
@@ -17,7 +17,10 @@ export function assertRelativePath(relativePath) {
|
|
|
17
17
|
}
|
|
18
18
|
export function resolveStorePath(rootDir, relativePath) {
|
|
19
19
|
const key = assertRelativePath(relativePath);
|
|
20
|
-
|
|
20
|
+
// FileStore constructors snapshot an absolute root before this helper runs.
|
|
21
|
+
// Do not re-resolve it: Node drops the trailing separator from an exact
|
|
22
|
+
// Windows namespace drive root such as `\\?\C:\`.
|
|
23
|
+
const root = path.isAbsolute(rootDir) ? rootDir : path.resolve(rootDir);
|
|
21
24
|
// The immutable key already passed segment validation; keep the containment check.
|
|
22
25
|
const target = path.resolve(root, key);
|
|
23
26
|
if (!isPathInside(root, target)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-prune.d.ts","sourceRoot":"","sources":["../src/file-store-prune.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,qBAAqB,CAAC;CAChC,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"file-store-prune.d.ts","sourceRoot":"","sources":["../src/file-store-prune.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,qBAAqB,CAAC;CAChC,GAAG,OAAO,CAAC,IAAI,CAAC,CA8FhB"}
|
package/dist/file-store-prune.js
CHANGED
|
@@ -76,7 +76,15 @@ export async function pruneExpiredStoreEntries(params) {
|
|
|
76
76
|
}
|
|
77
77
|
if (stat.isFile() && now - stat.mtimeMs > params.options.ttlMs) {
|
|
78
78
|
await assertRootGuard();
|
|
79
|
-
await scopedRoot.remove(relativePath
|
|
79
|
+
await scopedRoot.remove(relativePath, {
|
|
80
|
+
assertBeforeMutation: () => {
|
|
81
|
+
// Removal preparation can outlive the expiry observation above.
|
|
82
|
+
const current = fsSync.lstatSync(fullPath);
|
|
83
|
+
if (!current.isFile() || !(now - current.mtimeMs > params.options.ttlMs)) {
|
|
84
|
+
throw new FsSafeError("path-mismatch", "store entry is no longer an expired file");
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
}).catch(() => undefined);
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
if (!pruneEmptyDirs) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
type StoreMessagePrefix = "private store" | "store";
|
|
3
|
+
export type SyncStoreDirectoryReceipt = Readonly<{
|
|
4
|
+
dir: string;
|
|
5
|
+
realPath: string;
|
|
6
|
+
exactStat: BigIntStats;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function ensureSyncStoreDirectory(params: {
|
|
9
|
+
rootDir: string;
|
|
10
|
+
targetDir: string;
|
|
11
|
+
mode: number;
|
|
12
|
+
messagePrefix: StoreMessagePrefix;
|
|
13
|
+
}): SyncStoreDirectoryReceipt;
|
|
14
|
+
export declare function assertSyncStoreDirectoryReceipt(receipt: SyncStoreDirectoryReceipt): void;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=file-store-sync-directory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store-sync-directory.d.ts","sourceRoot":"","sources":["../src/file-store-sync-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAc/C,KAAK,kBAAkB,GAAG,eAAe,GAAG,OAAO,CAAC;AAOpD,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,CAAC;CACxB,CAAC,CAAC;AAkVH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,kBAAkB,CAAC;CACnC,GAAG,yBAAyB,CA4D5B;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,yBAAyB,GACjC,IAAI,CAsBN"}
|