@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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +290 -0
  3. package/dist/safe-fs/bridge/confinement.d.ts +3 -0
  4. package/dist/safe-fs/bridge/confinement.js +171 -0
  5. package/dist/safe-fs/bridge/filesystem.d.ts +88 -0
  6. package/dist/safe-fs/bridge/filesystem.js +411 -0
  7. package/dist/safe-fs/bridge/index.d.ts +6 -0
  8. package/dist/safe-fs/bridge/index.js +18 -0
  9. package/dist/safe-fs/bridge/stats.d.ts +4 -0
  10. package/dist/safe-fs/bridge/stats.js +37 -0
  11. package/dist/safe-fs/bridge/types.d.ts +75 -0
  12. package/dist/safe-fs/bridge/types.js +1 -0
  13. package/dist/safe-fs/bridge/values.d.ts +6 -0
  14. package/dist/safe-fs/bridge/values.js +65 -0
  15. package/dist/safe-fs/config/memory.d.ts +3 -0
  16. package/dist/safe-fs/config/memory.js +9 -0
  17. package/dist/safe-fs/config/real.d.ts +5 -0
  18. package/dist/safe-fs/config/real.js +16 -0
  19. package/dist/safe-fs/config.d.ts +15 -0
  20. package/dist/safe-fs/config.js +46 -0
  21. package/dist/safe-fs/config.node.d.ts +2 -0
  22. package/dist/safe-fs/config.node.js +22 -0
  23. package/dist/safe-fs/contracts/abort.d.ts +5 -0
  24. package/dist/safe-fs/contracts/abort.js +34 -0
  25. package/dist/safe-fs/contracts/cleanup.d.ts +1 -0
  26. package/dist/safe-fs/contracts/cleanup.js +9 -0
  27. package/dist/safe-fs/contracts/errors.d.ts +50 -0
  28. package/dist/safe-fs/contracts/errors.js +77 -0
  29. package/dist/safe-fs/contracts/filesystem.d.ts +96 -0
  30. package/dist/safe-fs/contracts/filesystem.js +1 -0
  31. package/dist/safe-fs/contracts/index.d.ts +4 -0
  32. package/dist/safe-fs/contracts/index.js +4 -0
  33. package/dist/safe-fs/contracts/io.d.ts +8 -0
  34. package/dist/safe-fs/contracts/io.js +92 -0
  35. package/dist/safe-fs/contracts/path.d.ts +8 -0
  36. package/dist/safe-fs/contracts/path.js +8 -0
  37. package/dist/safe-fs/contracts/portable-path.d.ts +13 -0
  38. package/dist/safe-fs/contracts/portable-path.js +57 -0
  39. package/dist/safe-fs/contracts/virtual-path.d.ts +7 -0
  40. package/dist/safe-fs/contracts/virtual-path.js +66 -0
  41. package/dist/safe-fs/core.d.ts +12 -0
  42. package/dist/safe-fs/core.js +12 -0
  43. package/dist/safe-fs/fs/memory/index.d.ts +58 -0
  44. package/dist/safe-fs/fs/memory/index.js +532 -0
  45. package/dist/safe-fs/fs/mount/comparison.d.ts +19 -0
  46. package/dist/safe-fs/fs/mount/comparison.js +118 -0
  47. package/dist/safe-fs/fs/mount/identity.d.ts +2 -0
  48. package/dist/safe-fs/fs/mount/identity.js +12 -0
  49. package/dist/safe-fs/fs/mount/index.d.ts +44 -0
  50. package/dist/safe-fs/fs/mount/index.js +546 -0
  51. package/dist/safe-fs/fs/overlay/index.d.ts +73 -0
  52. package/dist/safe-fs/fs/overlay/index.js +927 -0
  53. package/dist/safe-fs/fs/readonly/index.d.ts +31 -0
  54. package/dist/safe-fs/fs/readonly/index.js +128 -0
  55. package/dist/safe-fs/fs/real/allocation.d.ts +1 -0
  56. package/dist/safe-fs/fs/real/allocation.js +8 -0
  57. package/dist/safe-fs/fs/real/index.d.ts +76 -0
  58. package/dist/safe-fs/fs/real/index.js +543 -0
  59. package/dist/safe-fs/fs/s3/authority.d.ts +7 -0
  60. package/dist/safe-fs/fs/s3/authority.js +31 -0
  61. package/dist/safe-fs/fs/s3/filesystem.d.ts +93 -0
  62. package/dist/safe-fs/fs/s3/filesystem.js +1024 -0
  63. package/dist/safe-fs/fs/s3/http/index.d.ts +2 -0
  64. package/dist/safe-fs/fs/s3/http/index.js +1 -0
  65. package/dist/safe-fs/fs/s3/http/request.d.ts +16 -0
  66. package/dist/safe-fs/fs/s3/http/request.js +196 -0
  67. package/dist/safe-fs/fs/s3/http/signature.d.ts +20 -0
  68. package/dist/safe-fs/fs/s3/http/signature.js +74 -0
  69. package/dist/safe-fs/fs/s3/http/transport.d.ts +3 -0
  70. package/dist/safe-fs/fs/s3/http/transport.js +455 -0
  71. package/dist/safe-fs/fs/s3/http/types.d.ts +30 -0
  72. package/dist/safe-fs/fs/s3/http/types.js +1 -0
  73. package/dist/safe-fs/fs/s3/http/xml.d.ts +11 -0
  74. package/dist/safe-fs/fs/s3/http/xml.js +188 -0
  75. package/dist/safe-fs/fs/s3/index.d.ts +6 -0
  76. package/dist/safe-fs/fs/s3/index.js +3 -0
  77. package/dist/safe-fs/fs/s3/mock.d.ts +48 -0
  78. package/dist/safe-fs/fs/s3/mock.js +233 -0
  79. package/dist/safe-fs/fs/s3/registry.d.ts +10 -0
  80. package/dist/safe-fs/fs/s3/registry.js +71 -0
  81. package/dist/safe-fs/fs/s3/transport.d.ts +103 -0
  82. package/dist/safe-fs/fs/s3/transport.js +28 -0
  83. package/dist/safe-fs/fs/webdav/index.d.ts +2 -0
  84. package/dist/safe-fs/fs/webdav/index.js +1 -0
  85. package/dist/safe-fs/fs/webdav/resource-id.d.ts +18 -0
  86. package/dist/safe-fs/fs/webdav/resource-id.js +109 -0
  87. package/dist/safe-fs/fs/webdav/webdav.d.ts +101 -0
  88. package/dist/safe-fs/fs/webdav/webdav.js +1226 -0
  89. package/dist/safe-fs/fs/webdav/xml.d.ts +15 -0
  90. package/dist/safe-fs/fs/webdav/xml.js +250 -0
  91. package/dist/safe-fs/index.d.ts +14 -0
  92. package/dist/safe-fs/index.js +12 -0
  93. package/dist/safe-fs/node/filesystem.d.ts +11 -0
  94. package/dist/safe-fs/node/filesystem.js +28 -0
  95. package/dist/safe-fs/node/index.d.ts +2 -0
  96. package/dist/safe-fs/node/index.js +1 -0
  97. package/dist/safe-fs/node/stats.d.ts +1 -0
  98. package/dist/safe-fs/node/stats.js +1 -0
  99. package/dist/safe-fs/node/values.d.ts +1 -0
  100. package/dist/safe-fs/node/values.js +1 -0
  101. package/dist/safe-fs/node-host.d.ts +1 -0
  102. package/dist/safe-fs/node-host.js +1 -0
  103. package/dist/safe-fs/node-unavailable.d.ts +1 -0
  104. package/dist/safe-fs/node-unavailable.js +1 -0
  105. package/dist/safe-fs/platform/browser.d.ts +12 -0
  106. package/dist/safe-fs/platform/browser.js +39 -0
  107. package/dist/safe-fs/platform/node.d.ts +13 -0
  108. package/dist/safe-fs/platform/node.js +26 -0
  109. package/package.json +108 -0
@@ -0,0 +1,15 @@
1
+ import type { FileSystem, FileSystemFactory } from "./contracts/filesystem.js";
2
+ export type FileSystemConfig = {
3
+ readonly type: string;
4
+ readonly options?: Readonly<Record<string, unknown>>;
5
+ };
6
+ export type FileSystemAdapterDescriptor = {
7
+ readonly validateOptions: (options: Readonly<Record<string, unknown>>) => void;
8
+ readonly create: FileSystemFactory;
9
+ };
10
+ export type FileSystemAdapterRegistry = ReadonlyMap<string, FileSystemAdapterDescriptor>;
11
+ export declare function readConfigRecord(value: unknown, label: string, keys?: readonly string[]): Readonly<Record<string, unknown>>;
12
+ export declare function validateFileSystemConfig(value: unknown): FileSystemConfig;
13
+ export declare function createFileSystem(config: FileSystemConfig, options: {
14
+ registry: FileSystemAdapterRegistry;
15
+ }): Promise<FileSystem>;
@@ -0,0 +1,46 @@
1
+ export function readConfigRecord(value, label, keys) {
2
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
3
+ throw new TypeError(`${label} must be a record.`);
4
+ }
5
+ const prototype = Object.getPrototypeOf(value);
6
+ if (prototype !== Object.prototype && prototype !== null) {
7
+ throw new TypeError(`${label} must be a plain record.`);
8
+ }
9
+ const record = Object.create(null);
10
+ for (const key of Reflect.ownKeys(value)) {
11
+ if (typeof key !== "string" || (keys !== undefined && !keys.includes(key))) {
12
+ throw new TypeError(`Unknown ${label}: ${String(key)}`);
13
+ }
14
+ const property = Object.getOwnPropertyDescriptor(value, key);
15
+ if (!("value" in property))
16
+ throw new TypeError(`${label}.${key} must be a data property.`);
17
+ record[key] = property.value;
18
+ }
19
+ return Object.freeze(record);
20
+ }
21
+ export function validateFileSystemConfig(value) {
22
+ const config = readConfigRecord(value, "filesystem config", ["type", "options"]);
23
+ if (typeof config.type !== "string" || config.type.trim().length === 0) {
24
+ throw new TypeError("Filesystem adapter type must be a nonempty string.");
25
+ }
26
+ return {
27
+ type: config.type,
28
+ ...(config.options === undefined
29
+ ? {}
30
+ : { options: readConfigRecord(config.options, "filesystem options") })
31
+ };
32
+ }
33
+ export async function createFileSystem(config, options) {
34
+ const validated = validateFileSystemConfig(config);
35
+ const creation = readConfigRecord(options, "filesystem creation option", ["registry"]);
36
+ const registry = creation.registry;
37
+ if (registry === undefined || registry === null || typeof registry.get !== "function") {
38
+ throw new TypeError("registry must be a filesystem adapter map.");
39
+ }
40
+ const descriptor = registry.get(validated.type);
41
+ if (descriptor === undefined)
42
+ throw new TypeError(`Unknown filesystem adapter: ${validated.type}`);
43
+ const adapterOptions = validated.options ?? Object.freeze(Object.create(null));
44
+ descriptor.validateOptions(adapterOptions);
45
+ return await descriptor.create(adapterOptions);
46
+ }
@@ -0,0 +1,2 @@
1
+ import type { FileSystemAdapterRegistry } from "./config.js";
2
+ export declare function createNodeFileSystemAdapterRegistry(extensions?: FileSystemAdapterRegistry): FileSystemAdapterRegistry;
@@ -0,0 +1,22 @@
1
+ import { createMemoryFileSystemAdapter } from "./config/memory.js";
2
+ import { createRealFileSystemAdapter } from "./config/real.js";
3
+ import { createMemoryFileSystem } from "./fs/memory/index.js";
4
+ import { createRealFileSystem } from "./fs/real/index.js";
5
+ export function createNodeFileSystemAdapterRegistry(extensions) {
6
+ if (extensions !== undefined &&
7
+ (extensions === null ||
8
+ typeof extensions.get !== "function" ||
9
+ typeof extensions[Symbol.iterator] !== "function")) {
10
+ throw new TypeError("registry must be a filesystem adapter map.");
11
+ }
12
+ const registry = new Map([
13
+ ["memory", createMemoryFileSystemAdapter(createMemoryFileSystem)],
14
+ ["real", createRealFileSystemAdapter(createRealFileSystem)]
15
+ ]);
16
+ for (const [name, descriptor] of extensions ?? []) {
17
+ if (registry.has(name))
18
+ throw new TypeError(`Filesystem adapter already registered: ${name}`);
19
+ registry.set(name, descriptor);
20
+ }
21
+ return registry;
22
+ }
@@ -0,0 +1,5 @@
1
+ export interface AbortSignalScope {
2
+ readonly signal: AbortSignal;
3
+ dispose(): void;
4
+ }
5
+ export declare function composeAbortSignals(signals: readonly AbortSignal[]): AbortSignalScope;
@@ -0,0 +1,34 @@
1
+ export function composeAbortSignals(signals) {
2
+ const controller = new AbortController();
3
+ const listeners = new Map();
4
+ const dispose = () => {
5
+ for (const [signal, listener] of listeners)
6
+ signal.removeEventListener("abort", listener);
7
+ listeners.clear();
8
+ };
9
+ for (const signal of signals) {
10
+ if (signal.aborted) {
11
+ controller.abort(signal.reason);
12
+ return { signal: controller.signal, dispose };
13
+ }
14
+ }
15
+ try {
16
+ for (const signal of new Set(signals)) {
17
+ const listener = () => {
18
+ dispose();
19
+ controller.abort(signal.reason);
20
+ };
21
+ listeners.set(signal, listener);
22
+ signal.addEventListener("abort", listener, { once: true });
23
+ if (signal.aborted)
24
+ listener();
25
+ if (controller.signal.aborted)
26
+ break;
27
+ }
28
+ }
29
+ catch (error) {
30
+ dispose();
31
+ throw error;
32
+ }
33
+ return { signal: controller.signal, dispose };
34
+ }
@@ -0,0 +1 @@
1
+ export declare function finishCleanup(cleanup: () => unknown, failed: boolean): Promise<void>;
@@ -0,0 +1,9 @@
1
+ export async function finishCleanup(cleanup, failed) {
2
+ try {
3
+ await cleanup();
4
+ }
5
+ catch (error) {
6
+ if (!failed)
7
+ throw error;
8
+ }
9
+ }
@@ -0,0 +1,50 @@
1
+ import type { PlatformErrno } from "#safe-fs-platform";
2
+ export type { PlatformErrno } from "#safe-fs-platform";
3
+ declare const descriptions: {
4
+ readonly EACCES: "permission denied";
5
+ readonly EAGAIN: "resource temporarily unavailable";
6
+ readonly EBADF: "bad file descriptor";
7
+ readonly EBUSY: "resource busy or locked";
8
+ readonly ECANCELED: "operation canceled";
9
+ readonly EEXIST: "file already exists";
10
+ readonly EFBIG: "file too large";
11
+ readonly EINTR: "interrupted system call";
12
+ readonly EINVAL: "invalid argument";
13
+ readonly EIO: "input/output error";
14
+ readonly EISDIR: "illegal operation on a directory";
15
+ readonly ELOOP: "too many symbolic links encountered";
16
+ readonly EMFILE: "too many open files";
17
+ readonly ENAMETOOLONG: "name too long";
18
+ readonly ENFILE: "file table overflow";
19
+ readonly ENOENT: "no such file or directory";
20
+ readonly ENOMEM: "not enough memory";
21
+ readonly ENOSPC: "no space left on device";
22
+ readonly ENOSYS: "function not implemented";
23
+ readonly ENOTDIR: "not a directory";
24
+ readonly ENOTEMPTY: "directory not empty";
25
+ readonly ENOTSUP: "operation not supported";
26
+ readonly EOPNOTSUPP: "operation not supported";
27
+ readonly EPERM: "operation not permitted";
28
+ readonly EPIPE: "broken pipe";
29
+ readonly EROFS: "read-only file system";
30
+ readonly ETIMEDOUT: "operation timed out";
31
+ readonly EXDEV: "cross-device link not permitted";
32
+ };
33
+ export type ErrnoCode = keyof typeof descriptions;
34
+ export interface FsErrorOptions extends ErrorOptions {
35
+ readonly syscall?: string;
36
+ readonly path?: string;
37
+ readonly dest?: string;
38
+ readonly message?: string;
39
+ }
40
+ export declare class FsError extends Error {
41
+ readonly code: ErrnoCode;
42
+ readonly errno: PlatformErrno;
43
+ readonly syscall?: string;
44
+ readonly path?: string;
45
+ readonly dest?: string;
46
+ constructor(code: ErrnoCode, options?: FsErrorOptions);
47
+ }
48
+ export declare function isErrnoCode(code: unknown): code is ErrnoCode;
49
+ export declare function isFsError(error: unknown, code?: ErrnoCode): error is FsError;
50
+ export declare function toFsError(error: unknown, options?: FsErrorOptions): FsError;
@@ -0,0 +1,77 @@
1
+ import { platform } from "#safe-fs-platform";
2
+ const descriptions = {
3
+ EACCES: "permission denied",
4
+ EAGAIN: "resource temporarily unavailable",
5
+ EBADF: "bad file descriptor",
6
+ EBUSY: "resource busy or locked",
7
+ ECANCELED: "operation canceled",
8
+ EEXIST: "file already exists",
9
+ EFBIG: "file too large",
10
+ EINTR: "interrupted system call",
11
+ EINVAL: "invalid argument",
12
+ EIO: "input/output error",
13
+ EISDIR: "illegal operation on a directory",
14
+ ELOOP: "too many symbolic links encountered",
15
+ EMFILE: "too many open files",
16
+ ENAMETOOLONG: "name too long",
17
+ ENFILE: "file table overflow",
18
+ ENOENT: "no such file or directory",
19
+ ENOMEM: "not enough memory",
20
+ ENOSPC: "no space left on device",
21
+ ENOSYS: "function not implemented",
22
+ ENOTDIR: "not a directory",
23
+ ENOTEMPTY: "directory not empty",
24
+ ENOTSUP: "operation not supported",
25
+ EOPNOTSUPP: "operation not supported",
26
+ EPERM: "operation not permitted",
27
+ EPIPE: "broken pipe",
28
+ EROFS: "read-only file system",
29
+ ETIMEDOUT: "operation timed out",
30
+ EXDEV: "cross-device link not permitted",
31
+ };
32
+ export class FsError extends Error {
33
+ code;
34
+ errno;
35
+ syscall;
36
+ path;
37
+ dest;
38
+ constructor(code, options = {}) {
39
+ if (!isErrnoCode(code))
40
+ throw new TypeError(`Unsupported errno code: ${String(code)}`);
41
+ const errno = platform.errno(code);
42
+ const operation = options.syscall ? `, ${options.syscall}` : "";
43
+ const path = options.path === undefined ? "" : ` '${options.path}'`;
44
+ const destination = options.dest === undefined ? "" : ` -> '${options.dest}'`;
45
+ super(`${code}: ${options.message ?? descriptions[code]}${operation}${path}${destination}`, options);
46
+ this.name = "FsError";
47
+ this.code = code;
48
+ this.errno = errno;
49
+ if (options.syscall !== undefined)
50
+ this.syscall = options.syscall;
51
+ if (options.path !== undefined)
52
+ this.path = options.path;
53
+ if (options.dest !== undefined)
54
+ this.dest = options.dest;
55
+ }
56
+ }
57
+ export function isErrnoCode(code) {
58
+ return typeof code === "string" && Object.hasOwn(descriptions, code);
59
+ }
60
+ export function isFsError(error, code) {
61
+ return error instanceof FsError && (code === undefined || error.code === code);
62
+ }
63
+ export function toFsError(error, options = {}) {
64
+ if (error instanceof FsError && Object.keys(options).length === 0)
65
+ return error;
66
+ const source = typeof error === "object" && error !== null
67
+ ? error
68
+ : {};
69
+ const details = {
70
+ ...(typeof source.syscall === "string" ? { syscall: source.syscall } : {}),
71
+ ...(typeof source.path === "string" ? { path: source.path } : {}),
72
+ ...(typeof source.dest === "string" ? { dest: source.dest } : {}),
73
+ cause: error,
74
+ ...options,
75
+ };
76
+ return new FsError(isErrnoCode(source.code) ? source.code : "EIO", details);
77
+ }
@@ -0,0 +1,96 @@
1
+ import type { ByteSource } from "./io.js";
2
+ export type FileType = "file" | "directory" | "symlink";
3
+ export type EntryComparison = "same" | "distinct" | "unknown";
4
+ export interface FileStat {
5
+ readonly type: FileType;
6
+ readonly size: number;
7
+ readonly allocatedBytes?: number;
8
+ readonly mode: number;
9
+ readonly mtimeMs: number;
10
+ readonly atimeMs: number;
11
+ readonly ctimeMs: number;
12
+ readonly birthtimeMs?: number;
13
+ readonly identityScope?: object | symbol;
14
+ readonly ino?: number;
15
+ readonly dev?: number;
16
+ readonly nlink?: number;
17
+ readonly uid?: number;
18
+ readonly gid?: number;
19
+ }
20
+ export interface DirectoryEntry {
21
+ readonly name: string;
22
+ readonly type: FileType;
23
+ }
24
+ export interface FileSystemCapabilities {
25
+ readonly readOnly?: boolean;
26
+ readonly symlinks?: boolean;
27
+ readonly hardlinks?: boolean;
28
+ readonly permissions?: boolean;
29
+ readonly timestamps?: boolean;
30
+ readonly atomicRename?: boolean;
31
+ readonly snapshotRmdir?: boolean;
32
+ readonly streamingRead?: boolean;
33
+ readonly streamingWrite?: boolean;
34
+ readonly [capability: string]: boolean | undefined;
35
+ }
36
+ export interface FsOptions {
37
+ readonly signal?: AbortSignal;
38
+ }
39
+ export interface ReadFileOptions extends FsOptions {
40
+ readonly maxBytes?: number;
41
+ }
42
+ export interface WriteFileOptions extends FsOptions {
43
+ readonly flag?: "w" | "wx" | "a" | "ax";
44
+ readonly mode?: number;
45
+ }
46
+ export interface AppendFileOptions extends FsOptions {
47
+ readonly mode?: number;
48
+ }
49
+ export interface MkdirOptions extends FsOptions {
50
+ readonly recursive?: boolean;
51
+ readonly mode?: number;
52
+ }
53
+ export interface RemoveOptions extends FsOptions {
54
+ readonly recursive?: boolean;
55
+ readonly force?: boolean;
56
+ }
57
+ export interface CopyFileOptions extends FsOptions {
58
+ readonly exclusive?: boolean;
59
+ }
60
+ export interface ReadStreamOptions extends FsOptions {
61
+ readonly start?: number;
62
+ readonly endExclusive?: number;
63
+ readonly chunkSize?: number;
64
+ }
65
+ export interface FileSystem {
66
+ readonly capabilities: FileSystemCapabilities;
67
+ readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
68
+ writeFile(path: string, data: Uint8Array, options?: WriteFileOptions): Promise<void>;
69
+ appendFile(path: string, data: Uint8Array, options?: AppendFileOptions): Promise<void>;
70
+ stat(path: string, options?: FsOptions): Promise<FileStat>;
71
+ lstat(path: string, options?: FsOptions): Promise<FileStat>;
72
+ compareEntry?(path: string, peer: FileSystem, peerPath: string, options?: FsOptions): Promise<EntryComparison>;
73
+ readdir(path: string, options?: FsOptions): Promise<DirectoryEntry[]>;
74
+ mkdir(path: string, options?: MkdirOptions): Promise<void>;
75
+ rm(path: string, options?: RemoveOptions): Promise<void>;
76
+ rmdir?(path: string, options?: FsOptions): Promise<void>;
77
+ rename(source: string, destination: string, options?: FsOptions): Promise<void>;
78
+ copyFile(source: string, destination: string, options?: CopyFileOptions): Promise<void>;
79
+ realpath(path: string, options?: FsOptions): Promise<string>;
80
+ access(path: string, mode?: number, options?: FsOptions): Promise<void>;
81
+ readlink?(path: string, options?: FsOptions): Promise<string>;
82
+ symlink?(target: string, path: string, options?: FsOptions): Promise<void>;
83
+ link?(existingPath: string, newPath: string, options?: FsOptions): Promise<void>;
84
+ chmod?(path: string, mode: number, options?: FsOptions): Promise<void>;
85
+ utimes?(path: string, atimeMs: number, mtimeMs: number, options?: FsOptions): Promise<void>;
86
+ truncate?(path: string, length?: number, options?: FsOptions): Promise<void>;
87
+ readStream?(path: string, options?: ReadStreamOptions): ByteSource;
88
+ writeStream?(path: string, source: ByteSource, options?: WriteFileOptions): Promise<void>;
89
+ }
90
+ export type FileSystemFactory = (options: Readonly<Record<string, unknown>>) => FileSystem | Promise<FileSystem>;
91
+ export declare const ACCESS_MODES: Readonly<{
92
+ F_OK: 0;
93
+ R_OK: 4;
94
+ W_OK: 2;
95
+ X_OK: 1;
96
+ }>;
@@ -0,0 +1 @@
1
+ export const ACCESS_MODES = Object.freeze({ F_OK: 0, R_OK: 4, W_OK: 2, X_OK: 1 });
@@ -0,0 +1,4 @@
1
+ export * from "./errors.js";
2
+ export * from "./filesystem.js";
3
+ export * from "./io.js";
4
+ export * from "./path.js";
@@ -0,0 +1,4 @@
1
+ export * from "./errors.js";
2
+ export * from "./filesystem.js";
3
+ export * from "./io.js";
4
+ export * from "./path.js";
@@ -0,0 +1,8 @@
1
+ export type ByteSource = AsyncIterable<Uint8Array>;
2
+ export interface CollectOptions {
3
+ readonly maxBytes: number;
4
+ readonly signal?: AbortSignal;
5
+ }
6
+ export declare function toByteSource(input: string | Uint8Array): ByteSource;
7
+ export declare function collectBytes(source: ByteSource, options: CollectOptions): Promise<Uint8Array>;
8
+ export declare function readBytes(source: ByteSource, signal?: AbortSignal): AsyncGenerator<Uint8Array>;
@@ -0,0 +1,92 @@
1
+ import { FsError } from "./errors.js";
2
+ import { finishCleanup } from "./cleanup.js";
3
+ export function toByteSource(input) {
4
+ if (typeof input !== "string" && !(input instanceof Uint8Array)) {
5
+ throw new TypeError("Byte source input must be a string or Uint8Array");
6
+ }
7
+ const bytes = typeof input === "string" ? new TextEncoder().encode(input) : new Uint8Array(input);
8
+ return (async function* () {
9
+ if (bytes.byteLength > 0)
10
+ yield bytes;
11
+ })();
12
+ }
13
+ export async function collectBytes(source, options) {
14
+ if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
15
+ throw new RangeError("maxBytes must be a nonnegative safe integer");
16
+ }
17
+ const chunks = [];
18
+ let size = 0;
19
+ options.signal?.throwIfAborted();
20
+ for await (const chunk of readBytes(source, options.signal)) {
21
+ if (chunk.byteLength > options.maxBytes - size) {
22
+ throw new FsError("EFBIG", { syscall: "collectBytes", message: "output exceeds maxBytes" });
23
+ }
24
+ if (chunk.byteLength > 0)
25
+ chunks.push(new Uint8Array(chunk));
26
+ size += chunk.byteLength;
27
+ }
28
+ options.signal?.throwIfAborted();
29
+ const result = new Uint8Array(size);
30
+ let offset = 0;
31
+ for (const chunk of chunks) {
32
+ result.set(chunk, offset);
33
+ offset += chunk.byteLength;
34
+ }
35
+ return result;
36
+ }
37
+ async function abortable(operation, signal) {
38
+ signal?.throwIfAborted();
39
+ if (!signal)
40
+ return operation();
41
+ return new Promise((resolve, reject) => {
42
+ const onAbort = () => {
43
+ signal.removeEventListener("abort", onAbort);
44
+ reject(signal.reason);
45
+ };
46
+ signal.addEventListener("abort", onAbort, { once: true });
47
+ try {
48
+ Promise.resolve(operation()).then((result) => {
49
+ signal.removeEventListener("abort", onAbort);
50
+ resolve(result);
51
+ }, (error) => {
52
+ signal.removeEventListener("abort", onAbort);
53
+ reject(error);
54
+ });
55
+ }
56
+ catch (error) {
57
+ signal.removeEventListener("abort", onAbort);
58
+ reject(error);
59
+ }
60
+ });
61
+ }
62
+ export async function* readBytes(source, signal) {
63
+ signal?.throwIfAborted();
64
+ const iterator = source[Symbol.asyncIterator]();
65
+ let finished = false;
66
+ let failed = false;
67
+ try {
68
+ while (true) {
69
+ const result = await abortable(() => iterator.next(), signal);
70
+ if (result.done) {
71
+ finished = true;
72
+ return;
73
+ }
74
+ if (!(result.value instanceof Uint8Array))
75
+ throw new TypeError("Byte sources must yield Uint8Array chunks");
76
+ yield result.value;
77
+ }
78
+ }
79
+ catch (error) {
80
+ failed = true;
81
+ throw error;
82
+ }
83
+ finally {
84
+ if (!finished && iterator.return) {
85
+ const cleanup = Promise.resolve().then(() => iterator.return());
86
+ if (signal?.aborted)
87
+ void cleanup.catch(() => { });
88
+ else
89
+ await finishCleanup(() => abortable(() => cleanup, signal), failed);
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,8 @@
1
+ import { posix } from "node:path";
2
+ export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./virtual-path.js";
3
+ export declare const posixPath: typeof posix;
4
+ export declare const basename: typeof posix.basename;
5
+ export declare const dirname: typeof posix.dirname;
6
+ export declare const extname: typeof posix.extname;
7
+ export declare const joinPath: typeof posix.join;
8
+ export declare const isAbsolutePath: typeof posix.isAbsolute;
@@ -0,0 +1,8 @@
1
+ import { posix } from "node:path";
2
+ export { assertPathWithin, isPathWithin, normalizePath, relativePath, resolvePath, validatePath } from "./virtual-path.js";
3
+ export const posixPath = posix;
4
+ export const basename = posix.basename;
5
+ export const dirname = posix.dirname;
6
+ export const extname = posix.extname;
7
+ export const joinPath = posix.join;
8
+ export const isAbsolutePath = posix.isAbsolute;
@@ -0,0 +1,13 @@
1
+ import { dirname as virtualDirname } from "./virtual-path.js";
2
+ export declare function basename(path: string, suffix?: string): string;
3
+ export declare const dirname: typeof virtualDirname;
4
+ export declare function extname(path: string): string;
5
+ export declare function isAbsolutePath(path: string): boolean;
6
+ export declare function joinPath(...paths: string[]): string;
7
+ export declare const posixPath: Readonly<{
8
+ basename: typeof basename;
9
+ dirname: typeof virtualDirname;
10
+ extname: typeof extname;
11
+ join: typeof joinPath;
12
+ isAbsolute: typeof isAbsolutePath;
13
+ }>;
@@ -0,0 +1,57 @@
1
+ import { dirname as virtualDirname } from "./virtual-path.js";
2
+ function assertString(value) {
3
+ if (typeof value !== "string")
4
+ throw new TypeError("path must be a string");
5
+ }
6
+ export function basename(path, suffix = "") {
7
+ assertString(path);
8
+ assertString(suffix);
9
+ if (suffix && path === suffix)
10
+ return "";
11
+ let end = path.length;
12
+ while (end > 0 && path[end - 1] === "/")
13
+ end--;
14
+ if (end === 0 && suffix && suffix.length <= path.length)
15
+ return path;
16
+ const start = path.lastIndexOf("/", end - 1) + 1;
17
+ const base = path.slice(start, end);
18
+ if (suffix.length > base.length && suffix.length <= path.length && suffix.endsWith(base))
19
+ return path.slice(start);
20
+ return suffix && suffix.length < base.length && base.endsWith(suffix)
21
+ ? base.slice(0, -suffix.length)
22
+ : base;
23
+ }
24
+ export const dirname = virtualDirname;
25
+ export function extname(path) {
26
+ const base = basename(path);
27
+ const position = base.lastIndexOf(".");
28
+ return position <= 0 || base === ".." ? "" : base.slice(position);
29
+ }
30
+ export function isAbsolutePath(path) {
31
+ assertString(path);
32
+ return path.startsWith("/");
33
+ }
34
+ export function joinPath(...paths) {
35
+ for (const path of paths)
36
+ assertString(path);
37
+ const joined = paths.filter(Boolean).join("/");
38
+ if (!joined)
39
+ return ".";
40
+ const absolute = joined.startsWith("/");
41
+ const components = [];
42
+ for (const component of joined.split("/")) {
43
+ if (!component || component === ".")
44
+ continue;
45
+ if (component === "..") {
46
+ if (components.length && components.at(-1) !== "..")
47
+ components.pop();
48
+ else if (!absolute)
49
+ components.push(component);
50
+ }
51
+ else
52
+ components.push(component);
53
+ }
54
+ const result = `${absolute ? "/" : ""}${components.join("/")}` || ".";
55
+ return joined.endsWith("/") && result !== "/" ? `${result}/` : result;
56
+ }
57
+ export const posixPath = Object.freeze({ basename, dirname, extname, join: joinPath, isAbsolute: isAbsolutePath });
@@ -0,0 +1,7 @@
1
+ export declare function validatePath(path: string): void;
2
+ export declare function resolvePath(cwd: string, ...paths: string[]): string;
3
+ export declare function normalizePath(path: string, cwd?: string): string;
4
+ export declare function dirname(path: string): string;
5
+ export declare function relativePath(from: string, to: string): string;
6
+ export declare function isPathWithin(root: string, path: string): boolean;
7
+ export declare function assertPathWithin(root: string, path: string): string;
@@ -0,0 +1,66 @@
1
+ import { FsError } from "./errors.js";
2
+ export function validatePath(path) {
3
+ if (typeof path !== "string" || path.includes("\0")) {
4
+ throw new FsError("EINVAL", { syscall: "resolve", message: "paths must be strings without NUL bytes" });
5
+ }
6
+ }
7
+ export function resolvePath(cwd, ...paths) {
8
+ validatePath(cwd);
9
+ if (!cwd.startsWith("/"))
10
+ throw new FsError("EINVAL", { syscall: "resolve", path: cwd, message: "cwd must be absolute" });
11
+ for (const path of paths)
12
+ validatePath(path);
13
+ const components = [];
14
+ for (const path of [cwd, ...paths]) {
15
+ if (path.startsWith("/"))
16
+ components.length = 0;
17
+ for (const component of path.split("/")) {
18
+ if (component === "" || component === ".")
19
+ continue;
20
+ if (component === "..")
21
+ components.pop();
22
+ else
23
+ components.push(component);
24
+ }
25
+ }
26
+ return `/${components.join("/")}`;
27
+ }
28
+ export function normalizePath(path, cwd = "/") {
29
+ return resolvePath(cwd, path);
30
+ }
31
+ export function dirname(path) {
32
+ if (typeof path !== "string")
33
+ throw new TypeError("path must be a string");
34
+ let end = path.length;
35
+ while (end > 0 && path[end - 1] === "/")
36
+ end--;
37
+ if (end === 0)
38
+ return path.startsWith("/") ? "/" : ".";
39
+ const boundary = path.lastIndexOf("/", end - 1);
40
+ if (boundary < 0)
41
+ return ".";
42
+ if (boundary === 0)
43
+ return "/";
44
+ if (boundary === 1 && path.startsWith("/"))
45
+ return "//";
46
+ return path.slice(0, boundary);
47
+ }
48
+ export function relativePath(from, to) {
49
+ const source = normalizePath(from).split("/").filter(Boolean);
50
+ const target = normalizePath(to).split("/").filter(Boolean);
51
+ let common = 0;
52
+ while (common < source.length && common < target.length && source[common] === target[common])
53
+ common++;
54
+ return [...Array(source.length - common).fill(".."), ...target.slice(common)].join("/");
55
+ }
56
+ export function isPathWithin(root, path) {
57
+ const normalizedRoot = normalizePath(root);
58
+ const normalizedPath = normalizePath(path);
59
+ return normalizedRoot === "/" || normalizedPath === normalizedRoot || normalizedPath.startsWith(`${normalizedRoot}/`);
60
+ }
61
+ export function assertPathWithin(root, path) {
62
+ const normalizedPath = normalizePath(path);
63
+ if (!isPathWithin(root, normalizedPath))
64
+ throw new FsError("EACCES", { syscall: "resolve", path, message: "path escapes the allowed root" });
65
+ return normalizedPath;
66
+ }