@openclaw/fs-safe 0.8.6 → 0.10.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 +93 -0
- package/README.md +17 -0
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-crc32.d.ts.map +1 -1
- package/dist/archive-crc32.js +6 -1
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +3 -4
- package/dist/archive-durability.d.ts +24 -0
- package/dist/archive-durability.d.ts.map +1 -0
- package/dist/archive-durability.js +180 -0
- package/dist/archive-gzip-tail.d.ts +2 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +22 -4
- package/dist/archive-input.js +4 -4
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +3 -2
- package/dist/archive-merge.d.ts +1 -0
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +42 -9
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +4 -2
- package/dist/archive-options.d.ts +2 -0
- package/dist/archive-options.d.ts.map +1 -1
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +72 -70
- package/dist/archive-staging.d.ts +1 -1
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +24 -19
- package/dist/archive-tar-stream.d.ts +11 -4
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +20 -8
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +2 -1
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/bounded-read.d.ts +7 -0
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +73 -43
- package/dist/clone-metadata.d.ts +19 -0
- package/dist/clone-metadata.d.ts.map +1 -0
- package/dist/clone-metadata.js +32 -0
- package/dist/copy-file-input.d.ts +23 -0
- package/dist/copy-file-input.d.ts.map +1 -0
- package/dist/copy-file-input.js +91 -0
- package/dist/copy-policy.d.ts +3 -0
- package/dist/copy-policy.d.ts.map +1 -0
- package/dist/copy-policy.js +8 -0
- package/dist/copy-publication.d.ts +15 -0
- package/dist/copy-publication.d.ts.map +1 -0
- package/dist/copy-publication.js +30 -0
- package/dist/copy-tree-portable.d.ts +9 -0
- package/dist/copy-tree-portable.d.ts.map +1 -0
- package/dist/copy-tree-portable.js +191 -0
- package/dist/copy.d.ts +16 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +123 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +4 -4
- package/dist/durability.d.ts +1 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/error-detail.d.ts.map +1 -1
- package/dist/error-detail.js +4 -1
- package/dist/file-hash.d.ts +6 -2
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +49 -12
- package/dist/file-store-sync-write.d.ts +1 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +9 -6
- package/dist/file-store.d.ts +4 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +10 -1
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +2 -12
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +1 -2
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +1 -0
- package/dist/guarded-mutation.d.ts +2 -0
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/guarded-mutation.js +9 -5
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +9 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +9 -13
- package/dist/json-document-store.d.ts +3 -0
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +1 -0
- package/dist/json-durable-queue-directory.js +3 -3
- package/dist/json-durable-queue-ownership.js +3 -2
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +31 -27
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +22 -24
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +4 -3
- package/dist/move-path-stage.d.ts +8 -0
- package/dist/move-path-stage.d.ts.map +1 -0
- package/dist/move-path-stage.js +55 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +55 -47
- package/dist/mutation-authority.d.ts +8 -0
- package/dist/mutation-authority.d.ts.map +1 -0
- package/dist/mutation-authority.js +36 -0
- package/dist/native-binding.d.ts +24 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +11 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +18 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +1 -0
- package/dist/native-staged-file.d.ts +3 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +35 -8
- package/dist/opened-file-failure.d.ts +1 -1
- package/dist/opened-file-failure.d.ts.map +1 -1
- package/dist/opened-file-failure.js +3 -2
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +3 -1
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +38 -48
- package/dist/permissions.js +3 -3
- package/dist/pinned-operation.js +1 -1
- package/dist/pinned-write.d.ts +5 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +50 -30
- package/dist/positional-read.d.ts +9 -0
- package/dist/positional-read.d.ts.map +1 -0
- package/dist/positional-read.js +36 -0
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +9 -3
- package/dist/publish-copy-stage.d.ts +13 -0
- package/dist/publish-copy-stage.d.ts.map +1 -0
- package/dist/publish-copy-stage.js +47 -0
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +26 -25
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +30 -17
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -5
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +6 -2
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +8 -3
- package/dist/root-context.d.ts +3 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +51 -26
- package/dist/root-directory-list.d.ts +24 -0
- package/dist/root-directory-list.d.ts.map +1 -0
- package/dist/root-directory-list.js +201 -0
- package/dist/root-errors.d.ts +1 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +3 -0
- package/dist/root-file.d.ts +2 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +12 -4
- package/dist/root-impl.d.ts +6 -50
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +342 -208
- package/dist/root-options.d.ts +66 -0
- package/dist/root-options.d.ts.map +1 -0
- package/dist/root-options.js +18 -0
- package/dist/root-path-existing.d.ts +5 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +56 -1
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +74 -162
- package/dist/root-symlink-policy.d.ts +13 -0
- package/dist/root-symlink-policy.d.ts.map +1 -0
- package/dist/root-symlink-policy.js +34 -0
- package/dist/root-walk.d.ts +5 -4
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +99 -50
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +1 -4
- package/dist/root.d.ts +3 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts +1 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secret-read-async.js +5 -5
- package/dist/secure-file.d.ts +1 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +23 -13
- package/dist/sibling-staged-file.js +7 -7
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +16 -7
- package/dist/sidecar-lock-acquire.d.ts +1 -1
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +6 -4
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +5 -3
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +16 -4
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -1
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +3 -2
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +9 -4
- package/dist/timing.d.ts +1 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +25 -6
- package/dist/trash.js +2 -2
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +7 -27
- package/dist/windows-owner.d.ts +9 -12
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +24 -58
- package/dist/write-file-handle.d.ts +6 -0
- package/dist/write-file-handle.d.ts.map +1 -0
- package/dist/write-file-handle.js +25 -0
- package/dist/write-open-flags.d.ts.map +1 -1
- package/dist/write-open-flags.js +1 -2
- package/docs/advanced.md +18 -4
- package/docs/archive.md +57 -9
- package/docs/atomic.md +7 -0
- package/docs/contributing.md +7 -0
- package/docs/copy.md +86 -0
- package/docs/durability.md +17 -2
- package/docs/errors.md +1 -1
- package/docs/file-store.md +48 -3
- package/docs/json-store.md +10 -0
- package/docs/local-roots.md +8 -1
- package/docs/native.md +14 -1
- package/docs/permissions.md +20 -10
- package/docs/positional-read.md +63 -0
- package/docs/reading.md +6 -0
- package/docs/root.md +168 -6
- package/docs/secret-file.md +6 -0
- package/docs/security-model.md +14 -0
- package/docs/sidecar-lock.md +2 -0
- package/docs/timing.md +2 -0
- package/docs/types.md +18 -11
- package/docs/walk.md +54 -5
- package/docs/writing.md +25 -5
- package/package.json +16 -11
package/dist/archive-tar-wasm.js
CHANGED
|
@@ -17,6 +17,7 @@ const types = new Map([
|
|
|
17
17
|
[51, "CharacterDevice"], [52, "BlockDevice"], [53, "Directory"],
|
|
18
18
|
[54, "FIFO"], [55, "ContiguousFile"], [68, "GNUDumpDir"],
|
|
19
19
|
]);
|
|
20
|
+
const utf8Decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
20
21
|
function parserError(message) {
|
|
21
22
|
const mapped = classifyArchiveParserError(message);
|
|
22
23
|
if (mapped)
|
|
@@ -47,7 +48,7 @@ export class TarParserStream extends Transform {
|
|
|
47
48
|
}
|
|
48
49
|
text() {
|
|
49
50
|
const abi = this.abi;
|
|
50
|
-
return
|
|
51
|
+
return utf8Decoder.decode(this.bytes(abi.text_ptr(), abi.text_len()));
|
|
51
52
|
}
|
|
52
53
|
_transform(chunk, _encoding, callback) {
|
|
53
54
|
try {
|
package/dist/archive.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA4DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACpH,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAiNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CjF"}
|
package/dist/archive.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createTarEntryPlanner } from "./archive-tar.js";
|
|
2
2
|
import { inspectTar, replayTar } from "./archive-tar-stream.js";
|
|
3
3
|
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
4
|
-
import { constants as fsConstants } from "node:fs";
|
|
4
|
+
import fsSync, { constants as fsConstants } from "node:fs";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { Readable } from "node:stream";
|
|
@@ -77,6 +77,8 @@ async function writeZipFileEntry(params) {
|
|
|
77
77
|
dir: path.dirname(destinationPath),
|
|
78
78
|
chmodDir: false,
|
|
79
79
|
mode: 0o600,
|
|
80
|
+
syncTempFile: false,
|
|
81
|
+
syncParentDir: false,
|
|
80
82
|
writeTemp: async (tempPath) => {
|
|
81
83
|
tempHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o600);
|
|
82
84
|
const writable = tempHandle.createWriteStream();
|
|
@@ -134,7 +136,7 @@ async function extractZip(params) {
|
|
|
134
136
|
await withStagedArchiveDestination({
|
|
135
137
|
destinationRealDir,
|
|
136
138
|
run: async (stagingDir) => {
|
|
137
|
-
const stagingRealDir =
|
|
139
|
+
const stagingRealDir = fsSync.realpathSync.native(stagingDir);
|
|
138
140
|
const acceptedEntries = [];
|
|
139
141
|
for (const entry of entries) {
|
|
140
142
|
params.deadline.check();
|
|
@@ -185,6 +187,7 @@ async function extractZip(params) {
|
|
|
185
187
|
params.deadline.check();
|
|
186
188
|
await mergePlannedArchiveIntoDestination({
|
|
187
189
|
entries: acceptedEntries,
|
|
190
|
+
durable: params.durable,
|
|
188
191
|
sourceDir: stagingRealDir,
|
|
189
192
|
destinationDir: params.destDir,
|
|
190
193
|
destinationRealDir,
|
|
@@ -212,6 +215,7 @@ export async function extractArchive(params) {
|
|
|
212
215
|
// getters and inherited policy must survive identically on every backend.
|
|
213
216
|
const options = {
|
|
214
217
|
archivePath: params.archivePath, destDir: params.destDir,
|
|
218
|
+
durable: params.durable,
|
|
215
219
|
stripComponents: params.stripComponents, limits,
|
|
216
220
|
entryModes: params.entryModes, entryFilter: params.entryFilter, onFiltered,
|
|
217
221
|
};
|
|
@@ -246,7 +250,7 @@ async function extractWasmTar(params) {
|
|
|
246
250
|
deadline.check();
|
|
247
251
|
const destinationRealDir = await prepareArchiveDestinationDir(options.destDir);
|
|
248
252
|
await withStagedArchiveDestination({ destinationRealDir, run: async (stagingPath) => {
|
|
249
|
-
const stagingDir =
|
|
253
|
+
const stagingDir = fsSync.realpathSync.native(stagingPath);
|
|
250
254
|
const planEntry = createTarEntryPlanner({ ...options, rootDir: destinationRealDir, limits: params.limits });
|
|
251
255
|
const accepted = manifest.flatMap((entry) => {
|
|
252
256
|
deadline.check();
|
|
@@ -262,6 +266,7 @@ async function extractWasmTar(params) {
|
|
|
262
266
|
if (member.kind === "file") {
|
|
263
267
|
await runPinnedWriteHelper({ rootPath: stagingDir, relativeParentPath: path.posix.dirname(member.path),
|
|
264
268
|
basename: path.posix.basename(member.path), mkdir: false, mode: 0o600, overwrite: false,
|
|
269
|
+
sync: false,
|
|
265
270
|
maxBytes: member.size, input: { kind: "stream", stream: Readable.from(payload) } });
|
|
266
271
|
}
|
|
267
272
|
deadline.check();
|
|
@@ -269,7 +274,7 @@ async function extractWasmTar(params) {
|
|
|
269
274
|
});
|
|
270
275
|
deadline.check();
|
|
271
276
|
await mergePlannedArchiveIntoDestination({ entries: accepted, sourceDir: stagingDir,
|
|
272
|
-
destinationDir: options.destDir, destinationRealDir, deadline });
|
|
277
|
+
destinationDir: options.destDir, destinationRealDir, deadline, durable: options.durable });
|
|
273
278
|
deadline.check();
|
|
274
279
|
} });
|
|
275
280
|
}
|
package/dist/bounded-read.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { FileHandle } from "node:fs/promises";
|
|
2
2
|
type ReadableFileHandle = Pick<FileHandle, "read">;
|
|
3
|
+
export declare function readBoundedAsync(maxBytes: number, readChunk: (scratch: Buffer, length: number) => Promise<number>, options?: {
|
|
4
|
+
observeRegularFileSize?: () => number | undefined;
|
|
5
|
+
initialSize?: number;
|
|
6
|
+
createLimitError?: () => Error;
|
|
7
|
+
/** Keep the result outside Node's shared small-buffer allocation pool. */
|
|
8
|
+
unpooled?: boolean;
|
|
9
|
+
}): Promise<Buffer>;
|
|
3
10
|
/**
|
|
4
11
|
* Reads from the handle's current offset without closing it. A bounded read
|
|
5
12
|
* consumes at most maxBytes + 1 bytes so growth after an earlier stat cannot
|
|
@@ -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;AAQnD,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AA8CnD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EAC/D,OAAO,GAAE;IACP,sBAAsB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,KAAK,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,GACL,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAMjB;AAyBD,oFAAoF;AACpF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7F;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoBlF"}
|
package/dist/bounded-read.js
CHANGED
|
@@ -2,50 +2,69 @@ import fs from "node:fs";
|
|
|
2
2
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
const READ_CHUNK_BYTES = 64 * 1024;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return
|
|
5
|
+
// Preserve one-read performance through the default Root byte budget.
|
|
6
|
+
const MAX_INITIAL_READ_BYTES = 16 * 1024 * 1024;
|
|
7
|
+
function createInitialBuffer(maxBytes, size, allocate = Buffer.allocUnsafe) {
|
|
8
|
+
// A size hint can describe a huge sparse file or an already exhausted fd.
|
|
9
|
+
return allocate(Math.min(maxBytes, size, MAX_INITIAL_READ_BYTES) + 1);
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
return
|
|
13
|
-
? Math.min(capacity, maxBytes - total + 1)
|
|
14
|
-
: capacity;
|
|
11
|
+
function createScratchBuffer(maxBytes, allocate = Buffer.allocUnsafe) {
|
|
12
|
+
return allocate(Math.min(READ_CHUNK_BYTES, maxBytes + 1));
|
|
15
13
|
}
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
function growReadBuffer(buffer, maxBytes, allocate = Buffer.allocUnsafe) {
|
|
15
|
+
// Grow only after consuming the whole buffer, never from an unchecked size hint.
|
|
16
|
+
const capacity = Math.min(maxBytes + 1, Math.max(READ_CHUNK_BYTES, buffer.length * 2));
|
|
17
|
+
const grown = allocate(capacity);
|
|
18
|
+
buffer.copy(grown);
|
|
19
|
+
return grown;
|
|
20
|
+
}
|
|
21
|
+
function finishReadBuffer(buffer, total, allocate) {
|
|
22
|
+
if (total === 0)
|
|
23
|
+
return Buffer.alloc(0);
|
|
24
|
+
const result = buffer.subarray(0, total);
|
|
25
|
+
if (total >= buffer.length / 2)
|
|
26
|
+
return result;
|
|
27
|
+
if (!allocate)
|
|
28
|
+
return Buffer.from(result);
|
|
29
|
+
const compact = allocate(total);
|
|
30
|
+
result.copy(compact);
|
|
31
|
+
return compact;
|
|
32
|
+
}
|
|
33
|
+
function addReadBytes(total, bytesRead, maxBytes, createLimitError) {
|
|
34
|
+
const next = total + bytesRead;
|
|
35
|
+
if (next > maxBytes) {
|
|
36
|
+
if (createLimitError)
|
|
37
|
+
throw createLimitError();
|
|
38
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${next})`);
|
|
20
39
|
}
|
|
21
|
-
|
|
22
|
-
return total;
|
|
40
|
+
return next;
|
|
23
41
|
}
|
|
24
|
-
async function readBoundedAsync(maxBytes, readChunk,
|
|
42
|
+
export async function readBoundedAsync(maxBytes, readChunk, options = {}) {
|
|
25
43
|
normalizeMaxBytes(maxBytes);
|
|
26
|
-
const chunks = [];
|
|
27
44
|
let total = 0;
|
|
28
|
-
const
|
|
45
|
+
const { observeRegularFileSize, createLimitError } = options;
|
|
46
|
+
const allocate = options.unpooled ? Buffer.allocUnsafeSlow : undefined;
|
|
47
|
+
const size = options.initialSize ?? observeRegularFileSize?.();
|
|
48
|
+
let buffer = size === undefined ? createScratchBuffer(maxBytes, allocate) : createInitialBuffer(maxBytes, size, allocate);
|
|
29
49
|
if (size !== undefined) {
|
|
30
|
-
const
|
|
31
|
-
const bytesRead = await readChunk(first, first.length);
|
|
50
|
+
const bytesRead = await readChunk(buffer, buffer.length);
|
|
32
51
|
if (bytesRead === 0)
|
|
33
|
-
return
|
|
34
|
-
// Short reads can occur before EOF. Only
|
|
35
|
-
//
|
|
36
|
-
const currentSize = bytesRead <
|
|
52
|
+
return finishReadBuffer(buffer, 0, allocate);
|
|
53
|
+
// Short reads can occur before EOF. Only use the size shortcut on this
|
|
54
|
+
// initial read; virtual files can report zero or stale sizes thereafter.
|
|
55
|
+
const currentSize = bytesRead < buffer.length ? observeRegularFileSize?.() : undefined;
|
|
56
|
+
total = addReadBytes(total, bytesRead, maxBytes, createLimitError);
|
|
37
57
|
if (currentSize !== undefined && bytesRead >= currentSize)
|
|
38
|
-
return
|
|
39
|
-
total = appendChunk({ chunks, scratch: first, bytesRead, total, maxBytes });
|
|
58
|
+
return finishReadBuffer(buffer, total, allocate);
|
|
40
59
|
}
|
|
41
|
-
const scratch = createScratchBuffer(maxBytes);
|
|
42
60
|
while (true) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
if (total === buffer.length)
|
|
62
|
+
buffer = growReadBuffer(buffer, maxBytes, allocate);
|
|
63
|
+
const remaining = buffer.subarray(total);
|
|
64
|
+
const bytesRead = await readChunk(remaining, remaining.length);
|
|
65
|
+
if (bytesRead === 0)
|
|
66
|
+
return finishReadBuffer(buffer, total, allocate);
|
|
67
|
+
total = addReadBytes(total, bytesRead, maxBytes, createLimitError);
|
|
49
68
|
}
|
|
50
69
|
}
|
|
51
70
|
/**
|
|
@@ -58,7 +77,7 @@ export async function readFileHandleBounded(handle, maxBytes) {
|
|
|
58
77
|
const fd = "fd" in handle && typeof handle.fd === "number" ? handle.fd : undefined;
|
|
59
78
|
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
60
79
|
return (await handle.read(scratch, 0, length, null)).bytesRead;
|
|
61
|
-
}, fd === undefined ? undefined : () => regularFileSize(fd));
|
|
80
|
+
}, { observeRegularFileSize: fd === undefined ? undefined : () => regularFileSize(fd) });
|
|
62
81
|
}
|
|
63
82
|
function regularFileSize(fd) {
|
|
64
83
|
try {
|
|
@@ -87,20 +106,31 @@ export async function readFileDescriptorBounded(fd, maxBytes) {
|
|
|
87
106
|
normalizeMaxBytes(maxBytes);
|
|
88
107
|
return await readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
89
108
|
return await readDescriptorChunk(fd, scratch, length);
|
|
90
|
-
}, () => regularFileSize(fd));
|
|
109
|
+
}, { observeRegularFileSize: () => regularFileSize(fd) });
|
|
91
110
|
}
|
|
92
111
|
/** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
93
112
|
export function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
94
113
|
normalizeMaxBytes(maxBytes);
|
|
95
|
-
const chunks = [];
|
|
96
|
-
const scratch = createScratchBuffer(maxBytes);
|
|
97
114
|
let total = 0;
|
|
115
|
+
// Small budgets already bound the first allocation without a size lookup.
|
|
116
|
+
const size = maxBytes <= READ_CHUNK_BYTES ? maxBytes : regularFileSize(fd);
|
|
117
|
+
let buffer = size === undefined ? createScratchBuffer(maxBytes) : createInitialBuffer(maxBytes, size);
|
|
118
|
+
if (size !== undefined) {
|
|
119
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
|
|
120
|
+
if (bytesRead === 0)
|
|
121
|
+
return finishReadBuffer(buffer, 0);
|
|
122
|
+
const currentSize = bytesRead < buffer.length ? regularFileSize(fd) : undefined;
|
|
123
|
+
total = addReadBytes(total, bytesRead, maxBytes);
|
|
124
|
+
if (currentSize !== undefined && bytesRead >= currentSize)
|
|
125
|
+
return finishReadBuffer(buffer, total);
|
|
126
|
+
}
|
|
98
127
|
while (true) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
128
|
+
if (total === buffer.length)
|
|
129
|
+
buffer = growReadBuffer(buffer, maxBytes);
|
|
130
|
+
const remaining = buffer.subarray(total);
|
|
131
|
+
const bytesRead = fs.readSync(fd, remaining, 0, remaining.length, null);
|
|
132
|
+
if (bytesRead === 0)
|
|
133
|
+
return finishReadBuffer(buffer, total);
|
|
134
|
+
total = addReadBytes(total, bytesRead, maxBytes);
|
|
105
135
|
}
|
|
106
136
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type CloneFileMetadata = {
|
|
2
|
+
dev: number;
|
|
3
|
+
type: number;
|
|
4
|
+
mtimeSec: number;
|
|
5
|
+
mtimeNs: number;
|
|
6
|
+
ctimeSec: number;
|
|
7
|
+
ctimeNs: number;
|
|
8
|
+
uid: number;
|
|
9
|
+
gid: number;
|
|
10
|
+
mode: number;
|
|
11
|
+
ino: bigint;
|
|
12
|
+
size: bigint;
|
|
13
|
+
cloneId: bigint;
|
|
14
|
+
};
|
|
15
|
+
/** Batched APFS metadata snapshots; absent on unsupported files or filesystems.
|
|
16
|
+
* These are point-in-time facts, not authorization or a guarantee against later edits.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readCloneFileMetadata(files: readonly string[]): Promise<(CloneFileMetadata | undefined)[]>;
|
|
19
|
+
//# sourceMappingURL=clone-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone-metadata.d.ts","sourceRoot":"","sources":["../src/clone-metadata.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,CAAC,iBAAiB,GAAG,SAAS,CAAC,EAAE,CAAC,CA4B5C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { assertAbsolutePathInput } from "./absolute-path.js";
|
|
2
|
+
import { requireNativeBinding } from "./native.js";
|
|
3
|
+
/** Batched APFS metadata snapshots; absent on unsupported files or filesystems.
|
|
4
|
+
* These are point-in-time facts, not authorization or a guarantee against later edits.
|
|
5
|
+
*/
|
|
6
|
+
export async function readCloneFileMetadata(files) {
|
|
7
|
+
const paths = files.map(assertAbsolutePathInput);
|
|
8
|
+
const results = await requireNativeBinding().readCloneFileMetadata(paths);
|
|
9
|
+
return results.map((result) => {
|
|
10
|
+
if (!result ||
|
|
11
|
+
result.length !== 100 ||
|
|
12
|
+
result.readUInt32LE(0) !== 100 ||
|
|
13
|
+
result.readUInt32LE(4) !== 0x82038c0a ||
|
|
14
|
+
result.readUInt32LE(16) !== 0x200 ||
|
|
15
|
+
result.readUInt32LE(20) !== 0x100)
|
|
16
|
+
return undefined;
|
|
17
|
+
return {
|
|
18
|
+
dev: result.readUInt32LE(24),
|
|
19
|
+
type: result.readUInt32LE(28),
|
|
20
|
+
mtimeSec: Number(result.readBigInt64LE(32)),
|
|
21
|
+
mtimeNs: Number(result.readBigInt64LE(40)),
|
|
22
|
+
ctimeSec: Number(result.readBigInt64LE(48)),
|
|
23
|
+
ctimeNs: Number(result.readBigInt64LE(56)),
|
|
24
|
+
uid: result.readUInt32LE(64),
|
|
25
|
+
gid: result.readUInt32LE(68),
|
|
26
|
+
mode: result.readUInt32LE(72),
|
|
27
|
+
ino: result.readBigUInt64LE(76),
|
|
28
|
+
size: result.readBigUInt64LE(84),
|
|
29
|
+
cloneId: result.readBigUInt64LE(92),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
import { type CopyCloneMode } from "./copy-policy.js";
|
|
4
|
+
import { type NativeBinding } from "./native.js";
|
|
5
|
+
import type { NativeFileCopyResult } from "./native-binding.js";
|
|
6
|
+
export type CopyFileInput = {
|
|
7
|
+
kind: "file";
|
|
8
|
+
handle: FileHandle;
|
|
9
|
+
size: number;
|
|
10
|
+
clone: CopyCloneMode;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
verifySource(): Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
export declare function resolveFileCopyCloneMode(mode?: CopyCloneMode): CopyCloneMode;
|
|
15
|
+
export declare function assertCopySourceCurrent(source: {
|
|
16
|
+
handle: FileHandle;
|
|
17
|
+
realPath: string;
|
|
18
|
+
}, identity: BigIntStats): Promise<void>;
|
|
19
|
+
export declare function copyFileChunks(input: CopyFileInput, maxBytes?: number): AsyncGenerator<Buffer>;
|
|
20
|
+
export declare function writeCopyFileToFd(fd: number, input: CopyFileInput, maxBytes?: number, assertBeforeMutation?: () => void): Promise<void>;
|
|
21
|
+
export declare function createNativeCopyFile(native: NativeBinding, input: CopyFileInput, parentFd: number, basename: string, maxBytes: number | undefined, sync: boolean): Promise<NativeFileCopyResult | undefined>;
|
|
22
|
+
export declare function assertNativeCopyCompleted(input: CopyFileInput, copied?: NativeFileCopyResult): void;
|
|
23
|
+
//# sourceMappingURL=copy-file-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-file-input.d.ts","sourceRoot":"","sources":["../src/copy-file-input.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAIhE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,IAAI,CAAC,EAAE,aAAa,GAAG,aAAa,CAM5E;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAuB,cAAc,CACnC,KAAK,EAAE,aAAa,EACpB,QAAQ,SAAW,GAClB,cAAc,CAAC,MAAM,CAAC,CAiBxB;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,aAAa,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CA8B3C;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAWnG"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
|
+
import { resolveCopyCloneMode } from "./copy-policy.js";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { getNativeBinding } from "./native.js";
|
|
5
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
6
|
+
import { writeAllToFile } from "./write-file-handle.js";
|
|
7
|
+
export function resolveFileCopyCloneMode(mode) {
|
|
8
|
+
const clone = resolveCopyCloneMode(mode, "never");
|
|
9
|
+
if (clone === "always" && !getNativeBinding()?.copyFileExclusive) {
|
|
10
|
+
throw new FsSafeError("helper-unavailable", "native file cloning is unavailable");
|
|
11
|
+
}
|
|
12
|
+
return clone;
|
|
13
|
+
}
|
|
14
|
+
export async function assertCopySourceCurrent(source, identity) {
|
|
15
|
+
await inspectFileIdentity(() => fs.fstatSync(source.handle.fd, { bigint: true }), identity);
|
|
16
|
+
await inspectFileIdentity(() => {
|
|
17
|
+
const current = fs.lstatSync(source.realPath, { bigint: true });
|
|
18
|
+
if (current.isSymbolicLink() || !current.isFile()) {
|
|
19
|
+
throw new FsSafeError("path-mismatch", "copy source path changed");
|
|
20
|
+
}
|
|
21
|
+
return current;
|
|
22
|
+
}, identity);
|
|
23
|
+
}
|
|
24
|
+
export async function* copyFileChunks(input, maxBytes = Infinity) {
|
|
25
|
+
const buffer = Buffer.allocUnsafe(Math.min(256 * 1024, Math.max(64 * 1024, input.size)));
|
|
26
|
+
let position = 0;
|
|
27
|
+
while (true) {
|
|
28
|
+
input.signal?.throwIfAborted();
|
|
29
|
+
const { bytesRead } = await input.handle.read(buffer, 0, Math.min(buffer.length, maxBytes - position + 1), position);
|
|
30
|
+
input.signal?.throwIfAborted();
|
|
31
|
+
if (bytesRead > maxBytes - position) {
|
|
32
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`);
|
|
33
|
+
}
|
|
34
|
+
if (bytesRead === 0)
|
|
35
|
+
return;
|
|
36
|
+
position += bytesRead;
|
|
37
|
+
// The consumer finishes writing before the next iteration reuses this buffer.
|
|
38
|
+
yield buffer.subarray(0, bytesRead);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function writeCopyFileToFd(fd, input, maxBytes, assertBeforeMutation) {
|
|
42
|
+
for await (const chunk of copyFileChunks(input, maxBytes)) {
|
|
43
|
+
await writeAllToFile(fd, chunk, {
|
|
44
|
+
assertBeforeMutation: () => {
|
|
45
|
+
input.signal?.throwIfAborted();
|
|
46
|
+
assertBeforeMutation?.();
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function createNativeCopyFile(native, input, parentFd, basename, maxBytes, sync) {
|
|
52
|
+
input.signal?.throwIfAborted();
|
|
53
|
+
if (!native.copyFileExclusive) {
|
|
54
|
+
if (input.clone === "always") {
|
|
55
|
+
throw new FsSafeError("helper-unavailable", "native file cloning is unavailable");
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const nativeSignal = input.signal ? AbortSignal.any([input.signal]) : undefined;
|
|
60
|
+
try {
|
|
61
|
+
// The caller adopts this descriptor before observing a later cancellation.
|
|
62
|
+
return await native.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal, sync);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const code = error?.code;
|
|
66
|
+
if (code === "Cancelled" || code === "ABORT_ERR")
|
|
67
|
+
input.signal?.throwIfAborted();
|
|
68
|
+
if (code === "too-large") {
|
|
69
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`, { cause: error });
|
|
70
|
+
}
|
|
71
|
+
if (code === "ENOTSUP" && input.clone !== "always")
|
|
72
|
+
return undefined;
|
|
73
|
+
if (code === "ENOTSUP") {
|
|
74
|
+
throw new FsSafeError("unsupported-platform", "native file cloning is unsupported", { cause: error });
|
|
75
|
+
}
|
|
76
|
+
throw new FsSafeError("helper-failed", "native file copy failed", { cause: error });
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
if (nativeSignal)
|
|
80
|
+
nativeSignal.onabort = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function assertNativeCopyCompleted(input, copied) {
|
|
84
|
+
if (copied?.errorCode) {
|
|
85
|
+
if (copied.errorCode === "Cancelled" || copied.errorCode === "ABORT_ERR")
|
|
86
|
+
input.signal?.throwIfAborted();
|
|
87
|
+
const unsupported = ["ENOTSUP", "EOPNOTSUPP", "ENOSYS", "EXDEV", "EINVAL"].includes(copied.errorCode);
|
|
88
|
+
throw new FsSafeError(copied.errorCode === "too-large" ? "too-large" : unsupported ? "unsupported-platform" : "helper-failed", copied.errorMessage ?? "native file copy failed", { cause: Object.assign(new Error(copied.errorMessage ?? "native file copy failed"), { code: copied.errorCode }) });
|
|
89
|
+
}
|
|
90
|
+
input.signal?.throwIfAborted();
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-policy.d.ts","sourceRoot":"","sources":["../src/copy-policy.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAExD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,aAAa,GAAG,SAAS,EAC/B,WAAW,EAAE,aAAa,GACzB,aAAa,CAMf"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
export function resolveCopyCloneMode(mode, defaultMode) {
|
|
3
|
+
const policy = mode === undefined ? defaultMode : mode;
|
|
4
|
+
if (policy !== "auto" && policy !== "always" && policy !== "never") {
|
|
5
|
+
throw new FsSafeError("invalid-path", "copy clone policy must be auto, always, or never");
|
|
6
|
+
}
|
|
7
|
+
return policy;
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PinnedWriteParams, PublishedWriteIdentity } from "./pinned-write.js";
|
|
2
|
+
export type RootCopyPublicationReceipt = Readonly<{
|
|
3
|
+
path: string;
|
|
4
|
+
dev: bigint;
|
|
5
|
+
ino: bigint;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function createCopyPublicationObserver(path: string, notify?: (receipt: RootCopyPublicationReceipt) => void): {
|
|
8
|
+
onPublished(identity: PublishedWriteIdentity): void;
|
|
9
|
+
rethrowObserverFailure(error: unknown): void;
|
|
10
|
+
};
|
|
11
|
+
export declare const onCopyPublication: unique symbol;
|
|
12
|
+
export type CopyPublicationOptions = {
|
|
13
|
+
[onCopyPublication]?: PinnedWriteParams["verifyPublished"];
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=copy-publication.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-publication.d.ts","sourceRoot":"","sources":["../src/copy-publication.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAGnF,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI;IAIpD,WAAW,WAAW,sBAAsB,GAAG,IAAI;IAcnD,sBAAsB,QAAQ,OAAO,GAAG,IAAI;EAO/C;AAID,eAAO,MAAM,iBAAiB,eAA8B,CAAC;AAC7D,MAAM,MAAM,sBAAsB,GAAG;IACnC,CAAC,iBAAiB,CAAC,CAAC,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;CAC5D,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
export function createCopyPublicationObserver(path, notify) {
|
|
3
|
+
let rejected;
|
|
4
|
+
return {
|
|
5
|
+
onPublished(identity) {
|
|
6
|
+
const receipt = Object.freeze({ path, dev: identity.dev, ino: identity.ino });
|
|
7
|
+
try {
|
|
8
|
+
const result = notify?.(receipt);
|
|
9
|
+
if (result !== null && (typeof result === "object" || typeof result === "function") &&
|
|
10
|
+
"then" in result && typeof result.then === "function") {
|
|
11
|
+
void Promise.resolve(result).catch(() => undefined);
|
|
12
|
+
throw new TypeError("onDestinationPublished must be synchronous");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
rejected = { error };
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
rethrowObserverFailure(error) {
|
|
21
|
+
if (rejected && (error === rejected.error ||
|
|
22
|
+
(error instanceof FsSafeError && error.cause === rejected.error && error.details?.phase === "publish"))) {
|
|
23
|
+
throw rejected.error;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// Internal composition hook; the descriptor is borrowed until the callback returns.
|
|
29
|
+
// Kept off RootCopyOptions and all public package exports.
|
|
30
|
+
export const onCopyPublication = Symbol("onCopyPublication");
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NativeBinding } from "./native-binding.js";
|
|
2
|
+
import { openStagedDirectory } from "./staged-directory.js";
|
|
3
|
+
/** Byte-copy adapter for the shared immutable-source, caller-owned namespace contract. */
|
|
4
|
+
export declare function copyOwnedTree(source: ReturnType<typeof openStagedDirectory>, destination: string, options: {
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
concurrency: number;
|
|
7
|
+
copyFileContents?: NativeBinding["copyFileContents"];
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=copy-tree-portable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-tree-portable.d.ts","sourceRoot":"","sources":["../src/copy-tree-portable.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAGL,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAE/B,0FAA0F;AAC1F,wBAAsB,aAAa,CACjC,MAAM,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,EAC9C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IACP,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACtD,GACA,OAAO,CAAC,IAAI,CAAC,CAkLf"}
|