@openclaw/fs-safe 0.6.0 → 0.7.1
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 +79 -0
- package/README.md +7 -0
- package/dist/archive-deadline.d.ts +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +44 -8
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +1 -0
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +3 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +26 -16
- package/dist/archive-limits.d.ts +11 -3
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +24 -0
- package/dist/archive-native.d.ts +3 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +24 -9
- package/dist/archive-policy.d.ts +2 -0
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +9 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +69 -41
- package/dist/archive-staging.d.ts +3 -0
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +91 -43
- package/dist/archive-tar-admission.d.ts +7 -0
- package/dist/archive-tar-admission.d.ts.map +1 -0
- package/dist/archive-tar-admission.js +43 -0
- package/dist/archive-tar-gnu.d.ts +2 -0
- package/dist/archive-tar-gnu.d.ts.map +1 -0
- package/dist/archive-tar-gnu.js +20 -0
- package/dist/archive-tar-header.d.ts +8 -0
- package/dist/archive-tar-header.d.ts.map +1 -0
- package/dist/archive-tar-header.js +47 -0
- package/dist/archive-tar-meta.d.ts +29 -1
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +104 -35
- package/dist/archive-tar-runtime.d.ts +3 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +3 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -2
- package/dist/archive-zip-names.js +2 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +38 -27
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +2 -4
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +3 -10
- package/dist/byte-budget.d.ts +5 -0
- package/dist/byte-budget.d.ts.map +1 -0
- package/dist/byte-budget.js +9 -0
- package/dist/directory-guard.d.ts +2 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +10 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +81 -30
- package/dist/file-observation.d.ts +9 -0
- package/dist/file-observation.d.ts.map +1 -0
- package/dist/file-observation.js +22 -0
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +9 -5
- package/dist/file-store-limit.d.ts +2 -0
- package/dist/file-store-limit.d.ts.map +1 -0
- package/dist/file-store-limit.js +8 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +38 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +26 -26
- package/dist/json-durable-queue-directory.d.ts +2 -0
- package/dist/json-durable-queue-directory.d.ts.map +1 -0
- package/dist/json-durable-queue-directory.js +20 -0
- package/dist/json-durable-queue-ownership.d.ts +14 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -0
- package/dist/json-durable-queue-ownership.js +168 -0
- package/dist/json-durable-queue-retirement.d.ts +9 -0
- package/dist/json-durable-queue-retirement.d.ts.map +1 -0
- package/dist/json-durable-queue-retirement.js +126 -0
- package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
- package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
- package/dist/json-durable-queue-transfer-lock.js +19 -0
- package/dist/json-durable-queue.d.ts +1 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +90 -57
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +32 -13
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +4 -2
- package/dist/native-binding.d.ts +12 -3
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +1 -1
- package/dist/opened-file-failure.d.ts +7 -0
- package/dist/opened-file-failure.d.ts.map +1 -0
- package/dist/opened-file-failure.js +41 -0
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +8 -2
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +11 -110
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +4 -2
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +27 -48
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +10 -10
- package/dist/private-temp-workspace.d.ts +3 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +81 -56
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -4
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +6 -4
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +136 -82
- package/dist/replace-file-copy-fallback.d.ts +3 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +21 -36
- package/dist/replace-file-copy-source.d.ts +21 -0
- package/dist/replace-file-copy-source.d.ts.map +1 -0
- package/dist/replace-file-copy-source.js +112 -0
- package/dist/replace-file-descriptor.d.ts +13 -3
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +32 -7
- package/dist/replace-file-rename-policy.d.ts +7 -0
- package/dist/replace-file-rename-policy.d.ts.map +1 -0
- package/dist/replace-file-rename-policy.js +30 -0
- package/dist/replace-file-temp-owner.d.ts +46 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -0
- package/dist/replace-file-temp-owner.js +346 -0
- package/dist/replace-file.d.ts +6 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +72 -58
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +113 -82
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +6 -3
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +6 -5
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +17 -13
- package/dist/sibling-staged-file.d.ts +15 -0
- package/dist/sibling-staged-file.d.ts.map +1 -0
- package/dist/sibling-staged-file.js +148 -0
- package/dist/sibling-temp.d.ts +3 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +30 -74
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +91 -37
- package/dist/sidecar-lock-handle.d.ts +6 -2
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +17 -3
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +29 -0
- package/dist/sidecar-lock-reclaim.d.ts +16 -5
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +60 -28
- package/dist/sidecar-lock-root.d.ts +3 -0
- package/dist/sidecar-lock-root.d.ts.map +1 -0
- package/dist/sidecar-lock-root.js +76 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +28 -16
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +4 -1
- package/dist/suppressed-error.d.ts +6 -0
- package/dist/suppressed-error.d.ts.map +1 -0
- package/dist/suppressed-error.js +15 -0
- package/dist/temp-cleanup.d.ts +2 -0
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +25 -10
- package/dist/temp-workspace-owner.d.ts +23 -0
- package/dist/temp-workspace-owner.d.ts.map +1 -0
- package/dist/temp-workspace-owner.js +320 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +5 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/docs/advanced.md +8 -1
- package/docs/archive.md +202 -26
- package/docs/atomic.md +11 -2
- package/docs/durability.md +1 -1
- package/docs/file-store.md +2 -0
- package/docs/install.md +8 -4
- package/docs/json.md +11 -4
- package/docs/native-helper.md +15 -3
- package/docs/native.md +33 -6
- package/docs/output.md +16 -3
- package/docs/public-api.md +6 -3
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +1 -1
- package/docs/regular-file.md +9 -2
- package/docs/root.md +2 -0
- package/docs/secret-file.md +5 -1
- package/docs/secure-file.md +4 -2
- package/docs/security-model.md +18 -0
- package/docs/sidecar-lock.md +60 -4
- package/docs/store.md +13 -0
- package/docs/temp.md +128 -14
- package/docs/writing.md +6 -0
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bounded-read.d.ts","sourceRoot":"","sources":["../src/bounded-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"bounded-read.d.ts","sourceRoot":"","sources":["../src/bounded-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAmDnD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAIjB;AAcD,oFAAoF;AACpF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI7F;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAalF"}
|
package/dist/bounded-read.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
2
3
|
import { FsSafeError } from "./errors.js";
|
|
3
4
|
const READ_CHUNK_BYTES = 64 * 1024;
|
|
4
|
-
function assertMaxBytes(maxBytes) {
|
|
5
|
-
if (maxBytes === Number.POSITIVE_INFINITY) {
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
9
|
-
throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
5
|
function createScratchBuffer(maxBytes) {
|
|
13
6
|
const initialReadBytes = Number.isFinite(maxBytes)
|
|
14
7
|
? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
|
|
@@ -29,7 +22,7 @@ function appendChunk(params) {
|
|
|
29
22
|
return total;
|
|
30
23
|
}
|
|
31
24
|
async function readBoundedAsync(maxBytes, readChunk) {
|
|
32
|
-
|
|
25
|
+
normalizeMaxBytes(maxBytes);
|
|
33
26
|
const chunks = [];
|
|
34
27
|
const scratch = createScratchBuffer(maxBytes);
|
|
35
28
|
let total = 0;
|
|
@@ -71,7 +64,7 @@ export async function readFileDescriptorBounded(fd, maxBytes) {
|
|
|
71
64
|
}
|
|
72
65
|
/** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
73
66
|
export function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
74
|
-
|
|
67
|
+
normalizeMaxBytes(maxBytes);
|
|
75
68
|
const chunks = [];
|
|
76
69
|
const scratch = createScratchBuffer(maxBytes);
|
|
77
70
|
let total = 0;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"byte-budget.d.ts","sourceRoot":"","sources":["../src/byte-budget.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,MAAM,GAAG,SAAS,CASpB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function normalizeMaxBytes(value, options = {}) {
|
|
2
|
+
const selected = value === undefined ? options.defaultValue : value;
|
|
3
|
+
if (selected === undefined || selected === Number.POSITIVE_INFINITY)
|
|
4
|
+
return selected;
|
|
5
|
+
if (!Number.isSafeInteger(selected) || selected < 0) {
|
|
6
|
+
throw new RangeError(`${options.label ?? "maxBytes"} must be a non-negative safe integer or Infinity`);
|
|
7
|
+
}
|
|
8
|
+
return selected;
|
|
9
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Stats } from "node:fs";
|
|
1
|
+
import type { BigIntStats, Stats } from "node:fs";
|
|
2
2
|
export type AsyncDirectoryGuard = {
|
|
3
3
|
dir: string;
|
|
4
4
|
realPath: string;
|
|
@@ -15,4 +15,5 @@ export declare function createSyncDirectoryGuard(dir: string): SyncDirectoryGuar
|
|
|
15
15
|
export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard): void;
|
|
16
16
|
export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string): Promise<AsyncDirectoryGuard>;
|
|
17
17
|
export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
|
|
18
|
+
export declare function inspectDirectoryIdentity(dir: string, expected?: BigIntStats): Promise<BigIntStats>;
|
|
18
19
|
//# sourceMappingURL=directory-guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUlD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAMzF;AAED,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzF;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAMxE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAQxE;AAED,wBAAsB,mCAAmC,CACvD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAc9B;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB;AAGD,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAMxG"}
|
package/dist/directory-guard.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
5
|
import { sameFileIdentity } from "./file-identity.js";
|
|
6
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
6
7
|
import { isNotFoundPathError } from "./path.js";
|
|
7
8
|
import { directoryComponentNotDirectoryError } from "./root-errors.js";
|
|
8
9
|
export async function createAsyncDirectoryGuard(dir) {
|
|
@@ -69,3 +70,12 @@ export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
|
|
|
69
70
|
}
|
|
70
71
|
return createSyncDirectoryGuard(root);
|
|
71
72
|
}
|
|
73
|
+
// Recovery receipts must retain every identity bit, including on Windows.
|
|
74
|
+
export async function inspectDirectoryIdentity(dir, expected) {
|
|
75
|
+
return await inspectFileIdentity(async () => {
|
|
76
|
+
const stat = await fs.lstat(dir, { bigint: true });
|
|
77
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
78
|
+
throw directoryComponentNotDirectoryError();
|
|
79
|
+
return stat;
|
|
80
|
+
}, expected);
|
|
81
|
+
}
|
|
@@ -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,
|
|
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;AASnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAIjC,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;AAoJF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CAwMpB;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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
|
-
import { readSidecarLockSnapshotSync, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
5
|
-
import { computeSidecarLockDelayMs, sidecarLockPayloadCreatedAtMs, } from "./sidecar-lock-policy.js";
|
|
4
|
+
import { readSidecarLockSnapshotSync, readSidecarLockRawSnapshotSync, parseSidecarLockSnapshot, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
5
|
+
import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, sidecarLockPayloadCreatedAtMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
6
6
|
import { getFsSafeLockConfig } from "./lock-config.js";
|
|
7
7
|
import { sleepSync } from "./timing.js";
|
|
8
8
|
const SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncSidecarLocks");
|
|
@@ -23,7 +23,8 @@ function releaseAllSyncHeldLocks() {
|
|
|
23
23
|
held.timer = undefined;
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
|
-
|
|
26
|
+
if (held.fd !== undefined)
|
|
27
|
+
fs.closeSync(held.fd);
|
|
27
28
|
}
|
|
28
29
|
catch {
|
|
29
30
|
// Best-effort process-exit cleanup.
|
|
@@ -53,16 +54,23 @@ function releaseSyncHeldLock(held) {
|
|
|
53
54
|
const heldLocks = getSyncHeldLocks();
|
|
54
55
|
if (heldLocks.get(held.normalizedTargetPath) !== held)
|
|
55
56
|
return false;
|
|
56
|
-
held.refCount
|
|
57
|
-
|
|
57
|
+
if (held.refCount > 1) {
|
|
58
|
+
held.refCount -= 1;
|
|
58
59
|
return false;
|
|
59
|
-
|
|
60
|
+
}
|
|
61
|
+
// Keep the final reference and cleanup receipt until deletion succeeds.
|
|
60
62
|
if (held.timer) {
|
|
61
63
|
clearInterval(held.timer);
|
|
62
64
|
held.timer = undefined;
|
|
63
65
|
}
|
|
64
|
-
|
|
66
|
+
if (held.fd !== undefined) {
|
|
67
|
+
const fd = held.fd;
|
|
68
|
+
// A close error may still free the number; consume ownership before closing.
|
|
69
|
+
held.fd = undefined;
|
|
70
|
+
fs.closeSync(fd);
|
|
71
|
+
}
|
|
65
72
|
removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
|
|
73
|
+
heldLocks.delete(held.normalizedTargetPath);
|
|
66
74
|
return true;
|
|
67
75
|
}
|
|
68
76
|
function createSyncHeldLockHandle(held) {
|
|
@@ -70,8 +78,8 @@ function createSyncHeldLockHandle(held) {
|
|
|
70
78
|
const release = () => {
|
|
71
79
|
if (released)
|
|
72
80
|
return;
|
|
73
|
-
released = true;
|
|
74
81
|
releaseSyncHeldLock(held);
|
|
82
|
+
released = true;
|
|
75
83
|
};
|
|
76
84
|
return {
|
|
77
85
|
lockPath: held.lockPath,
|
|
@@ -81,11 +89,15 @@ function createSyncHeldLockHandle(held) {
|
|
|
81
89
|
[Symbol.dispose]: release,
|
|
82
90
|
};
|
|
83
91
|
}
|
|
92
|
+
function canonicalLockParentSync(parent) {
|
|
93
|
+
// Match Root's async realpath, including Windows short-name expansion.
|
|
94
|
+
return process.platform === "win32" ? fs.realpathSync.native(parent) : fs.realpathSync(parent);
|
|
95
|
+
}
|
|
84
96
|
function normalizeTargetPath(targetPath) {
|
|
85
97
|
const resolved = path.resolve(targetPath);
|
|
86
98
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
87
99
|
try {
|
|
88
|
-
return path.join(
|
|
100
|
+
return path.join(canonicalLockParentSync(path.dirname(resolved)), path.basename(resolved));
|
|
89
101
|
}
|
|
90
102
|
catch {
|
|
91
103
|
return resolved;
|
|
@@ -97,23 +109,19 @@ function boundedLockPath(lockPath, lockRoot) {
|
|
|
97
109
|
return resolved;
|
|
98
110
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
99
111
|
const parent = path.dirname(resolved);
|
|
100
|
-
const parentReal =
|
|
112
|
+
const parentReal = canonicalLockParentSync(parent);
|
|
101
113
|
const parentRelative = path.relative(lockRoot.rootReal, parentReal);
|
|
102
114
|
if (parentRelative === ".." || parentRelative.startsWith(`..${path.sep}`) || path.isAbsolute(parentRelative)) {
|
|
103
115
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
104
116
|
}
|
|
105
117
|
return path.join(parentReal, path.basename(resolved));
|
|
106
118
|
}
|
|
107
|
-
function defaultShouldReclaim(
|
|
108
|
-
const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
|
|
119
|
+
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
120
|
+
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
109
121
|
if (createdAtMs !== null)
|
|
110
122
|
return nowMs - createdAtMs > staleMs;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
123
|
+
// A cooperating holder can unlink after the snapshot; use its observed age.
|
|
124
|
+
return !snapshot.stat || nowMs - snapshot.stat.mtimeMs > staleMs;
|
|
117
125
|
}
|
|
118
126
|
function reclaimGuardExists(reclaimGuardPath) {
|
|
119
127
|
try {
|
|
@@ -127,6 +135,11 @@ function reclaimGuardExists(reclaimGuardPath) {
|
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
export function acquireFileLockSync(targetPath, options) {
|
|
138
|
+
const defaults = getFsSafeLockConfig();
|
|
139
|
+
const retry = options.retry ?? defaults.retry ?? {};
|
|
140
|
+
const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
|
|
141
|
+
validateSidecarLockRetryOptions(retry);
|
|
142
|
+
validateSidecarLockTimeoutMs(timeoutMs);
|
|
130
143
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
131
144
|
const lockPath = boundedLockPath(options.lockPath ?? `${normalizedTargetPath}.lock`, options.lockRoot);
|
|
132
145
|
const heldLocks = getSyncHeldLocks();
|
|
@@ -140,13 +153,11 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
140
153
|
}
|
|
141
154
|
// Process defaults fill the same fields here as withLockDefaults() fills for
|
|
142
155
|
// the asynchronous manager, so both acquirers honor configureFsSafeLocks().
|
|
143
|
-
const defaults = getFsSafeLockConfig();
|
|
144
156
|
const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
|
|
145
|
-
const retry = options.retry ?? defaults.retry ?? {};
|
|
146
|
-
const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
|
|
147
157
|
const staleRecovery = options.staleRecovery ?? defaults.staleRecovery;
|
|
148
158
|
const startedAt = Date.now();
|
|
149
159
|
let attempt = 0;
|
|
160
|
+
let transientDenials = 0;
|
|
150
161
|
const reclaimGuardPath = `${lockPath}.reclaim`;
|
|
151
162
|
const waitForRetry = () => {
|
|
152
163
|
const elapsed = Date.now() - startedAt;
|
|
@@ -158,22 +169,47 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
158
169
|
normalizedTargetPath,
|
|
159
170
|
});
|
|
160
171
|
}
|
|
161
|
-
|
|
172
|
+
const remaining = timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY
|
|
173
|
+
? Number.POSITIVE_INFINITY
|
|
174
|
+
: Math.max(0, timeoutMs - elapsed);
|
|
175
|
+
sleepSync(Math.min(computeSidecarLockDelayMs(retry, attempt), remaining));
|
|
162
176
|
attempt += 1;
|
|
163
177
|
};
|
|
178
|
+
const retryLockFileDenial = (error) => {
|
|
179
|
+
if (!isTransientLockFileDenial(error, lockPath) ||
|
|
180
|
+
++transientDenials > maxTransientLockDenials)
|
|
181
|
+
return false;
|
|
182
|
+
try {
|
|
183
|
+
waitForRetry();
|
|
184
|
+
}
|
|
185
|
+
catch (waitError) {
|
|
186
|
+
// Preserve the filesystem diagnosis when the caller's budget runs out.
|
|
187
|
+
if (waitError.code === "file_lock_timeout")
|
|
188
|
+
throw error;
|
|
189
|
+
throw waitError;
|
|
190
|
+
}
|
|
191
|
+
return true;
|
|
192
|
+
};
|
|
164
193
|
while (true) {
|
|
165
194
|
if (reclaimGuardExists(reclaimGuardPath)) {
|
|
166
195
|
waitForRetry();
|
|
167
196
|
continue;
|
|
168
197
|
}
|
|
169
198
|
let fd;
|
|
199
|
+
const payload = options.payload();
|
|
200
|
+
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
201
|
+
let lockFileCreateDenied = false;
|
|
170
202
|
try {
|
|
171
|
-
const payload = options.payload();
|
|
172
|
-
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
173
203
|
const noFollow = process.platform !== "win32" && typeof fs.constants.O_NOFOLLOW === "number"
|
|
174
204
|
? fs.constants.O_NOFOLLOW
|
|
175
205
|
: 0;
|
|
176
|
-
|
|
206
|
+
try {
|
|
207
|
+
fd = fs.openSync(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow, 0o600);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
177
213
|
fs.writeFileSync(fd, raw, "utf8");
|
|
178
214
|
fs.fsyncSync(fd);
|
|
179
215
|
const snapshot = {
|
|
@@ -221,17 +257,32 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
221
257
|
fd = undefined;
|
|
222
258
|
removeSidecarLockIfUnchangedSync(lockPath, failed);
|
|
223
259
|
}
|
|
260
|
+
if (lockFileCreateDenied && retryLockFileDenial(error))
|
|
261
|
+
continue;
|
|
224
262
|
if (error.code !== "EEXIST")
|
|
225
263
|
throw error;
|
|
226
264
|
if (heldLocks.has(normalizedTargetPath)) {
|
|
227
265
|
waitForRetry();
|
|
228
266
|
continue;
|
|
229
267
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
268
|
+
let rawSnapshot;
|
|
269
|
+
let lockFileOpenDenied = false;
|
|
270
|
+
try {
|
|
271
|
+
rawSnapshot = readSidecarLockRawSnapshotSync(lockPath, {
|
|
272
|
+
rejectNonFile: true,
|
|
273
|
+
onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
catch (readError) {
|
|
277
|
+
if (lockFileOpenDenied && retryLockFileDenial(readError))
|
|
278
|
+
continue;
|
|
279
|
+
throw readError;
|
|
280
|
+
}
|
|
281
|
+
const snapshot = parseSidecarLockSnapshot(rawSnapshot, options.parsePayload);
|
|
282
|
+
if (!snapshot) {
|
|
283
|
+
waitForRetry();
|
|
234
284
|
continue;
|
|
285
|
+
}
|
|
235
286
|
const nowMs = Date.now();
|
|
236
287
|
const reclaim = options.shouldReclaim
|
|
237
288
|
? options.shouldReclaim({
|
|
@@ -242,7 +293,7 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
242
293
|
nowMs,
|
|
243
294
|
heldByThisProcess: false,
|
|
244
295
|
})
|
|
245
|
-
: defaultShouldReclaim(snapshot
|
|
296
|
+
: defaultShouldReclaim(snapshot, staleMs, nowMs);
|
|
246
297
|
if (reclaim) {
|
|
247
298
|
if (staleRecovery === "remove-if-unchanged" &&
|
|
248
299
|
snapshot.raw !== undefined &&
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type FailureKind = "identity" | "resolution" | `open:${string}` | `unlinked:${string}` | `changed:${string}`;
|
|
2
|
+
export declare function recordFileObservationFailure(error: unknown, kind: FailureKind): void;
|
|
3
|
+
export declare function isFileObservationFailure(error: unknown, kind: FailureKind): boolean;
|
|
4
|
+
export declare function fileObservation(): {
|
|
5
|
+
run<T>(operation: () => T): T;
|
|
6
|
+
has(error: unknown, kind: FailureKind): boolean;
|
|
7
|
+
};
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=file-observation.d.ts.map
|
|
@@ -0,0 +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,YAAY,MAAM,EAAE,GAAG,WAAW,MAAM,EAAE,CAAC;AAG7G,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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
const active = new AsyncLocalStorage();
|
|
3
|
+
export function recordFileObservationFailure(error, kind) {
|
|
4
|
+
const failures = active.getStore();
|
|
5
|
+
if (!failures)
|
|
6
|
+
return;
|
|
7
|
+
let kinds = failures.get(error);
|
|
8
|
+
if (!kinds)
|
|
9
|
+
failures.set(error, kinds = new Set());
|
|
10
|
+
kinds.add(kind);
|
|
11
|
+
}
|
|
12
|
+
export function isFileObservationFailure(error, kind) {
|
|
13
|
+
return active.getStore()?.get(error)?.has(kind) === true;
|
|
14
|
+
}
|
|
15
|
+
// Each Root observation owns its receipts, including nested and concurrent reads.
|
|
16
|
+
export function fileObservation() {
|
|
17
|
+
const failures = new Map();
|
|
18
|
+
return {
|
|
19
|
+
run(operation) { return active.run(failures, operation); },
|
|
20
|
+
has(error, kind) { return failures.get(error)?.has(kind) === true; },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAOjD,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,CAKhB;AAgDD,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,CAWrE;AAUD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,eAAe,CAOlB;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,CAyDlB"}
|
|
@@ -2,6 +2,7 @@ import syncFs from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Transform } from "node:stream";
|
|
5
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
5
6
|
import { pipeline } from "node:stream/promises";
|
|
6
7
|
import { assertSyncDirectoryGuard as assertDirectoryGuardSync, createSyncDirectoryGuard, } from "./directory-guard.js";
|
|
7
8
|
import { FsSafeError } from "./errors.js";
|
|
@@ -22,9 +23,10 @@ export async function ensureParentInRoot(scopedRoot, relativePath, mode) {
|
|
|
22
23
|
await chmodDirectoryInRootBestEffort(scopedRoot, parent, mode).catch(() => undefined);
|
|
23
24
|
}
|
|
24
25
|
export async function openWritableStoreRoot(params) {
|
|
26
|
+
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
25
27
|
await fs.mkdir(params.rootDir, { recursive: true, mode: params.dirMode });
|
|
26
28
|
await fs.chmod(params.rootDir, params.dirMode).catch(() => undefined);
|
|
27
|
-
return await root(params.rootDir, { hardlinks: "reject", maxBytes
|
|
29
|
+
return await root(params.rootDir, { hardlinks: "reject", maxBytes });
|
|
28
30
|
}
|
|
29
31
|
async function chmodDirectoryInRootBestEffort(scopedRoot, relativePath, mode) {
|
|
30
32
|
const dirPath = await scopedRoot.resolve(relativePath);
|
|
@@ -49,7 +51,8 @@ async function chmodDirectoryInRootBestEffort(scopedRoot, relativePath, mode) {
|
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
function createMaxBytesTransform(maxBytes) {
|
|
52
|
-
|
|
54
|
+
const limit = normalizeMaxBytes(maxBytes);
|
|
55
|
+
if (limit === undefined) {
|
|
53
56
|
return undefined;
|
|
54
57
|
}
|
|
55
58
|
let total = 0;
|
|
@@ -57,8 +60,8 @@ function createMaxBytesTransform(maxBytes) {
|
|
|
57
60
|
transform(chunk, _encoding, callback) {
|
|
58
61
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
59
62
|
total += buffer.byteLength;
|
|
60
|
-
if (total >
|
|
61
|
-
callback(new FsSafeError("too-large", `file exceeds maximum size of ${
|
|
63
|
+
if (total > limit) {
|
|
64
|
+
callback(new FsSafeError("too-large", `file exceeds maximum size of ${limit} bytes`));
|
|
62
65
|
return;
|
|
63
66
|
}
|
|
64
67
|
callback(null, buffer);
|
|
@@ -66,6 +69,7 @@ function createMaxBytesTransform(maxBytes) {
|
|
|
66
69
|
});
|
|
67
70
|
}
|
|
68
71
|
export async function writeStreamToTempSource(params) {
|
|
72
|
+
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
69
73
|
const tempRoot = resolveSecureTempRoot({
|
|
70
74
|
fallbackPrefix: "fs-safe-file-store",
|
|
71
75
|
unsafeFallbackLabel: "file store temp dir",
|
|
@@ -81,7 +85,7 @@ export async function writeStreamToTempSource(params) {
|
|
|
81
85
|
writable.once("close", () => {
|
|
82
86
|
handleClosedByStream = true;
|
|
83
87
|
});
|
|
84
|
-
const limiter = createMaxBytesTransform(
|
|
88
|
+
const limiter = createMaxBytesTransform(maxBytes);
|
|
85
89
|
if (limiter) {
|
|
86
90
|
await pipeline(params.stream, limiter, writable);
|
|
87
91
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store-limit.d.ts","sourceRoot":"","sources":["../src/file-store-limit.ts"],"names":[],"mappings":"AAGA,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAK7E"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
export function assertFileStoreMaxBytes(size, maxBytes) {
|
|
4
|
+
const limit = normalizeMaxBytes(maxBytes);
|
|
5
|
+
if (limit !== undefined && size > limit) {
|
|
6
|
+
throw new FsSafeError("too-large", `file exceeds maximum size of ${limit} bytes`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-sync-write.d.ts","sourceRoot":"","sources":["../src/file-store-sync-write.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-store-sync-write.d.ts","sourceRoot":"","sources":["../src/file-store-sync-write.ts"],"names":[],"mappings":"AAwBA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CA6GT"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { syncDirectorySync } from "./directory-durability.js";
|
|
4
5
|
import { FsSafeError } from "./errors.js";
|
|
5
6
|
import { sameFileIdentity } from "./file-identity.js";
|
|
6
7
|
import { assertSyncDirectoryGuard, ensureParentSync, ensureStoreDirectorySync, } from "./file-store-boundary.js";
|
|
@@ -48,15 +49,35 @@ export function writeFileSyncAtomic(params) {
|
|
|
48
49
|
if (parentGuard) {
|
|
49
50
|
assertSyncDirectoryGuard(parentGuard);
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
const tempStat = (() => {
|
|
53
|
+
const descriptor = fs.openSync(tempPath, "wx", params.mode);
|
|
54
|
+
tempExists = true;
|
|
55
|
+
try {
|
|
56
|
+
fs.writeFileSync(descriptor, params.content);
|
|
57
|
+
try {
|
|
58
|
+
fs.fchmodSync(descriptor, params.mode);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Best-effort on platforms that do not enforce POSIX modes.
|
|
62
|
+
}
|
|
63
|
+
const opened = fs.fstatSync(descriptor);
|
|
64
|
+
if (!opened.isFile() || opened.nlink > 1) {
|
|
65
|
+
throw new FsSafeError("path-mismatch", "store temp is not an owned regular file");
|
|
66
|
+
}
|
|
67
|
+
fs.fsyncSync(descriptor);
|
|
68
|
+
return opened;
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
fs.closeSync(descriptor);
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
74
|
+
const tempPathStat = fs.lstatSync(tempPath);
|
|
75
|
+
if (tempPathStat.isSymbolicLink() ||
|
|
76
|
+
!tempPathStat.isFile() ||
|
|
77
|
+
tempPathStat.nlink > 1 ||
|
|
78
|
+
!sameFileIdentity(tempPathStat, tempStat)) {
|
|
79
|
+
throw new FsSafeError("path-mismatch", "store temp changed before publication");
|
|
58
80
|
}
|
|
59
|
-
const tempStat = fs.lstatSync(tempPath);
|
|
60
81
|
if (parentGuard) {
|
|
61
82
|
assertSyncDirectoryGuard(parentGuard);
|
|
62
83
|
}
|
|
@@ -82,6 +103,15 @@ export function writeFileSyncAtomic(params) {
|
|
|
82
103
|
cause: error instanceof Error ? error : undefined,
|
|
83
104
|
});
|
|
84
105
|
}
|
|
106
|
+
if (parentGuard) {
|
|
107
|
+
assertSyncDirectoryGuard(parentGuard);
|
|
108
|
+
syncDirectorySync({
|
|
109
|
+
path: parentGuard.dir,
|
|
110
|
+
realPath: parentGuard.realPath,
|
|
111
|
+
identity: parentGuard.stat,
|
|
112
|
+
}, { label: "store parent" });
|
|
113
|
+
assertSyncDirectoryGuard(parentGuard);
|
|
114
|
+
}
|
|
85
115
|
return filePath;
|
|
86
116
|
}
|
|
87
117
|
finally {
|
package/dist/file-store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAK5C,OAAO,EAA4B,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC7F,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAOnE,OAAO,EAAmB,KAAK,oBAAoB,EAAE,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAItG,OAAO,EAAQ,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAOpG,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG;IAAE,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,CAAC;AAEnF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,CACH,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,WAAW,CACT,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,EAChB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CACJ,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3E,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3E,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,QAAQ,CACN,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/F,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxF,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACrB,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,SAAS,CACP,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,SAAS,CACP,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,qBAAqB,GAAG;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAC9D,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACtF,YAAY,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,GAAG,IAAI,CAAC;IACvF,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IAC/F,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC;IAChG,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC;IACpG,SAAS,CACP,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,qBAAqB,GAAG;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAC9D,MAAM,CAAC;CACX,CAAC;AAuHF,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAuM9D;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAsEtE"}
|