@openclaw/fs-safe 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +28 -6
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +6 -7
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +5 -6
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +7 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +79 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +1 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +127 -0
- package/dist/file-cleanup.d.ts +19 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +78 -0
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +2 -5
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +36 -7
- package/dist/move-path.js +1 -1
- package/dist/native-binding.d.ts +11 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +2 -0
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +3 -2
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +173 -28
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +19 -3
- package/dist/native-policy-parent-windows.d.ts.map +1 -1
- package/dist/native-policy-parent-windows.js +15 -6
- package/dist/native-staged-file.d.ts +3 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +86 -39
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +25 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +24 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +187 -0
- package/dist/pinned-write-types.d.ts +3 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +35 -145
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/replace-file-temp-owner.d.ts +0 -7
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +3 -54
- package/dist/root-create-input.d.ts +2 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +13 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +28 -7
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +22 -13
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-write-publication.js +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -1
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/docs/advanced.md +3 -1
- package/docs/archive.md +61 -37
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +2 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/install.md +31 -7
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +22 -9
- package/docs/native.md +38 -7
- package/docs/permissions.md +37 -14
- package/docs/root.md +34 -0
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +5 -4
- package/docs/staged-file.md +5 -0
- package/docs/writing.md +71 -5
- package/package.json +18 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.16.0 - 2026-09-19
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- **Private files and directories through existing APIs:** add `private: true` to Root `mkdir()`, `ensureRoot()`, `create()`, and `createJson()`, with verified owner-only permissions and creation-time Windows ACL protection. New `/advanced` helpers `createDirectory()`, `createDirectorySync()`, and `createFileSync()` create one exclusive entry under an existing trusted parent; the file helper returns an owned, disposable descriptor. ([#487](https://github.com/openclaw/fs-safe/pull/487), [#482](https://github.com/openclaw/fs-safe/issues/482))
|
|
10
|
+
- **Safer secret and private writes:** verify the actual staging permissions before writing content, rejecting filesystems that accept permission changes without enforcing the required private mode. ([#491](https://github.com/openclaw/fs-safe/pull/491), [#493](https://github.com/openclaw/fs-safe/pull/493))
|
|
11
|
+
- **Atomic buffered file creation:** opt into `atomic: true` on `Root.create()` or `createJson()` to keep the destination absent until its content is ready, including without the native addon. Independently select `durable: "file"` when file-flush failures must propagate. Existing defaults remain unchanged. ([#486](https://github.com/openclaw/fs-safe/pull/486))
|
|
12
|
+
- **Compressed TAR without native installation:** extract and read bounded entries from zstd and bzip2 TAR archives through bundled WASM codecs, even with all optional dependencies omitted. The fallback retains the shared Rust TAR parser, full-stream validation, byte limits, and guarded publication, with no runtime interpreter or download. ([#488](https://github.com/openclaw/fs-safe/pull/488))
|
|
13
|
+
- **Windows security operations without the addon:** use packaged, readable PowerShell/C# helpers for raw owner/DACL inspection, private-directory creation, and secure-file reads in native `auto` or `off` mode when capabilities are unavailable. Private permissions apply at creation, and secure reads inspect the same open handle that supplies the bytes. ([#476](https://github.com/openclaw/fs-safe/pull/476))
|
|
14
|
+
|
|
15
|
+
### Compatibility and upgrade notes
|
|
16
|
+
|
|
17
|
+
- Private creation on macOS requires the matching native helper's ACL-inspection capability; disabled, missing, or older helpers reject before creating parents or stages. Relevant inheritable parent ACLs reject creation, while noninheriting ACLs remain allowed. Existing entries are never repaired or made less restrictive. Nonprivate creation and native-free secret-file writes retain their behavior; see the [creation contract](https://github.com/openclaw/fs-safe/blob/v0.16.0/docs/creation.md) for supported modes and platform limits.
|
|
18
|
+
- Native-free `atomic: true` creation requires hardlinks and rejects unsupported filesystems without publishing partial content. Private Windows file publication also requires hardlinks on the same local filesystem. Streamed creates continue to stage complete content; atomic visibility does not strengthen pathname containment or guarantee crash durability.
|
|
19
|
+
- `durable: "file"` applies to buffered, streamed, and JSON creates and propagates file-sync errors, including `EPERM`. Parent-directory synchronization remains best-effort, independently of the selected publication strategy.
|
|
20
|
+
- Windows fallbacks require system PowerShell and permission to run the packaged scripts and `.NET Add-Type` under normal system policy. They emit a path-free `FS_SAFE_NATIVE_FALLBACK` warning once per capability per process and add startup and compilation overhead; disallowed execution fails closed. No system policy is bypassed.
|
|
21
|
+
- Native `require` remains strict, and failures from available native operations never trigger a fallback retry. No-clobber `Root.move()` still requires native support. ZIP fallback still needs optional `jszip`, and `inspectTarArchive()` continues to accept only plain TAR and gzip.
|
|
22
|
+
- Atomic, streamed, and private creation can report cleanup, close, mode, or synchronization errors after a complete file has been published. Preserve the publication and cleanup receipts when handling failures; completed destinations remain in place, and indeterminate outcomes retain names for recovery.
|
|
23
|
+
|
|
24
|
+
### Security and correctness
|
|
25
|
+
|
|
26
|
+
- Verify `0600` before secret-file payload writes in native and JavaScript writers. Verify actual native private-file ownership and permissions before payload writes and at publication, rejecting filesystems that accept permission changes without enforcing them; retain explicit final modes and public staging's `0600` guarantees. ([#491](https://github.com/openclaw/fs-safe/pull/491), [#493](https://github.com/openclaw/fs-safe/pull/493))
|
|
27
|
+
- Compare exact directory identities before and after durability syncs, retry unknown Windows observations once, and reject rounded or unverifiable caller receipts. Bind numeric metadata to the same observation and preserve private identity authority across receipt mutation, durable creation, publication, and retained staging. ([#483](https://github.com/openclaw/fs-safe/pull/483))
|
|
28
|
+
- Retain streamed `Root.create()` authority callbacks and abort signals across producer waits so replacing caller options cannot detach a revoked lease or redirect cancellation; preserve callback receivers and owned-stage cleanup. ([#490](https://github.com/openclaw/fs-safe/pull/490))
|
|
29
|
+
- Recheck retained parent and staging identities after final publication authority callbacks on JavaScript and native writers, preserving substituted entries before their bytes can be published. Flush native Windows creation through the retained writable descriptor and preserve published destinations after finalization failures.
|
|
30
|
+
- Snapshot `Root.move()` mutation policy before asynchronous admission so changes to caller-owned deny paths or prefixes cannot change an in-flight move. Recheck native no-clobber source identity, type, and hardlink policy after the final authority callback; live revocation remains available through `assertBeforeMutation`.
|
|
31
|
+
- Keep asynchronous Root-backed file locks waiting through successive owner handoffs instead of failing with `path-mismatch`, while retaining identity checks and requiring a fresh exclusive acquisition.
|
|
32
|
+
- Return one unsupported clone-metadata result per input without the native addon on non-macOS platforms in `auto` and `off` modes. Preserve input validation, strict `require` mode, and native-only APFS metadata on macOS. ([#489](https://github.com/openclaw/fs-safe/pull/489))
|
|
33
|
+
|
|
34
|
+
### Diagnostics and maintenance
|
|
35
|
+
|
|
36
|
+
- Address Windows security commands by their built-in module names and restrict discovery to PowerShell's bundled system modules, avoiding broad discovery scans on helper startup. ([#484](https://github.com/openclaw/fs-safe/pull/484))
|
|
37
|
+
- Clarify that `resolveExistingPathsWithinRoot()` permits missing paths while `resolveStrictExistingPathsWithinRoot()` requires existing regular files, and distinguish native beneath mechanisms from the best-effort containment reported by public Root open, read, and writable-open results. ([#485](https://github.com/openclaw/fs-safe/pull/485))
|
|
38
|
+
- Honor case-insensitive Windows build environment names so configured WASM compilers and archivers remain selected in worker processes; preserve child-only compiler flags without duplicate case variants. ([#492](https://github.com/openclaw/fs-safe/pull/492))
|
|
39
|
+
- Refresh JavaScript and Rust dependencies, pnpm, CodeQL, and archive/release build toolchains, including current NAPI interoperability fixes; retain Node 22 support and Rust 1.88 compatibility. Includes the Dependabot updates in [#481](https://github.com/openclaw/fs-safe/pull/481).
|
|
40
|
+
|
|
3
41
|
## 0.15.0 - 2026-09-18
|
|
4
42
|
|
|
5
43
|
### Highlights
|
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ The same idea has landed in other languages. Go [added `os.Root` and `OpenInRoot
|
|
|
45
45
|
| `path.resolve().startsWith()` | string check only | – | – | – | – |
|
|
46
46
|
| [`write-file-atomic`](https://www.npmjs.com/package/write-file-atomic) | – | ✓ | – | – | – |
|
|
47
47
|
| Go [`os.Root`](https://go.dev/blog/osroot) / Rust [`cap-std`](https://github.com/bytecodealliance/cap-std) | ✓ | platform | ✓ | ✓ | – |
|
|
48
|
-
| **`@openclaw/fs-safe`** | **✓** | **✓** | **✓** | **Linux atomic; others best-effort** | **✓ (ZIP/TAR
|
|
48
|
+
| **`@openclaw/fs-safe`** | **✓** | **✓** | **✓** | **Linux atomic; others best-effort** | **✓ (ZIP/TAR/gzip/zstd/bzip2)** |
|
|
49
49
|
|
|
50
50
|
## Not a sandbox
|
|
51
51
|
|
|
@@ -57,7 +57,7 @@ This is a **library-level guardrail**, not OS-level isolation. It does not repla
|
|
|
57
57
|
pnpm add @openclaw/fs-safe
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Node 22 or newer. Core root/path/json/temp helpers avoid framework dependencies. With all optional dependencies omitted, public subpaths remain safe to import and fallback-capable operations work in `auto` or `off`. Native-only features, including no-clobber `Root.move()
|
|
60
|
+
Node 22 or newer. Core root/path/json/temp helpers avoid framework dependencies. With all optional dependencies omitted, public subpaths remain safe to import and fallback-capable operations work in `auto` or `off`. Native-only features, including no-clobber `Root.move()` and [`private: true` creation on macOS](docs/creation.md#permission-options), remain unavailable and fail with `helper-unavailable`. TAR, gzip, zstd, and bzip2 extraction and bounded entry reads use the same Rust TAR parser through bundled WASM when native support is disabled or absent. Zstd/bzip2 codecs are bundled too; gzip uses Node's built-in decoder. ZIP fallback still needs optional `jszip`. See the [0.6 migration guide](docs/migrating-to-0.6.md).
|
|
61
61
|
|
|
62
62
|
Bun 1.4.2 is also supported with the [Bun runtime requirements](docs/install.md#bun-runtime), including the matching Rust addon on macOS and Linux. JIT-disabled Bun works too.
|
|
63
63
|
|
|
@@ -70,7 +70,7 @@ environment policy:
|
|
|
70
70
|
import { configureFsSafeNative } from "@openclaw/fs-safe";
|
|
71
71
|
|
|
72
72
|
configureFsSafeNative({ mode: "auto" }); // default: native when available
|
|
73
|
-
configureFsSafeNative({ mode: "off" }); //
|
|
73
|
+
configureFsSafeNative({ mode: "off" }); // disable the addon; use supported fallbacks
|
|
74
74
|
configureFsSafeNative({ mode: "require" }); // fail closed if the binding is unavailable
|
|
75
75
|
```
|
|
76
76
|
|
|
@@ -158,6 +158,23 @@ const opened = await fs.open("notes/today.txt");
|
|
|
158
158
|
await fs.create("notes/README.md", "seed\n"); // throws if it already exists
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
Use `private: true` on `mkdir()`, `ensureRoot()`, `create()`, or `createJson()`
|
|
162
|
+
for private creation. On macOS, this requires native ACL inspection before
|
|
163
|
+
creating parents or stages and verifies owner-only permissions with no ACL
|
|
164
|
+
before writing payload bytes. Native `off` or a missing ACL capability rejects
|
|
165
|
+
with `helper-unavailable`; nonprivate creation is unchanged. See the
|
|
166
|
+
[creation contract](docs/creation.md#permission-options) for parent ACL handling
|
|
167
|
+
and platform support.
|
|
168
|
+
|
|
169
|
+
Pass `{ atomic: true }` to buffered `create()` or `createJson()` to keep the
|
|
170
|
+
destination absent until complete content is ready, including with native support
|
|
171
|
+
disabled. The JavaScript fallback requires hardlinks and never downgrades to a
|
|
172
|
+
partial visible file. Omitted or `false` retains the existing buffered behavior.
|
|
173
|
+
Atomic visibility is separate from the existing `durable` synchronization policy.
|
|
174
|
+
Use `durable: "file"` on `create()` or `createJson()` when file-flush errors,
|
|
175
|
+
including `EPERM`, must propagate. It combines with `atomic: true` without
|
|
176
|
+
requiring strict parent-directory synchronization.
|
|
177
|
+
|
|
161
178
|
`create()` also accepts an `AsyncIterable<Uint8Array>` for large or incrementally
|
|
162
179
|
produced files. Streamed creates keep the destination absent until all chunks
|
|
163
180
|
are written, support `maxBytes` and `signal`, and recheck mutation authority
|
|
@@ -259,7 +276,7 @@ contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are
|
|
|
259
276
|
| `@openclaw/fs-safe/permissions` | POSIX mode and Windows ACL inspection, raw owner/ACE facts, private-directory creation, and remediation helpers |
|
|
260
277
|
| `@openclaw/fs-safe/walk` | budget-bounded directory walking with symlink policy, filters, and truncation accounting; not root-bounded |
|
|
261
278
|
| `@openclaw/fs-safe/copy` | directory copying with `clone: "auto"`, `"always"`, or `"never"`; native APFS, Btrfs, ReFS, XFS, and ZFS cloning, portable byte copying, and clone metadata; see [directory copying](docs/copy.md) |
|
|
262
|
-
| `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits,
|
|
279
|
+
| `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits, gzip/zstd/bzip2 support, and bounded entry reads |
|
|
263
280
|
| `@openclaw/fs-safe/advanced` | lower-level composition helpers such as path scopes, root-file open, bounded descriptor reads, [borrowed-handle and descriptor copying](docs/copy.md#borrowed-filehandle-transfers), [exact directory identity](docs/directory-identity.md), [case probing](docs/path-case.md), [suffix-alias probing](docs/path-suffix-aliases.md), [in-place writes](docs/in-place-write.md), [versioned install-ID encoding](docs/install-path.md#safepathsegmenthashedv2), filename sanitizing, temp-file targets, sibling-temp writes, local-root readers, regular-file helpers, `pathExists`, and `withTimeout`; less stable than focused public subpaths |
|
|
264
281
|
| `@openclaw/fs-safe/errors` | `FsSafeError`, closed codes/categories, causes, and operation-specific details receipts |
|
|
265
282
|
| `@openclaw/fs-safe/types` | shared types: `DirEntry`, `PathStat`, … |
|
|
@@ -466,8 +483,13 @@ instead of a root-relative workspace path. It opens the file first, validates th
|
|
|
466
483
|
same handle it will read from, checks trusted directories, owner, POSIX mode or
|
|
467
484
|
Windows ACLs, hardlink count, size, and optional timeout, then reads through the
|
|
468
485
|
pinned handle. On Windows, both the bytes and the owner/DACL facts come from that
|
|
469
|
-
handle
|
|
470
|
-
|
|
486
|
+
handle. In native `auto` or `off` mode, a packaged, readable PowerShell script can
|
|
487
|
+
inspect that borrowed handle when the native capability is unavailable, subject
|
|
488
|
+
to the [Windows security fallback prerequisites](docs/install.md#windows-security-fallback).
|
|
489
|
+
It emits one fallback warning per process for secure reads and adds PowerShell
|
|
490
|
+
startup and compilation overhead per call. Native `require` remains strict, and
|
|
491
|
+
native operation failures are terminal. Neither route reopens the pathname to
|
|
492
|
+
inspect its ACL.
|
|
471
493
|
|
|
472
494
|
```ts
|
|
473
495
|
import { readSecureFile } from "@openclaw/fs-safe/secure-file";
|
package/dist/advanced.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createAsyncLock } from "./async-lock.js";
|
|
2
|
+
export { createDirectory, createDirectorySync, createFileSync, type CreateDirectoryOptions, type CreateFileOptions, type OwnedFileDescriptorSync, } from "./create.js";
|
|
2
3
|
export { copyFileHandle, copyFileDescriptorSync, type CopyFileHandleOptions } from "./file-handle-transfer.js";
|
|
3
4
|
export { overwriteFileHandle, type OverwriteFileHandleOptions } from "./overwrite-file-handle.js";
|
|
4
5
|
export { probePathCaseInsensitiveSync, type ProbePathCaseOptions } from "./path-case.js";
|
package/dist/advanced.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"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"}
|
package/dist/advanced.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// public subpaths; prefer root/json/store/temp/archive unless you are building a
|
|
3
3
|
// higher-level primitive.
|
|
4
4
|
export { createAsyncLock } from "./async-lock.js";
|
|
5
|
+
export { createDirectory, createDirectorySync, createFileSync, } from "./create.js";
|
|
5
6
|
export { copyFileHandle, copyFileDescriptorSync } from "./file-handle-transfer.js";
|
|
6
7
|
export { overwriteFileHandle } from "./overwrite-file-handle.js";
|
|
7
8
|
export { probePathCaseInsensitiveSync } from "./path-case.js";
|
package/dist/archive-kind.d.ts
CHANGED
|
@@ -4,6 +4,5 @@ type ResolvePackedRootDirOptions = {
|
|
|
4
4
|
rootMarkers?: string[];
|
|
5
5
|
};
|
|
6
6
|
export declare function resolvePackedRootDir(extractDir: string, options?: ResolvePackedRootDirOptions): Promise<string>;
|
|
7
|
-
export declare function assertPortableArchiveKind(kind: ArchiveKind): void;
|
|
8
7
|
export {};
|
|
9
8
|
//# sourceMappingURL=archive-kind.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-kind.d.ts","sourceRoot":"","sources":["../src/archive-kind.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-kind.d.ts","sourceRoot":"","sources":["../src/archive-kind.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC;AAQnE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAgBvE;AAED,KAAK,2BAA2B,GAAG;IACjC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAkBF,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,MAAM,CAAC,CA4BjB"}
|
package/dist/archive-kind.js
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { FsSafeError } from "./errors.js";
|
|
5
4
|
import { getNativeBinding } from "./native.js";
|
|
6
5
|
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
7
6
|
const TAR_SUFFIXES = [".tgz", ".tar.gz", ".tar"];
|
|
8
|
-
const
|
|
7
|
+
const COMPRESSED_TAR_SUFFIXES = [
|
|
9
8
|
{ suffixes: [".tbz2", ".tbz", ".tar.bz2"], kind: "tar-bzip2" },
|
|
10
9
|
{ suffixes: [".tzst", ".tar.zst", ".tar.zstd"], kind: "tar-zstd" },
|
|
11
10
|
];
|
|
12
|
-
function requireNativeArchiveKind(kind) {
|
|
13
|
-
if (!getNativeBinding()) {
|
|
14
|
-
throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
|
|
15
|
-
"install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
|
|
16
|
-
}
|
|
17
|
-
return kind;
|
|
18
|
-
}
|
|
19
11
|
export function resolveArchiveKind(filePath) {
|
|
20
12
|
const lower = normalizeLowercaseStringOrEmpty(filePath);
|
|
21
13
|
if (lower.endsWith(".zip")) {
|
|
22
14
|
return "zip";
|
|
23
15
|
}
|
|
24
|
-
for (const { suffixes, kind } of
|
|
16
|
+
for (const { suffixes, kind } of COMPRESSED_TAR_SUFFIXES) {
|
|
25
17
|
if (suffixes.some((suffix) => lower.endsWith(suffix))) {
|
|
26
|
-
|
|
18
|
+
// Preserve strict require diagnostics even when only resolving a suffix.
|
|
19
|
+
getNativeBinding();
|
|
20
|
+
return kind;
|
|
27
21
|
}
|
|
28
22
|
}
|
|
29
23
|
if (TAR_SUFFIXES.some((suffix) => lower.endsWith(suffix))) {
|
|
@@ -75,9 +69,3 @@ export async function resolvePackedRootDir(extractDir, options) {
|
|
|
75
69
|
}
|
|
76
70
|
return path.join(extractDir, onlyDir);
|
|
77
71
|
}
|
|
78
|
-
export function assertPortableArchiveKind(kind) {
|
|
79
|
-
if (kind === "tar-zstd" || kind === "tar-bzip2") {
|
|
80
|
-
throw new FsSafeError("helper-unavailable", `${kind} archives require the matching optional native platform package; ` +
|
|
81
|
-
"install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
|
|
82
|
-
}
|
|
83
|
-
}
|
package/dist/archive-parser.wasm
CHANGED
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAgBA,OAAO,
|
|
1
|
+
{"version":3,"file":"archive-read.d.ts","sourceRoot":"","sources":["../src/archive-read.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAqOzE,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAAE,GAChD,OAAO,CAAC,MAAM,CAAC,CAiBjB"}
|
package/dist/archive-read.js
CHANGED
|
@@ -7,7 +7,7 @@ import { readBoundedAsync } from "./bounded-read.js";
|
|
|
7
7
|
import { ArchiveFormatError, ArchiveSecurityError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
|
|
8
8
|
import { formatErrorDetail } from "./error-detail.js";
|
|
9
9
|
import { stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
|
|
10
|
-
import {
|
|
10
|
+
import { resolveArchiveKind } from "./archive-kind.js";
|
|
11
11
|
import { DEFAULT_MAX_ARCHIVE_BYTES_ZIP, ArchiveLimitError, ARCHIVE_LIMIT_ERROR_CODE, } from "./archive-limits.js";
|
|
12
12
|
import { isGzipBuffer } from "./archive-gzip-tail.js";
|
|
13
13
|
import { inspectTar, replayTar } from "./archive-tar-stream.js";
|
|
@@ -139,11 +139,11 @@ async function readZipEntry(buffer, entryPath, maxBytes, admitted) {
|
|
|
139
139
|
throw normalizeZipIntegrityError(error);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
-
async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
|
|
142
|
+
async function readTarEntry(archiveBuffer, entryPath, maxBytes, kind) {
|
|
143
143
|
const seenPaths = new Set();
|
|
144
144
|
let selected;
|
|
145
145
|
const limits = resolveTarMeterLimits();
|
|
146
|
-
await inspectTar({ archiveBuffer, limits, onMember(info) {
|
|
146
|
+
await inspectTar({ archiveBuffer, kind, limits, onMember(info) {
|
|
147
147
|
const normalized = canonicalEntryPath(info.path);
|
|
148
148
|
if (seenPaths.has(normalized)) {
|
|
149
149
|
throw new ArchiveSecurityError("entry-path", `archive contains duplicate entry path: ${formatErrorDetail(normalized)}`);
|
|
@@ -159,7 +159,7 @@ async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
|
|
|
159
159
|
}
|
|
160
160
|
if (selected.size > maxBytes)
|
|
161
161
|
throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ENTRY_EXTRACTED_SIZE_EXCEEDS_LIMIT);
|
|
162
|
-
if (!isGzipBuffer(archiveBuffer)) {
|
|
162
|
+
if (kind === "tar" && !isGzipBuffer(archiveBuffer)) {
|
|
163
163
|
// Complete admission already validated this private snapshot through EOF.
|
|
164
164
|
// Copy the admitted payload so the result cannot expose or mutate its input.
|
|
165
165
|
const end = selected.offset + selected.size;
|
|
@@ -170,7 +170,7 @@ async function readTarEntry(archiveBuffer, entryPath, maxBytes) {
|
|
|
170
170
|
return Buffer.from(archiveBuffer.subarray(selected.offset, end));
|
|
171
171
|
}
|
|
172
172
|
let result;
|
|
173
|
-
await replayTar({ archiveBuffer, limits, members: [selected], async consume(member, payload) {
|
|
173
|
+
await replayTar({ archiveBuffer, kind, limits, members: [selected], async consume(member, payload) {
|
|
174
174
|
result = await readAdmittedTarPayload(payload, member.size);
|
|
175
175
|
} });
|
|
176
176
|
return result;
|
|
@@ -238,7 +238,6 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
238
238
|
const native = getNativeBinding();
|
|
239
239
|
if (native)
|
|
240
240
|
return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, options.maxBytes, zipEntries);
|
|
241
|
-
assertPortableArchiveKind(kind);
|
|
242
241
|
return kind === "zip" ? await readZipEntry(buffer, requestedEntry, options.maxBytes, zipEntries)
|
|
243
|
-
: await readTarEntry(buffer, requestedEntry, options.maxBytes);
|
|
242
|
+
: await readTarEntry(buffer, requestedEntry, options.maxBytes, kind);
|
|
244
243
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ArchiveKind } from "./archive-kind.js";
|
|
1
2
|
import type { TarMeterLimits } from "./archive-limits.js";
|
|
2
3
|
import { type AdmittedTarMember } from "./archive-tar-wasm.js";
|
|
3
4
|
/** Buffers are private immutable snapshots, just like the staged file route. */
|
|
@@ -11,6 +12,7 @@ type TarInput = {
|
|
|
11
12
|
export declare function inspectTar(params: TarInput & {
|
|
12
13
|
limits: TarMeterLimits;
|
|
13
14
|
signal?: AbortSignal;
|
|
15
|
+
kind?: Exclude<ArchiveKind, "zip">;
|
|
14
16
|
onMember?: (entry: AdmittedTarMember) => void;
|
|
15
17
|
}): Promise<void>;
|
|
16
18
|
/** Replay in physical order, retaining at most one decoded chunk. Every range
|
|
@@ -18,6 +20,7 @@ export declare function inspectTar(params: TarInput & {
|
|
|
18
20
|
export declare function replayTar<T extends AdmittedTarMember>(params: TarInput & {
|
|
19
21
|
limits: TarMeterLimits;
|
|
20
22
|
signal?: AbortSignal;
|
|
23
|
+
kind?: Exclude<ArchiveKind, "zip">;
|
|
21
24
|
members: readonly T[];
|
|
22
25
|
consume(member: T, payload: AsyncIterable<Buffer>): Promise<void>;
|
|
23
26
|
}): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-tar-stream.d.ts","sourceRoot":"","sources":["../src/archive-tar-stream.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,
|
|
1
|
+
{"version":3,"file":"archive-tar-stream.d.ts","sourceRoot":"","sources":["../src/archive-tar-stream.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAmC,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGhG,gFAAgF;AAChF,KAAK,QAAQ,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AA0EhH,wBAAsB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG;IAClD,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACjF,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/C,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAED;2DAC2D;AAC3D,wBAAsB,SAAS,CAAC,CAAC,SAAS,iBAAiB,EAAE,MAAM,EAAE,QAAQ,GAAG;IAC9E,MAAM,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACjF,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhB"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { Readable, Writable } from "node:stream";
|
|
3
|
-
import { pipeline } from "node:stream/promises";
|
|
3
|
+
import { finished, pipeline } from "node:stream/promises";
|
|
4
4
|
import { createGunzip } from "node:zlib";
|
|
5
5
|
import { GzipInput, isGzipBuffer, validateGzipBufferTail, validateGzipContainerTail } from "./archive-gzip-tail.js";
|
|
6
6
|
import { ArchiveFormatError } from "./archive-errors.js";
|
|
7
|
-
import { TarParserStream } from "./archive-tar-wasm.js";
|
|
7
|
+
import { TarParserStream, TarWasmSession } from "./archive-tar-wasm.js";
|
|
8
8
|
import { readFileWindowFully } from "./positional-read.js";
|
|
9
9
|
function* bufferChunks(buffer) {
|
|
10
10
|
for (let offset = 0; offset < buffer.length; offset += 65536)
|
|
@@ -23,45 +23,64 @@ async function gzipFile(filePath) {
|
|
|
23
23
|
}
|
|
24
24
|
async function withTarStream(params, consume) {
|
|
25
25
|
const buffer = params.archiveBuffer;
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
? Readable.from(bufferChunks(buffer), { objectMode: false, highWaterMark: 65536 })
|
|
30
|
-
: fs.createReadStream(params.archivePath, { highWaterMark: 65536 });
|
|
31
|
-
// Match the WASM input window for both staged files and buffered reads.
|
|
32
|
-
const decoder = gzip ? createGunzip({ chunkSize: 65536 }) : undefined;
|
|
33
|
-
const gzipInput = decoder ? new GzipInput(decoder) : undefined;
|
|
34
|
-
const destroy = (error) => {
|
|
35
|
-
input.destroy(error);
|
|
36
|
-
gzipInput?.destroy(error);
|
|
37
|
-
decoder?.destroy(error);
|
|
38
|
-
parser.destroy(error);
|
|
39
|
-
};
|
|
40
|
-
const pumps = decoder && gzipInput
|
|
41
|
-
? [pipeline(decoder, parser, { signal: params.signal }), pipeline(input, gzipInput, { signal: params.signal })]
|
|
42
|
-
: [pipeline(input, parser, { signal: params.signal })];
|
|
43
|
-
// Either pump tears down both routes; join every pump even after the first failure.
|
|
44
|
-
const settled = Promise.all(pumps.map((pump) => pump.then(() => undefined, (cause) => {
|
|
45
|
-
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
46
|
-
destroy(error);
|
|
47
|
-
return error;
|
|
48
|
-
}))).then((errors) => errors.find((error) => error !== undefined));
|
|
26
|
+
const kind = params.kind ?? "tar";
|
|
27
|
+
const gzip = kind === "tar" && (buffer !== undefined ? isGzipBuffer(buffer) : await gzipFile(params.archivePath));
|
|
28
|
+
const session = new TarWasmSession(params.limits);
|
|
49
29
|
try {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
30
|
+
const parser = new TarParserStream(params.limits, params.onMember, session);
|
|
31
|
+
const input = buffer !== undefined
|
|
32
|
+
? Readable.from(bufferChunks(buffer), { objectMode: false, highWaterMark: 65536 })
|
|
33
|
+
: fs.createReadStream(params.archivePath, { highWaterMark: 65536 });
|
|
34
|
+
// Match the WASM input window for both staged files and buffered reads.
|
|
35
|
+
const decoder = gzip ? createGunzip({ chunkSize: 65536 }) : undefined;
|
|
36
|
+
const gzipInput = decoder ? new GzipInput(decoder) : undefined;
|
|
37
|
+
const destroy = (error) => {
|
|
38
|
+
input.destroy(error);
|
|
39
|
+
gzipInput?.destroy(error);
|
|
40
|
+
decoder?.destroy(error);
|
|
41
|
+
parser.destroy(error);
|
|
42
|
+
};
|
|
43
|
+
// A pipeline with an async-generator stage can settle before its file's
|
|
44
|
+
// asynchronous close. Join the actual close events before freeing WASM.
|
|
45
|
+
const closed = Promise.all([input, parser, ...(decoder ? [decoder] : []), ...(gzipInput ? [gzipInput] : [])].map(stream => {
|
|
46
|
+
const closing = new Promise(resolve => { stream.once("close", resolve); });
|
|
47
|
+
return finished(stream, { cleanup: true }).then(() => undefined, (cause) => cause instanceof Error ? cause : new Error(String(cause))).then(async (error) => { await closing; return error; });
|
|
48
|
+
}));
|
|
49
|
+
const pumps = decoder && gzipInput
|
|
50
|
+
? [pipeline(decoder, parser, { signal: params.signal }), pipeline(input, gzipInput, { signal: params.signal })]
|
|
51
|
+
: kind !== "tar"
|
|
52
|
+
? [pipeline(input, (source) => session.decode(source, kind, params.signal), parser, { signal: params.signal })]
|
|
53
|
+
: [pipeline(input, parser, { signal: params.signal })];
|
|
54
|
+
// Either pump tears down both routes; join every pump even after the first failure.
|
|
55
|
+
const settled = Promise.all(pumps.map((pump) => pump.then(() => undefined, (cause) => {
|
|
56
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
57
|
+
destroy(error);
|
|
58
|
+
return error;
|
|
59
|
+
}))).then((errors) => errors.find((error) => error !== undefined));
|
|
60
|
+
try {
|
|
61
|
+
const result = await consume(parser);
|
|
62
|
+
const error = await settled;
|
|
63
|
+
if (error)
|
|
64
|
+
throw error;
|
|
65
|
+
const closeError = (await closed).find(error => error !== undefined);
|
|
66
|
+
if (closeError)
|
|
67
|
+
throw closeError;
|
|
68
|
+
if (gzipInput) {
|
|
69
|
+
if (buffer !== undefined)
|
|
70
|
+
await validateGzipBufferTail(buffer, gzipInput.tailOffset, params.signal);
|
|
71
|
+
else
|
|
72
|
+
await validateGzipContainerTail(params.archivePath, gzipInput.tailOffset, params.signal);
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
destroy();
|
|
78
|
+
await settled;
|
|
79
|
+
await closed;
|
|
59
80
|
}
|
|
60
|
-
return result;
|
|
61
81
|
}
|
|
62
82
|
finally {
|
|
63
|
-
|
|
64
|
-
await settled;
|
|
83
|
+
session.dispose();
|
|
65
84
|
}
|
|
66
85
|
}
|
|
67
86
|
export async function inspectTar(params) {
|
|
@@ -4,13 +4,25 @@ import type { TarEntryInfo } from "./archive-tar.js";
|
|
|
4
4
|
export type AdmittedTarMember = TarEntryInfo & {
|
|
5
5
|
offset: number;
|
|
6
6
|
};
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
/** One bounded memory domain owns both admission and decompression. Its owner
|
|
8
|
+
* must join the decoder and parser before disposing this shared instance. */
|
|
9
|
+
export declare class TarWasmSession {
|
|
10
10
|
private abi;
|
|
11
|
-
constructor(limits: TarMeterLimits
|
|
11
|
+
constructor(limits: TarMeterLimits);
|
|
12
12
|
private bytes;
|
|
13
13
|
private text;
|
|
14
|
+
parse(chunk: Buffer, onMember?: (entry: AdmittedTarMember) => void): void;
|
|
15
|
+
finish(): void;
|
|
16
|
+
private codecError;
|
|
17
|
+
decode(source: AsyncIterable<Buffer>, kind: "tar-zstd" | "tar-bzip2", signal?: AbortSignal): AsyncGenerator<Buffer>;
|
|
18
|
+
dispose(): void;
|
|
19
|
+
}
|
|
20
|
+
/** Backpressure-aware transport only; all TAR semantics live in the Rust core. */
|
|
21
|
+
export declare class TarParserStream extends Transform {
|
|
22
|
+
private readonly onMember?;
|
|
23
|
+
private readonly session;
|
|
24
|
+
private readonly ownsSession;
|
|
25
|
+
constructor(limits: TarMeterLimits, onMember?: ((entry: AdmittedTarMember) => void) | undefined, session?: TarWasmSession);
|
|
14
26
|
_transform(chunk: Buffer, _encoding: BufferEncoding, callback: TransformCallback): void;
|
|
15
27
|
_flush(callback: TransformCallback): void;
|
|
16
28
|
_destroy(error: Error | null, callback: (error: Error | null) => void): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"archive-tar-wasm.d.ts","sourceRoot":"","sources":["../src/archive-tar-wasm.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAmDlE;6EAC6E;AAC7E,qBAAa,cAAc;IACzB,OAAO,CAAC,GAAG,CAAkB;IAC7B,YAAY,MAAM,EAAE,cAAc,EAMjC;IACD,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAIZ,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,IAAI,CAkBxE;IACD,MAAM,IAAI,IAAI,CAEb;IACD,OAAO,CAAC,UAAU;IAIX,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,GAAG,WAAW,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CA2DzH;IACD,OAAO,IAAI,IAAI,CAOd;CACF;AAED,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,SAAS;IAGR,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAF9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IACtC,YAAY,MAAM,EAAE,cAAc,EAAmB,QAAQ,CAAC,GAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,aAAA,EAAE,OAAO,CAAC,EAAE,cAAc,EAI3H;IACQ,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAG/F;IACQ,MAAM,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAGjD;IACQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAIpF;CACF"}
|