@poe-platform/safe-fs 0.1.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/LICENSE +21 -0
- package/README.md +290 -0
- package/dist/safe-fs/bridge/confinement.d.ts +3 -0
- package/dist/safe-fs/bridge/confinement.js +171 -0
- package/dist/safe-fs/bridge/filesystem.d.ts +88 -0
- package/dist/safe-fs/bridge/filesystem.js +411 -0
- package/dist/safe-fs/bridge/index.d.ts +6 -0
- package/dist/safe-fs/bridge/index.js +18 -0
- package/dist/safe-fs/bridge/stats.d.ts +4 -0
- package/dist/safe-fs/bridge/stats.js +37 -0
- package/dist/safe-fs/bridge/types.d.ts +75 -0
- package/dist/safe-fs/bridge/types.js +1 -0
- package/dist/safe-fs/bridge/values.d.ts +6 -0
- package/dist/safe-fs/bridge/values.js +65 -0
- package/dist/safe-fs/config/memory.d.ts +3 -0
- package/dist/safe-fs/config/memory.js +9 -0
- package/dist/safe-fs/config/real.d.ts +5 -0
- package/dist/safe-fs/config/real.js +16 -0
- package/dist/safe-fs/config.d.ts +15 -0
- package/dist/safe-fs/config.js +46 -0
- package/dist/safe-fs/config.node.d.ts +2 -0
- package/dist/safe-fs/config.node.js +22 -0
- package/dist/safe-fs/contracts/abort.d.ts +5 -0
- package/dist/safe-fs/contracts/abort.js +34 -0
- package/dist/safe-fs/contracts/cleanup.d.ts +1 -0
- package/dist/safe-fs/contracts/cleanup.js +9 -0
- package/dist/safe-fs/contracts/errors.d.ts +50 -0
- package/dist/safe-fs/contracts/errors.js +77 -0
- package/dist/safe-fs/contracts/filesystem.d.ts +96 -0
- package/dist/safe-fs/contracts/filesystem.js +1 -0
- package/dist/safe-fs/contracts/index.d.ts +4 -0
- package/dist/safe-fs/contracts/index.js +4 -0
- package/dist/safe-fs/contracts/io.d.ts +8 -0
- package/dist/safe-fs/contracts/io.js +92 -0
- package/dist/safe-fs/contracts/path.d.ts +8 -0
- package/dist/safe-fs/contracts/path.js +8 -0
- package/dist/safe-fs/contracts/portable-path.d.ts +13 -0
- package/dist/safe-fs/contracts/portable-path.js +57 -0
- package/dist/safe-fs/contracts/virtual-path.d.ts +7 -0
- package/dist/safe-fs/contracts/virtual-path.js +66 -0
- package/dist/safe-fs/core.d.ts +12 -0
- package/dist/safe-fs/core.js +12 -0
- package/dist/safe-fs/fs/memory/index.d.ts +58 -0
- package/dist/safe-fs/fs/memory/index.js +532 -0
- package/dist/safe-fs/fs/mount/comparison.d.ts +19 -0
- package/dist/safe-fs/fs/mount/comparison.js +118 -0
- package/dist/safe-fs/fs/mount/identity.d.ts +2 -0
- package/dist/safe-fs/fs/mount/identity.js +12 -0
- package/dist/safe-fs/fs/mount/index.d.ts +44 -0
- package/dist/safe-fs/fs/mount/index.js +546 -0
- package/dist/safe-fs/fs/overlay/index.d.ts +73 -0
- package/dist/safe-fs/fs/overlay/index.js +927 -0
- package/dist/safe-fs/fs/readonly/index.d.ts +31 -0
- package/dist/safe-fs/fs/readonly/index.js +128 -0
- package/dist/safe-fs/fs/real/allocation.d.ts +1 -0
- package/dist/safe-fs/fs/real/allocation.js +8 -0
- package/dist/safe-fs/fs/real/index.d.ts +76 -0
- package/dist/safe-fs/fs/real/index.js +543 -0
- package/dist/safe-fs/fs/s3/authority.d.ts +7 -0
- package/dist/safe-fs/fs/s3/authority.js +31 -0
- package/dist/safe-fs/fs/s3/filesystem.d.ts +93 -0
- package/dist/safe-fs/fs/s3/filesystem.js +1024 -0
- package/dist/safe-fs/fs/s3/http/index.d.ts +2 -0
- package/dist/safe-fs/fs/s3/http/index.js +1 -0
- package/dist/safe-fs/fs/s3/http/request.d.ts +16 -0
- package/dist/safe-fs/fs/s3/http/request.js +196 -0
- package/dist/safe-fs/fs/s3/http/signature.d.ts +20 -0
- package/dist/safe-fs/fs/s3/http/signature.js +74 -0
- package/dist/safe-fs/fs/s3/http/transport.d.ts +3 -0
- package/dist/safe-fs/fs/s3/http/transport.js +455 -0
- package/dist/safe-fs/fs/s3/http/types.d.ts +30 -0
- package/dist/safe-fs/fs/s3/http/types.js +1 -0
- package/dist/safe-fs/fs/s3/http/xml.d.ts +11 -0
- package/dist/safe-fs/fs/s3/http/xml.js +188 -0
- package/dist/safe-fs/fs/s3/index.d.ts +6 -0
- package/dist/safe-fs/fs/s3/index.js +3 -0
- package/dist/safe-fs/fs/s3/mock.d.ts +48 -0
- package/dist/safe-fs/fs/s3/mock.js +233 -0
- package/dist/safe-fs/fs/s3/registry.d.ts +10 -0
- package/dist/safe-fs/fs/s3/registry.js +71 -0
- package/dist/safe-fs/fs/s3/transport.d.ts +103 -0
- package/dist/safe-fs/fs/s3/transport.js +28 -0
- package/dist/safe-fs/fs/webdav/index.d.ts +2 -0
- package/dist/safe-fs/fs/webdav/index.js +1 -0
- package/dist/safe-fs/fs/webdav/resource-id.d.ts +18 -0
- package/dist/safe-fs/fs/webdav/resource-id.js +109 -0
- package/dist/safe-fs/fs/webdav/webdav.d.ts +101 -0
- package/dist/safe-fs/fs/webdav/webdav.js +1226 -0
- package/dist/safe-fs/fs/webdav/xml.d.ts +15 -0
- package/dist/safe-fs/fs/webdav/xml.js +250 -0
- package/dist/safe-fs/index.d.ts +14 -0
- package/dist/safe-fs/index.js +12 -0
- package/dist/safe-fs/node/filesystem.d.ts +11 -0
- package/dist/safe-fs/node/filesystem.js +28 -0
- package/dist/safe-fs/node/index.d.ts +2 -0
- package/dist/safe-fs/node/index.js +1 -0
- package/dist/safe-fs/node/stats.d.ts +1 -0
- package/dist/safe-fs/node/stats.js +1 -0
- package/dist/safe-fs/node/values.d.ts +1 -0
- package/dist/safe-fs/node/values.js +1 -0
- package/dist/safe-fs/node-host.d.ts +1 -0
- package/dist/safe-fs/node-host.js +1 -0
- package/dist/safe-fs/node-unavailable.d.ts +1 -0
- package/dist/safe-fs/node-unavailable.js +1 -0
- package/dist/safe-fs/platform/browser.d.ts +12 -0
- package/dist/safe-fs/platform/browser.js +39 -0
- package/dist/safe-fs/platform/node.d.ts +13 -0
- package/dist/safe-fs/platform/node.js +26 -0
- package/package.json +108 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Poe Platform
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# safe-fs
|
|
2
|
+
|
|
3
|
+
An asynchronous filesystem interface for memory, rooted host directories, S3, and WebDAV, with mounts, copy-on-write overlays, and Node-style bridges.
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
Install the standalone package; it does not depend on SafeJS, Safe Bash, or the `poe-code` CLI. These examples use TypeScript in a Node ESM application.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @poe-platform/safe-fs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createMemoryFileSystem, createNodeFsBridge } from "@poe-platform/safe-fs";
|
|
15
|
+
|
|
16
|
+
const storage = createMemoryFileSystem();
|
|
17
|
+
await storage.mkdir("/work");
|
|
18
|
+
await storage.writeFile("/work/note.txt", new TextEncoder().encode("hello"));
|
|
19
|
+
console.log(new TextDecoder().decode(await storage.readFile("/work/note.txt")));
|
|
20
|
+
|
|
21
|
+
const fs = createNodeFsBridge(storage, { cwd: "/work" });
|
|
22
|
+
await fs.appendFile("note.txt", " world", "utf8");
|
|
23
|
+
console.log(await fs.readFile("note.txt", "utf8"));
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Output: `hello`, then `hello world`. Nothing touches the host filesystem. Raw adapters exchange `Uint8Array` values; the Node bridge adds strings, encodings, `Buffer` results, and stat predicates. Its `cwd` is both the relative-path base and the confinement boundary.
|
|
27
|
+
|
|
28
|
+
For host storage, use `await createRealFileSystem({ root: "/absolute/existing/directory" })` instead. The root must already exist; virtual `/` maps to that directory. Read the safety boundary below before exposing it to untrusted code.
|
|
29
|
+
|
|
30
|
+
## Entry points and shared identity
|
|
31
|
+
|
|
32
|
+
| Import | Use |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `@poe-platform/safe-fs` | Full Node API; selects the portable core under the `browser` condition. |
|
|
35
|
+
| `@poe-platform/safe-fs/core` | Memory, mounts, overlays, read-only, WebDAV, portable bridges, types, and errors. Bundle with the `browser` condition for browsers and Workers. |
|
|
36
|
+
| `@poe-platform/safe-fs/node` | Host filesystem, Node bridge, S3, and configuration helpers; unavailable in browsers. |
|
|
37
|
+
| `@poe-platform/safe-fs/fs/*` | Individual adapters: `memory`, `readonly`, `mount`, `overlay`, `webdav`, `real`, `s3`, and `s3/http`. The last three are Node-only. |
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { createMemoryFileSystem, FsError, type FileSystem } from "@poe-platform/safe-fs/core";
|
|
41
|
+
|
|
42
|
+
const fs: FileSystem = createMemoryFileSystem();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Matching scoped SafeJS and Safe Bash releases use this package's types and runtime.
|
|
46
|
+
Existing `@poe-platform/safe-js/fs`, `/fs/core`, and `/fs/node` imports remain
|
|
47
|
+
re-exports, so `FsError` identity is shared; do not copy its implementation or the
|
|
48
|
+
`FileSystem` declaration. `FileSystem` is an interface, not a runtime class.
|
|
49
|
+
|
|
50
|
+
Legacy `poe-code/safe-fs` imports still use the CLI's bundled runtime. Keep an
|
|
51
|
+
application's factories, shell, and errors in one import family. At a host-owned
|
|
52
|
+
legacy adapter boundary, normalize foreign filesystem failures with `toFsError`
|
|
53
|
+
from the receiving runtime; it retains the code, operation paths, and original
|
|
54
|
+
cause. Preserve cancellation/control exceptions separately. Cross-family
|
|
55
|
+
`instanceof FsError` is not supported without normalization.
|
|
56
|
+
|
|
57
|
+
## Supported operations
|
|
58
|
+
|
|
59
|
+
| Operation | Interface |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| Read and write bytes | `readFile`, `writeFile`, `appendFile` |
|
|
62
|
+
| Inspect entries | `stat`, `lstat`, `readdir`, `realpath`, `access` |
|
|
63
|
+
| Change the namespace | `mkdir`, `rm`, `rename`, `copyFile` |
|
|
64
|
+
| Remove an empty directory | Optional `rmdir`; never a recursive-delete fallback |
|
|
65
|
+
| Links and metadata | Optional `readlink`, `symlink`, `link`, `chmod`, `utimes`, `truncate` |
|
|
66
|
+
| Stream bytes | Optional `readStream`, `writeStream`, using async iterables of byte chunks |
|
|
67
|
+
| Compare backing entries | Optional `compareEntry`, returning `same`, `distinct`, or `unknown` |
|
|
68
|
+
|
|
69
|
+
All required methods must exist, but a backend may reject an operation with `FsError`, such as `EROFS` for a write or `ENOTSUP` for unsupported semantics. Check optional methods and `capabilities` rather than assuming every backend behaves like a local disk. Errors expose `code` and may include `syscall`, `path`, `dest`, and `cause`.
|
|
70
|
+
|
|
71
|
+
| Backend or wrapper | Use it for |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `createMemoryFileSystem()` | Isolated, nonpersistent storage with links, permissions, timestamps, and streams |
|
|
74
|
+
| `createRealFileSystem({ root })` | An existing host directory, with virtual paths rooted inside it; Node only |
|
|
75
|
+
| `new S3FileSystem({ transport, bucket, … })` | Bucket/prefix storage through an explicitly supplied transport; Node only |
|
|
76
|
+
| `new WebDavFileSystem({ baseUrl, fetch, … })` | A WebDAV namespace through an explicitly supplied Fetch implementation |
|
|
77
|
+
| `createReadOnlyFileSystem(filesystem)` | Rejecting writes through one view of an existing filesystem |
|
|
78
|
+
| `createMountFileSystem({ root, mounts })` | Routing absolute virtual mount paths to different filesystems |
|
|
79
|
+
| `createOverlayFileSystem({ upper, lower })` | Reading through to a lower layer and writing changes to an upper layer |
|
|
80
|
+
|
|
81
|
+
`createNodeFsBridge` offers a promises-shaped subset, including recursive `cp` and `mkdtemp`. The portable `createFsBridge` from `@poe-platform/safe-fs/core` instead requires a caller-supplied text codec and returns `Uint8Array` values.
|
|
82
|
+
|
|
83
|
+
## Write an adapter
|
|
84
|
+
|
|
85
|
+
A backend implements the exported [`FileSystem` interface](src/contracts/filesystem.ts). It needs every non-optional method, a truthful `capabilities` object, byte-safe reads/writes, and appropriate errors. A configuration adapter adds `validateOptions` and a `create` factory returning that filesystem, synchronously or asynchronously.
|
|
86
|
+
|
|
87
|
+
This hypothetical `catalog` adapter presents a snapshot of named text documents as read-only files at `/`. It implements the full required interface without a host filesystem or network connection. Only the root directory and single-component filenames are supported; timestamps are synthetic and optional operations are absent.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import {
|
|
91
|
+
ACCESS_MODES, FsError, createFileSystem,
|
|
92
|
+
createNodeFileSystemAdapterRegistry, readConfigRecord, validatePath,
|
|
93
|
+
type FileStat, type FileSystem, type FileSystemAdapterDescriptor,
|
|
94
|
+
type FsOptions,
|
|
95
|
+
} from "@poe-platform/safe-fs";
|
|
96
|
+
|
|
97
|
+
const catalogAdapter = {
|
|
98
|
+
validateOptions(options) {
|
|
99
|
+
const config = readConfigRecord(options, "catalog option", ["files"]);
|
|
100
|
+
const files = readConfigRecord(config.files, "catalog files");
|
|
101
|
+
for (const [name, text] of Object.entries(files)) {
|
|
102
|
+
if (!name || name === "." || name === ".." || name.includes("/")
|
|
103
|
+
|| name.includes("\0") || typeof text !== "string") {
|
|
104
|
+
throw new TypeError("files must map single-component names to strings");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
create(options): FileSystem {
|
|
109
|
+
const files = new Map(Object.entries(readConfigRecord(options.files, "catalog files"))
|
|
110
|
+
.map(([name, text]) => [name, new TextEncoder().encode(text as string)]));
|
|
111
|
+
|
|
112
|
+
function lookup(path: string, options?: FsOptions) {
|
|
113
|
+
if (options?.signal?.aborted) throw new FsError("ECANCELED", { path });
|
|
114
|
+
validatePath(path);
|
|
115
|
+
let name = "";
|
|
116
|
+
let bytes: Uint8Array | undefined;
|
|
117
|
+
for (const part of path.split("/")) {
|
|
118
|
+
if (bytes !== undefined) throw new FsError("ENOTDIR", { path });
|
|
119
|
+
if (part === "" || part === "." || part === "..") continue;
|
|
120
|
+
bytes = files.get(part);
|
|
121
|
+
if (bytes === undefined) throw new FsError("ENOENT", { path });
|
|
122
|
+
name = part;
|
|
123
|
+
}
|
|
124
|
+
return { path: `/${name}`, bytes };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function inspect(path: string, options?: FsOptions): Promise<FileStat> {
|
|
128
|
+
const { bytes } = lookup(path, options);
|
|
129
|
+
return {
|
|
130
|
+
type: bytes === undefined ? "directory" : "file",
|
|
131
|
+
size: bytes?.byteLength ?? 0,
|
|
132
|
+
mode: bytes === undefined ? 0o40555 : 0o100444,
|
|
133
|
+
atimeMs: 0, mtimeMs: 0, ctimeMs: 0,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function denyWrite(): Promise<never> {
|
|
138
|
+
throw new FsError("EROFS");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
capabilities: { readOnly: true },
|
|
143
|
+
async readFile(path, options) {
|
|
144
|
+
const { bytes } = lookup(path, options);
|
|
145
|
+
if (bytes === undefined) throw new FsError("EISDIR", { path });
|
|
146
|
+
const limit = options?.maxBytes ?? Number.MAX_SAFE_INTEGER;
|
|
147
|
+
if (!Number.isSafeInteger(limit) || limit < 0) throw new FsError("EINVAL", { path });
|
|
148
|
+
if (bytes.byteLength > limit) throw new FsError("EFBIG", { path });
|
|
149
|
+
return bytes.slice();
|
|
150
|
+
},
|
|
151
|
+
stat: inspect,
|
|
152
|
+
lstat: inspect,
|
|
153
|
+
async readdir(path, options) {
|
|
154
|
+
if (lookup(path, options).bytes !== undefined) throw new FsError("ENOTDIR", { path });
|
|
155
|
+
return [...files.keys()].sort().map(name => ({ name, type: "file" as const }));
|
|
156
|
+
},
|
|
157
|
+
async realpath(path, options) {
|
|
158
|
+
const entry = lookup(path, options);
|
|
159
|
+
return entry.path;
|
|
160
|
+
},
|
|
161
|
+
async access(path, mode = ACCESS_MODES.F_OK, options) {
|
|
162
|
+
const entry = lookup(path, options);
|
|
163
|
+
if (!Number.isInteger(mode) || mode < 0 || mode > 7) throw new FsError("EINVAL", { path });
|
|
164
|
+
if (mode & ACCESS_MODES.W_OK) throw new FsError("EROFS", { path });
|
|
165
|
+
if ((mode & ACCESS_MODES.X_OK) && entry.bytes !== undefined) {
|
|
166
|
+
throw new FsError("EACCES", { path });
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
writeFile: denyWrite, appendFile: denyWrite, mkdir: denyWrite,
|
|
170
|
+
rm: denyWrite, rename: denyWrite, copyFile: denyWrite,
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
} satisfies FileSystemAdapterDescriptor;
|
|
174
|
+
|
|
175
|
+
const registry = createNodeFileSystemAdapterRegistry(new Map([["catalog", catalogAdapter]]));
|
|
176
|
+
const catalog = await createFileSystem({
|
|
177
|
+
type: "catalog",
|
|
178
|
+
options: { files: { "welcome.txt": "Hello from the catalog" } },
|
|
179
|
+
}, { registry });
|
|
180
|
+
|
|
181
|
+
console.log(new TextDecoder().decode(await catalog.readFile("/welcome.txt")));
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Output: `Hello from the catalog`. `readFile` returns a copy so callers cannot mutate the stored snapshot. Reads check cancellation and `maxBytes`; required mutations always reject with `EROFS`. `stat` and `lstat` share an implementation because this adapter has no symlinks.
|
|
185
|
+
|
|
186
|
+
`createFileSystem` validates options before calling the factory. The Node registry includes `memory` and `real`, accepts new names, and rejects attempts to replace those defaults. For an explicit allowlist without either default, pass `new Map([["catalog", catalogAdapter]])` directly as `registry`. Names select registered factories; they do not load modules from config.
|
|
187
|
+
|
|
188
|
+
For a mutable backend, preserve exclusive-create behavior, return independent byte buffers, handle cancellation and size limits, and declare only capabilities you actually provide. Use `FsError` for filesystem failures. Do not invent entry identity from matching paths or metadata; omit `compareEntry` when identity is unknown.
|
|
189
|
+
|
|
190
|
+
## Options
|
|
191
|
+
|
|
192
|
+
There are no package environment variables, implicit credentials, or automatic `.env` loading. Applications supply configuration, transports, and credentials explicitly. Factories accept the same options as their corresponding classes.
|
|
193
|
+
|
|
194
|
+
### Local storage, composition, and bridges
|
|
195
|
+
|
|
196
|
+
| API | Options and defaults |
|
|
197
|
+
| --- | --- |
|
|
198
|
+
| `createFileSystem(config, { registry })` | Required `config.type`; `config.options` defaults to an empty record. `registry` is required. Built-in `memory` accepts no options; built-in `real` requires `root`. |
|
|
199
|
+
| `createNodeFileSystemAdapterRegistry(extensions?)` | Optional map of additional adapter descriptors; defaults to only `memory` and `real`. |
|
|
200
|
+
| Memory / read-only | Memory takes no options. Read-only takes the backing filesystem, without an options object. |
|
|
201
|
+
| Real | Required `root`: existing absolute host directory; the constructor/factory also accepts the root string directly. |
|
|
202
|
+
| Mount | Required `root`: fallback filesystem. `mounts` defaults to `{}` and maps absolute virtual paths to filesystems. |
|
|
203
|
+
| Overlay | Required `upper` and `lower`; `maxBufferBytes` defaults to 64 MiB. |
|
|
204
|
+
| Node bridge | `cwd` defaults to `/`, must be an absolute virtual path; optional lifetime `signal`. |
|
|
205
|
+
| Portable bridge | Same `cwd` and `signal`, plus required `codec` with `isEncoding`, `encode`, and `decode` functions. |
|
|
206
|
+
| Catalog example | Required `files`: a plain record of single-component filenames to text strings; no other options. |
|
|
207
|
+
|
|
208
|
+
### Per-operation options
|
|
209
|
+
|
|
210
|
+
Every raw filesystem operation accepts an optional `signal`. Additional fields are:
|
|
211
|
+
|
|
212
|
+
| Operation | Options |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| `readFile` | `maxBytes`: upper bound on collected bytes |
|
|
215
|
+
| `writeFile`, `writeStream` | `flag`: `w` (default, replace), `wx` (exclusive create), `a` (append), or `ax` (exclusive append/create); optional `mode` |
|
|
216
|
+
| `appendFile` | Optional `mode` |
|
|
217
|
+
| `mkdir` | `recursive` (default false); optional `mode` |
|
|
218
|
+
| `rm` | `recursive` and `force` (default false) |
|
|
219
|
+
| `copyFile` | `exclusive` (default false) |
|
|
220
|
+
| `readStream` | `start` (default 0), `endExclusive` (default end of file), `chunkSize` (built-in default 64 KiB) |
|
|
221
|
+
|
|
222
|
+
`access` takes a separate mode bitmask from `ACCESS_MODES`. `chmod` takes a mode, `utimes` takes millisecond timestamps, and `truncate` takes a byte length (default 0). Backend limits still apply. Node-shaped bridge methods translate their own options rather than accepting these raw option objects; see the [bridge signatures](src/bridge/filesystem.ts).
|
|
223
|
+
|
|
224
|
+
<details>
|
|
225
|
+
<summary>S3 filesystem and HTTP transport options</summary>
|
|
226
|
+
|
|
227
|
+
`S3FileSystem` requires `transport` and `bucket`. A transport supplies `headObject`, `getObject`, `putObject`, `deleteObject`, `copyObject`, and `listObjectsV2`; see the [transport interface](src/fs/s3/transport.ts).
|
|
228
|
+
|
|
229
|
+
`createS3Transport(client, capabilities?)` wraps a compatible client with explicit `streamingRead`, `streamingWrite`, `conditionalPut`, `conditionalCopy`, and `conditionalDelete` flags (all absent by default). Direct transport requests accept an optional `abortSignal`.
|
|
230
|
+
|
|
231
|
+
For in-memory S3 simulations, `new MockS3Client({ buckets, pageSize?, now?, authorize? })` requires bucket names. `pageSize` defaults to 1,000 (range 1–1,000), `now` defaults to the current date/time, and optional `authorize(request)` can reject requests. It does not verify a real provider.
|
|
232
|
+
|
|
233
|
+
| Optional field | Default / meaning |
|
|
234
|
+
| --- | --- |
|
|
235
|
+
| `prefix` | Empty; canonical relative object-key prefix |
|
|
236
|
+
| `readOnly` | `false` |
|
|
237
|
+
| `allowNonAtomicRename` | `true`; rename copies then deletes, without rollback guarantees |
|
|
238
|
+
| `pageSize` | 1,000; range 1–1,000 |
|
|
239
|
+
| `maxReadBytes` | 64 MiB |
|
|
240
|
+
| `maxStreamBytes` | 5,000,000,000 bytes; also the maximum accepted value |
|
|
241
|
+
| `maxListEntries` | 100,000 |
|
|
242
|
+
| `compareEntry` | Optional trusted backing-identity callback |
|
|
243
|
+
|
|
244
|
+
`createS3HttpTransport` requires `endpoint` (an origin without path or credentials), `region`, and `credentials`. Credentials contain `accessKeyId`, `secretAccessKey`, and optional `sessionToken`, or come from an async provider receiving `{ signal }`.
|
|
245
|
+
|
|
246
|
+
| Optional field | Default / meaning |
|
|
247
|
+
| --- | --- |
|
|
248
|
+
| `addressingStyle` | `path`; alternative `virtual-hosted` requires a DNS endpoint |
|
|
249
|
+
| `listUrlEncoding` | `percent`; alternative `form` |
|
|
250
|
+
| `allowInsecureHttp` | `false`; HTTPS required unless explicitly enabled |
|
|
251
|
+
| `maxPutBytes`, `maxGetBytes` | 64 MiB each |
|
|
252
|
+
| `maxXmlBytes` | 4 MiB; maximum 16 MiB |
|
|
253
|
+
| `requestTimeoutMs` | 30,000 |
|
|
254
|
+
| `enableCopy` | `true`; disabling uses a buffered GET/PUT fallback |
|
|
255
|
+
| `verifiedConditionalOperations` | Optional `put`, `copy`, `delete` booleans, each defaulting to false; enable only after verifying the server's semantics |
|
|
256
|
+
| `clock` | Current date/time function, used for signing |
|
|
257
|
+
| `request` | Optional Node HTTP request factory; defaults to the built-in HTTP(S) client |
|
|
258
|
+
|
|
259
|
+
</details>
|
|
260
|
+
|
|
261
|
+
<details>
|
|
262
|
+
<summary>WebDAV options</summary>
|
|
263
|
+
|
|
264
|
+
`WebDavFileSystem` requires `baseUrl` and `fetch`.
|
|
265
|
+
|
|
266
|
+
| Optional field | Default / meaning |
|
|
267
|
+
| --- | --- |
|
|
268
|
+
| `headers` | Empty; explicit authentication/custom headers. Protocol-reserved headers are rejected; authorization and cookies require HTTPS. |
|
|
269
|
+
| `requestStreamSupport` | `native` for global Fetch, otherwise false; accepts `native` or a boolean declaration for the injected transport |
|
|
270
|
+
| `maxResponseBytes` | 64 MiB |
|
|
271
|
+
| `maxXmlBytes` | 2 MiB |
|
|
272
|
+
| `maxEntries` | 10,000 |
|
|
273
|
+
| `timeoutMs` | 30,000 |
|
|
274
|
+
| `overwritePolicy` | `lock`; alternative `etag` uses conditional overwrites |
|
|
275
|
+
| `atomicEmptyDirectory` | Optional trusted binding with the canonical `namespaceUrl` and `removeEmptyDirectory` callback; required for strict empty-only `rmdir` |
|
|
276
|
+
| `compareEntry` | Optional trusted backing-identity callback on Node; unavailable under browser policy |
|
|
277
|
+
|
|
278
|
+
See the [binding types](src/fs/webdav/webdav.ts) before implementing atomic directory removal. A recursive WebDAV DELETE does not satisfy that contract.
|
|
279
|
+
|
|
280
|
+
</details>
|
|
281
|
+
|
|
282
|
+
## Safety boundary and limitations
|
|
283
|
+
|
|
284
|
+
- **Not an OS sandbox.** Rooted storage and bridge confinement check paths and symlinks, but checks and host operations can race concurrent changes. Use a separate OS isolation boundary for hostile workloads. A custom adapter or transport is trusted code with its own host authority.
|
|
285
|
+
- **A read-only view is not an immutable store.** Other references can still change the backing filesystem. Overlays are not transactions; cancellation and cleanup do not guarantee rollback. Cross-mount rename can fail with `EXDEV`.
|
|
286
|
+
- **Remote storage is not a POSIX disk.** S3 and WebDAV do not provide hardlinks or symlinks. S3 rename is non-atomic and may leave partial copies/deletions; `S3RenameError` reports the phase and affected keys. Strong empty-only removal and conditional writes depend on backend support, not a prior listing.
|
|
287
|
+
- **The bridges are partial Node compatibility layers.** No synchronous/callback API, file handles, watchers, bigint stats, flush/retry support, or `cp` dereference/timestamp-preservation options. Missing optional operations fail rather than being approximated with destructive alternatives.
|
|
288
|
+
- **Browser support is filesystem-only.** The `browser` export condition selects the portable surface: memory, mounts, overlays, read-only, WebDAV, and the codec-based bridge. Real storage, S3, the Node bridge, and the configuration registry are not browser exports. WebDAV still needs server CORS support; no OPFS or directory-handle adapter is included. This does not make the SafeJS runtime browser-compatible.
|
|
289
|
+
- **Portable temporary names require secure randomness.** Browser overlay staging and portable bridge `mkdtemp` require `crypto.randomUUID` or `crypto.getRandomValues`; without either, they fail with `ENOTSUP` rather than use `Math.random`.
|
|
290
|
+
- **Allocation and identity may be unknown.** Optional `FileStat.allocatedBytes` is provider-reported allocation, not logical length or reclaimable space. Do not infer identity from size, timestamps, or inode numbers across unrelated backends.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { FileSystem, FsOptions } from "../contracts/filesystem.js";
|
|
2
|
+
export declare function assertBridgePath(root: string, path: string): void;
|
|
3
|
+
export declare function checkedBridgePath(fs: FileSystem, root: string, path: string, options: FsOptions, followFinal: boolean): Promise<string>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { FsError, isFsError } from "../contracts/errors.js";
|
|
2
|
+
import { dirname } from "../contracts/virtual-path.js";
|
|
3
|
+
import { checkSignal } from "./values.js";
|
|
4
|
+
function denied(path) {
|
|
5
|
+
throw new FsError("EACCES", { syscall: "resolve", path, message: "path escapes the bridge cwd" });
|
|
6
|
+
}
|
|
7
|
+
function relativeComponents(root, path) {
|
|
8
|
+
if (typeof path !== "string" || !path.startsWith("/") || path.includes("\0"))
|
|
9
|
+
denied(path);
|
|
10
|
+
const boundary = root.split("/").filter(Boolean);
|
|
11
|
+
const components = path.split("/").filter(Boolean);
|
|
12
|
+
let index = 0;
|
|
13
|
+
for (const component of boundary) {
|
|
14
|
+
while (components[index] === ".")
|
|
15
|
+
index++;
|
|
16
|
+
if (components[index] !== component)
|
|
17
|
+
denied(path);
|
|
18
|
+
index++;
|
|
19
|
+
}
|
|
20
|
+
const relative = components.slice(index);
|
|
21
|
+
let depth = 0;
|
|
22
|
+
for (const component of relative) {
|
|
23
|
+
if (component === ".")
|
|
24
|
+
continue;
|
|
25
|
+
if (component === "..") {
|
|
26
|
+
if (depth === 0)
|
|
27
|
+
denied(path);
|
|
28
|
+
depth--;
|
|
29
|
+
}
|
|
30
|
+
else
|
|
31
|
+
depth++;
|
|
32
|
+
}
|
|
33
|
+
if (path.endsWith("/"))
|
|
34
|
+
relative.push(".");
|
|
35
|
+
return relative;
|
|
36
|
+
}
|
|
37
|
+
export function assertBridgePath(root, path) {
|
|
38
|
+
if (root !== "/")
|
|
39
|
+
relativeComponents(root, path);
|
|
40
|
+
}
|
|
41
|
+
async function assertCanonicalPath(fs, root, path, options) {
|
|
42
|
+
checkSignal(options.signal);
|
|
43
|
+
const canonical = await fs.realpath(path, options);
|
|
44
|
+
checkSignal(options.signal);
|
|
45
|
+
assertBridgePath(root, canonical);
|
|
46
|
+
if (canonical !== path)
|
|
47
|
+
denied(path);
|
|
48
|
+
}
|
|
49
|
+
async function assertCanonicalOperand(fs, root, path, expected, options) {
|
|
50
|
+
let operand = path;
|
|
51
|
+
let target = expected;
|
|
52
|
+
while (true) {
|
|
53
|
+
checkSignal(options.signal);
|
|
54
|
+
try {
|
|
55
|
+
const canonical = await fs.realpath(operand, options);
|
|
56
|
+
checkSignal(options.signal);
|
|
57
|
+
assertBridgePath(root, canonical);
|
|
58
|
+
if (canonical !== target)
|
|
59
|
+
denied(path);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
checkSignal(options.signal);
|
|
64
|
+
if (!isFsError(error) || error.code !== "ENOENT")
|
|
65
|
+
throw error;
|
|
66
|
+
let missing = false;
|
|
67
|
+
try {
|
|
68
|
+
await fs.lstat(operand, options);
|
|
69
|
+
}
|
|
70
|
+
catch (inspectionError) {
|
|
71
|
+
checkSignal(options.signal);
|
|
72
|
+
if (!isFsError(inspectionError) || inspectionError.code !== "ENOENT")
|
|
73
|
+
throw inspectionError;
|
|
74
|
+
missing = true;
|
|
75
|
+
}
|
|
76
|
+
checkSignal(options.signal);
|
|
77
|
+
if (!missing || operand === root)
|
|
78
|
+
denied(path);
|
|
79
|
+
operand = dirname(operand);
|
|
80
|
+
target = dirname(target);
|
|
81
|
+
assertBridgePath(root, operand);
|
|
82
|
+
assertBridgePath(root, target);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export async function checkedBridgePath(fs, root, path, options, followFinal) {
|
|
87
|
+
if (root === "/")
|
|
88
|
+
return path;
|
|
89
|
+
let pending = relativeComponents(root, path);
|
|
90
|
+
let current = "";
|
|
91
|
+
for (const component of root.split("/").filter(Boolean)) {
|
|
92
|
+
current += `/${component}`;
|
|
93
|
+
checkSignal(options.signal);
|
|
94
|
+
try {
|
|
95
|
+
const stat = await fs.lstat(current, options);
|
|
96
|
+
checkSignal(options.signal);
|
|
97
|
+
if (stat.type === "symlink")
|
|
98
|
+
denied(current);
|
|
99
|
+
if (stat.type !== "directory")
|
|
100
|
+
throw new FsError("ENOTDIR", { syscall: "resolve", path: current });
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
checkSignal(options.signal);
|
|
104
|
+
if (!isFsError(error) || error.code !== "ENOENT")
|
|
105
|
+
throw error;
|
|
106
|
+
if (current !== root)
|
|
107
|
+
denied(path);
|
|
108
|
+
if (pending.includes(".."))
|
|
109
|
+
throw error;
|
|
110
|
+
const parent = dirname(root);
|
|
111
|
+
await assertCanonicalPath(fs, parent, parent, options);
|
|
112
|
+
return path;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
await assertCanonicalPath(fs, root, root, options);
|
|
116
|
+
let links = 0;
|
|
117
|
+
let finalLink = false;
|
|
118
|
+
let index = 0;
|
|
119
|
+
while (index < pending.length) {
|
|
120
|
+
const component = pending[index++];
|
|
121
|
+
checkSignal(options.signal);
|
|
122
|
+
if (component === ".")
|
|
123
|
+
continue;
|
|
124
|
+
if (component === "..") {
|
|
125
|
+
if (current === root)
|
|
126
|
+
denied(path);
|
|
127
|
+
current = dirname(current);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const candidate = `${current}/${component}`;
|
|
131
|
+
let stat;
|
|
132
|
+
try {
|
|
133
|
+
stat = await fs.lstat(candidate, options);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
checkSignal(options.signal);
|
|
137
|
+
if (!isFsError(error) || error.code !== "ENOENT")
|
|
138
|
+
throw error;
|
|
139
|
+
if (pending.slice(index).includes(".."))
|
|
140
|
+
throw error;
|
|
141
|
+
await assertCanonicalPath(fs, root, current, options);
|
|
142
|
+
const expected = [candidate, ...pending.slice(index)].join("/");
|
|
143
|
+
await assertCanonicalOperand(fs, root, path, expected, options);
|
|
144
|
+
return path;
|
|
145
|
+
}
|
|
146
|
+
checkSignal(options.signal);
|
|
147
|
+
if (stat.type === "symlink" && (followFinal || index < pending.length)) {
|
|
148
|
+
if (++links > 40)
|
|
149
|
+
throw new FsError("ELOOP", { syscall: "resolve", path });
|
|
150
|
+
if (typeof fs.readlink !== "function")
|
|
151
|
+
throw new FsError("ENOTSUP", { syscall: "readlink", path: candidate });
|
|
152
|
+
const target = await fs.readlink(candidate, options);
|
|
153
|
+
checkSignal(options.signal);
|
|
154
|
+
if (typeof target !== "string" || target.length === 0 || target.includes("\0"))
|
|
155
|
+
denied(candidate);
|
|
156
|
+
const absolute = target.startsWith("/") ? target : `${current}/${target}`;
|
|
157
|
+
pending = [...relativeComponents(root, absolute), ...pending.slice(index)];
|
|
158
|
+
index = 0;
|
|
159
|
+
current = root;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (index < pending.length && stat.type !== "directory") {
|
|
163
|
+
throw new FsError("ENOTDIR", { syscall: "resolve", path: candidate });
|
|
164
|
+
}
|
|
165
|
+
current = candidate;
|
|
166
|
+
finalLink = stat.type === "symlink";
|
|
167
|
+
}
|
|
168
|
+
await assertCanonicalPath(fs, root, finalLink ? dirname(current) : current, options);
|
|
169
|
+
await assertCanonicalOperand(fs, root, finalLink ? dirname(path) : path, finalLink ? dirname(current) : current, options);
|
|
170
|
+
return path;
|
|
171
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { FileSystem } from "../contracts/filesystem.js";
|
|
2
|
+
import type { BridgePrimitives, BufferEncodingOption, FsBridgeDirent, FsBridgeEncoding, FsBridgeStats, MakeDirectoryOptions, Mode, ObjectEncodingOptions, StatOptions } from "./types.js";
|
|
3
|
+
type Encoding = FsBridgeEncoding | "buffer";
|
|
4
|
+
type ReadOptions = ObjectEncodingOptions & {
|
|
5
|
+
flag?: string | number | undefined;
|
|
6
|
+
signal?: AbortSignal | undefined;
|
|
7
|
+
};
|
|
8
|
+
type DirectoryOptions = {
|
|
9
|
+
encoding?: Encoding | null | undefined;
|
|
10
|
+
recursive?: boolean | undefined;
|
|
11
|
+
withFileTypes?: boolean | undefined;
|
|
12
|
+
};
|
|
13
|
+
export declare class FileSystemBridge<Binary extends Uint8Array> {
|
|
14
|
+
#private;
|
|
15
|
+
constructor(fs: FileSystem, options: {
|
|
16
|
+
readonly cwd?: string;
|
|
17
|
+
readonly signal?: AbortSignal;
|
|
18
|
+
}, primitives: BridgePrimitives<Binary>);
|
|
19
|
+
readFile(path: unknown, options?: {
|
|
20
|
+
encoding?: null | undefined;
|
|
21
|
+
flag?: string | number | undefined;
|
|
22
|
+
signal?: AbortSignal | undefined;
|
|
23
|
+
} | null): Promise<Binary>;
|
|
24
|
+
readFile(path: unknown, options: FsBridgeEncoding | (ReadOptions & {
|
|
25
|
+
encoding: FsBridgeEncoding;
|
|
26
|
+
})): Promise<string>;
|
|
27
|
+
readFile(path: unknown, options?: ReadOptions | FsBridgeEncoding | null): Promise<string | Binary>;
|
|
28
|
+
writeFile(path: unknown, data: unknown, options?: unknown): Promise<void>;
|
|
29
|
+
appendFile(path: unknown, data: unknown, options?: unknown): Promise<void>;
|
|
30
|
+
stat(path: unknown, options?: StatOptions & {
|
|
31
|
+
bigint?: false | undefined;
|
|
32
|
+
}): Promise<FsBridgeStats>;
|
|
33
|
+
stat(path: unknown, options: StatOptions & {
|
|
34
|
+
bigint: true;
|
|
35
|
+
}): Promise<never>;
|
|
36
|
+
stat(path: unknown, options?: StatOptions): Promise<FsBridgeStats>;
|
|
37
|
+
lstat(path: unknown, options?: StatOptions & {
|
|
38
|
+
bigint?: false | undefined;
|
|
39
|
+
}): Promise<FsBridgeStats>;
|
|
40
|
+
lstat(path: unknown, options: StatOptions & {
|
|
41
|
+
bigint: true;
|
|
42
|
+
}): Promise<never>;
|
|
43
|
+
lstat(path: unknown, options?: StatOptions): Promise<FsBridgeStats>;
|
|
44
|
+
readdir(path: unknown, options?: FsBridgeEncoding | (ObjectEncodingOptions & {
|
|
45
|
+
withFileTypes?: false | undefined;
|
|
46
|
+
recursive?: boolean | undefined;
|
|
47
|
+
}) | null): Promise<string[]>;
|
|
48
|
+
readdir(path: unknown, options: "buffer" | (DirectoryOptions & {
|
|
49
|
+
encoding: "buffer";
|
|
50
|
+
withFileTypes?: false | undefined;
|
|
51
|
+
})): Promise<Binary[]>;
|
|
52
|
+
readdir(path: unknown, options: ObjectEncodingOptions & {
|
|
53
|
+
withFileTypes: true;
|
|
54
|
+
recursive?: boolean | undefined;
|
|
55
|
+
}): Promise<FsBridgeDirent[]>;
|
|
56
|
+
readdir(path: unknown, options: DirectoryOptions & {
|
|
57
|
+
encoding: "buffer";
|
|
58
|
+
withFileTypes: true;
|
|
59
|
+
}): Promise<FsBridgeDirent<Binary>[]>;
|
|
60
|
+
mkdir(path: unknown, options: MakeDirectoryOptions & {
|
|
61
|
+
recursive: true;
|
|
62
|
+
}): Promise<string | undefined>;
|
|
63
|
+
mkdir(path: unknown, options?: Mode | (MakeDirectoryOptions & {
|
|
64
|
+
recursive?: false | undefined;
|
|
65
|
+
}) | null): Promise<void>;
|
|
66
|
+
mkdir(path: unknown, options?: Mode | MakeDirectoryOptions | null): Promise<string | undefined>;
|
|
67
|
+
access(path: unknown, mode?: number): Promise<void>;
|
|
68
|
+
rm(path: unknown, value?: unknown): Promise<void>;
|
|
69
|
+
rmdir(path: unknown, value?: unknown): Promise<void>;
|
|
70
|
+
rename(source: unknown, destination: unknown): Promise<void>;
|
|
71
|
+
copyFile(source: unknown, destination: unknown, mode?: number): Promise<void>;
|
|
72
|
+
cp(source: unknown, destination: unknown, value?: unknown): Promise<void>;
|
|
73
|
+
readlink(path: unknown, options?: ObjectEncodingOptions | FsBridgeEncoding | null): Promise<string>;
|
|
74
|
+
readlink(path: unknown, options: BufferEncodingOption): Promise<Binary>;
|
|
75
|
+
readlink(path: unknown, options?: ObjectEncodingOptions | BufferEncodingOption | string | null): Promise<string | Binary>;
|
|
76
|
+
realpath(path: unknown, options?: ObjectEncodingOptions | FsBridgeEncoding | null): Promise<string>;
|
|
77
|
+
realpath(path: unknown, options: BufferEncodingOption): Promise<Binary>;
|
|
78
|
+
realpath(path: unknown, options?: ObjectEncodingOptions | BufferEncodingOption | string | null): Promise<string | Binary>;
|
|
79
|
+
mkdtemp(prefix: string, options?: ObjectEncodingOptions | FsBridgeEncoding | null): Promise<string>;
|
|
80
|
+
mkdtemp(prefix: string, options: BufferEncodingOption): Promise<Binary>;
|
|
81
|
+
mkdtemp(prefix: string, options?: ObjectEncodingOptions | BufferEncodingOption | string | null): Promise<string | Binary>;
|
|
82
|
+
symlink(target: unknown, path: unknown, type?: unknown): Promise<void>;
|
|
83
|
+
link(existing: unknown, path: unknown): Promise<void>;
|
|
84
|
+
chmod(path: unknown, value: unknown): Promise<void>;
|
|
85
|
+
utimes(path: unknown, atime: unknown, mtime: unknown): Promise<void>;
|
|
86
|
+
truncate(path: unknown, length?: number): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
export {};
|