@zenfs/core 0.9.7 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/backends/AsyncStore.js +29 -29
  2. package/dist/backends/Fetch.d.ts +84 -0
  3. package/dist/backends/Fetch.js +171 -0
  4. package/dist/backends/Index.js +19 -19
  5. package/dist/backends/Locked.d.ts +11 -11
  6. package/dist/backends/Locked.js +50 -49
  7. package/dist/backends/Overlay.js +21 -21
  8. package/dist/backends/SyncStore.js +27 -27
  9. package/dist/backends/backend.js +4 -4
  10. package/dist/backends/port/fs.d.ts +124 -0
  11. package/dist/backends/port/fs.js +241 -0
  12. package/dist/backends/port/rpc.d.ts +60 -0
  13. package/dist/backends/port/rpc.js +71 -0
  14. package/dist/backends/port/store.d.ts +30 -0
  15. package/dist/backends/port/store.js +142 -0
  16. package/dist/browser.min.js +4 -4
  17. package/dist/browser.min.js.map +4 -4
  18. package/dist/config.d.ts +8 -10
  19. package/dist/config.js +11 -11
  20. package/dist/emulation/async.js +6 -6
  21. package/dist/emulation/dir.js +2 -2
  22. package/dist/emulation/index.d.ts +1 -1
  23. package/dist/emulation/index.js +1 -1
  24. package/dist/emulation/path.d.ts +3 -2
  25. package/dist/emulation/path.js +19 -45
  26. package/dist/emulation/promises.d.ts +7 -12
  27. package/dist/emulation/promises.js +144 -146
  28. package/dist/emulation/shared.d.ts +5 -10
  29. package/dist/emulation/shared.js +8 -8
  30. package/dist/emulation/streams.js +3 -3
  31. package/dist/emulation/sync.js +25 -25
  32. package/dist/{ApiError.d.ts → error.d.ts} +13 -14
  33. package/dist/error.js +292 -0
  34. package/dist/file.d.ts +2 -0
  35. package/dist/file.js +10 -4
  36. package/dist/filesystem.js +15 -15
  37. package/dist/index.d.ts +4 -1
  38. package/dist/index.js +4 -1
  39. package/dist/mutex.js +2 -1
  40. package/dist/utils.d.ts +8 -7
  41. package/dist/utils.js +11 -12
  42. package/package.json +3 -3
  43. package/readme.md +17 -9
  44. package/src/backends/AsyncStore.ts +29 -29
  45. package/src/backends/Fetch.ts +230 -0
  46. package/src/backends/Index.ts +19 -19
  47. package/src/backends/Locked.ts +50 -49
  48. package/src/backends/Overlay.ts +23 -23
  49. package/src/backends/SyncStore.ts +27 -27
  50. package/src/backends/backend.ts +6 -6
  51. package/src/backends/port/fs.ts +308 -0
  52. package/src/backends/port/readme.md +59 -0
  53. package/src/backends/port/rpc.ts +144 -0
  54. package/src/backends/port/store.ts +187 -0
  55. package/src/config.ts +20 -24
  56. package/src/emulation/async.ts +6 -6
  57. package/src/emulation/dir.ts +2 -2
  58. package/src/emulation/index.ts +1 -1
  59. package/src/emulation/path.ts +25 -49
  60. package/src/emulation/promises.ts +150 -159
  61. package/src/emulation/shared.ts +12 -14
  62. package/src/emulation/streams.ts +3 -3
  63. package/src/emulation/sync.ts +28 -28
  64. package/src/{ApiError.ts → error.ts} +89 -89
  65. package/src/file.ts +12 -4
  66. package/src/filesystem.ts +15 -15
  67. package/src/index.ts +4 -1
  68. package/src/mutex.ts +3 -1
  69. package/src/utils.ts +16 -18
  70. package/tsconfig.json +2 -2
  71. package/dist/ApiError.js +0 -292
@@ -1,12 +1,12 @@
1
1
  // Utilities and shared data
2
2
 
3
- import { ApiError, ErrorCode } from '../ApiError.js';
3
+ import { ErrnoError, Errno } from '../error.js';
4
4
  import { InMemory } from '../backends/InMemory.js';
5
5
  import { Cred, rootCred } from '../cred.js';
6
6
  import type { File } from '../file.js';
7
7
  import { FileSystem } from '../filesystem.js';
8
8
  import { normalizePath } from '../utils.js';
9
- import { resolve } from './path.js';
9
+ import { resolve, type AbsolutePath } from './path.js';
10
10
 
11
11
  // credentials
12
12
  export let cred: Cred = rootCred;
@@ -24,15 +24,13 @@ export function file2fd(file: File): number {
24
24
  }
25
25
  export function fd2file(fd: number): File {
26
26
  if (!fdMap.has(fd)) {
27
- throw new ApiError(ErrorCode.EBADF);
27
+ throw new ErrnoError(Errno.EBADF);
28
28
  }
29
29
  return fdMap.get(fd)!;
30
30
  }
31
31
 
32
32
  // mounting
33
- export interface MountMapping {
34
- [point: string]: FileSystem;
35
- }
33
+ export type MountObject = Record<AbsolutePath, FileSystem>;
36
34
 
37
35
  /**
38
36
  * The map of mount points
@@ -54,7 +52,7 @@ export function mount(mountPoint: string, fs: FileSystem): void {
54
52
  }
55
53
  mountPoint = resolve(mountPoint);
56
54
  if (mounts.has(mountPoint)) {
57
- throw new ApiError(ErrorCode.EINVAL, 'Mount point ' + mountPoint + ' is already in use.');
55
+ throw new ErrnoError(Errno.EINVAL, 'Mount point ' + mountPoint + ' is already in use.');
58
56
  }
59
57
  mounts.set(mountPoint, fs);
60
58
  }
@@ -68,7 +66,7 @@ export function umount(mountPoint: string): void {
68
66
  }
69
67
  mountPoint = resolve(mountPoint);
70
68
  if (!mounts.has(mountPoint)) {
71
- throw new ApiError(ErrorCode.EINVAL, 'Mount point ' + mountPoint + ' is already unmounted.');
69
+ throw new ErrnoError(Errno.EINVAL, 'Mount point ' + mountPoint + ' is already unmounted.');
72
70
  }
73
71
  mounts.delete(mountPoint);
74
72
  }
@@ -90,20 +88,20 @@ export function resolveMount(path: string): { fs: FileSystem; path: string; moun
90
88
  }
91
89
  }
92
90
 
93
- throw new ApiError(ErrorCode.EIO, 'ZenFS not initialized with a file system');
91
+ throw new ErrnoError(Errno.EIO, 'ZenFS not initialized with a file system');
94
92
  }
95
93
 
96
94
  /**
97
95
  * Reverse maps the paths in text from the mounted FileSystem to the global path
98
96
  */
99
- export function fixPaths(text: string, paths: { [from: string]: string }): string {
97
+ export function fixPaths(text: string, paths: Record<string, string>): string {
100
98
  for (const [from, to] of Object.entries(paths)) {
101
99
  text = text?.replaceAll(from, to);
102
100
  }
103
101
  return text;
104
102
  }
105
103
 
106
- export function fixError<E extends Error>(e: E, paths: { [from: string]: string }): E {
104
+ export function fixError<E extends Error>(e: E, paths: Record<string, string>): E {
107
105
  if (typeof e.stack == 'string') {
108
106
  e.stack = fixPaths(e.stack, paths);
109
107
  }
@@ -111,11 +109,11 @@ export function fixError<E extends Error>(e: E, paths: { [from: string]: string
111
109
  return e;
112
110
  }
113
111
 
114
- export function mountMapping(mountMapping: MountMapping): void {
115
- if ('/' in mountMapping) {
112
+ export function mountObject(mounts: MountObject): void {
113
+ if ('/' in mounts) {
116
114
  umount('/');
117
115
  }
118
- for (const [point, fs] of Object.entries(mountMapping)) {
116
+ for (const [point, fs] of Object.entries(mounts)) {
119
117
  mount(point, fs);
120
118
  }
121
119
  }
@@ -1,7 +1,7 @@
1
1
  import type * as Node from 'fs';
2
2
  import { Readable, Writable } from 'readable-stream';
3
3
  import { Callback } from '../utils.js';
4
- import { ApiError, ErrorCode } from '../ApiError.js';
4
+ import { ErrnoError, Errno } from '../error.js';
5
5
 
6
6
  export class ReadStream extends Readable implements Node.ReadStream {
7
7
  close(callback: Callback = () => null): void {
@@ -10,7 +10,7 @@ export class ReadStream extends Readable implements Node.ReadStream {
10
10
  super.emit('close');
11
11
  callback();
12
12
  } catch (err) {
13
- callback(new ApiError(ErrorCode.EIO, (err as Error).toString()));
13
+ callback(new ErrnoError(Errno.EIO, (err as Error).toString()));
14
14
  }
15
15
  }
16
16
  declare bytesRead: number;
@@ -25,7 +25,7 @@ export class WriteStream extends Writable implements Node.WriteStream {
25
25
  super.emit('close');
26
26
  callback();
27
27
  } catch (err) {
28
- callback(new ApiError(ErrorCode.EIO, (err as Error).toString()));
28
+ callback(new ErrnoError(Errno.EIO, (err as Error).toString()));
29
29
  }
30
30
  }
31
31
  declare bytesWritten: number;
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'buffer';
2
2
  import type * as fs from 'node:fs';
3
- import { ApiError, ErrorCode } from '../ApiError.js';
3
+ import { ErrnoError, Errno } from '../error.js';
4
4
  import { ActionType, File, isAppendable, isReadable, isWriteable, parseFlag, pathExistsAction, pathNotExistsAction } from '../file.js';
5
5
  import { FileContents, FileSystem } from '../filesystem.js';
6
6
  import { BigIntStats, FileType, type BigIntStatsFs, type Stats, type StatsFs } from '../stats.js';
@@ -23,7 +23,7 @@ function doOp<M extends FileSystemMethod, RT extends ReturnType<M>>(...[name, re
23
23
  // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
24
24
  return fs[name](resolvedPath, ...args) as RT;
25
25
  } catch (e) {
26
- throw fixError(<Error>e, { [resolvedPath]: path });
26
+ throw fixError(e as Error, { [resolvedPath]: path });
27
27
  }
28
28
  }
29
29
 
@@ -46,7 +46,7 @@ export function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void {
46
46
  writeFileSync(newPath, readFileSync(oldPath));
47
47
  unlinkSync(oldPath);
48
48
  } catch (e) {
49
- throw fixError(<Error>e, paths);
49
+ throw fixError(e as Error, paths);
50
50
  }
51
51
  }
52
52
  renameSync satisfies typeof fs.renameSync;
@@ -61,7 +61,7 @@ export function existsSync(path: fs.PathLike): boolean {
61
61
  const { fs, path: resolvedPath } = resolveMount(realpathSync(path));
62
62
  return fs.existsSync(resolvedPath, cred);
63
63
  } catch (e) {
64
- if ((e as ApiError).errno == ErrorCode.ENOENT) {
64
+ if ((e as ErrnoError).errno == Errno.ENOENT) {
65
65
  return false;
66
66
  }
67
67
 
@@ -136,23 +136,23 @@ function _openSync(_path: fs.PathLike, _flag: fs.OpenMode, _mode?: fs.Mode | nul
136
136
  // Ensure parent exists.
137
137
  const parentStats: Stats = doOp('statSync', resolveSymlinks, dirname(path), cred);
138
138
  if (!parentStats.isDirectory()) {
139
- throw ApiError.With('ENOTDIR', dirname(path), '_open');
139
+ throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
140
140
  }
141
141
  return doOp('createFileSync', resolveSymlinks, path, flag, mode, cred);
142
142
  case ActionType.THROW:
143
- throw ApiError.With('ENOENT', path, '_open');
143
+ throw ErrnoError.With('ENOENT', path, '_open');
144
144
  default:
145
- throw new ApiError(ErrorCode.EINVAL, 'Invalid FileFlag object.');
145
+ throw new ErrnoError(Errno.EINVAL, 'Invalid FileFlag object.');
146
146
  }
147
147
  }
148
148
  if (!stats.hasAccess(mode, cred)) {
149
- throw ApiError.With('EACCES', path, '_open');
149
+ throw ErrnoError.With('EACCES', path, '_open');
150
150
  }
151
151
 
152
152
  // File exists.
153
153
  switch (pathExistsAction(flag)) {
154
154
  case ActionType.THROW:
155
- throw ApiError.With('EEXIST', path, '_open');
155
+ throw ErrnoError.With('EEXIST', path, '_open');
156
156
  case ActionType.TRUNCATE:
157
157
  // Delete file.
158
158
  doOp('unlinkSync', resolveSymlinks, path, cred);
@@ -166,7 +166,7 @@ function _openSync(_path: fs.PathLike, _flag: fs.OpenMode, _mode?: fs.Mode | nul
166
166
  case ActionType.NOP:
167
167
  return doOp('openFileSync', resolveSymlinks, path, flag, cred);
168
168
  default:
169
- throw new ApiError(ErrorCode.EINVAL, 'Invalid FileFlag object.');
169
+ throw new ErrnoError(Errno.EINVAL, 'Invalid FileFlag object.');
170
170
  }
171
171
  }
172
172
 
@@ -223,7 +223,7 @@ export function readFileSync(path: fs.PathOrFileDescriptor, _options: fs.WriteFi
223
223
  const options = normalizeOptions(_options, null, 'r', 0o644);
224
224
  const flag = parseFlag(options.flag);
225
225
  if (!isReadable(flag)) {
226
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed to readFile must allow for reading.');
226
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed to readFile must allow for reading.');
227
227
  }
228
228
  const data: Buffer = Buffer.from(_readFileSync(typeof path == 'number' ? fd2file(path).path! : path.toString(), options.flag, true));
229
229
  return options.encoding ? data.toString(options.encoding) : data;
@@ -263,14 +263,14 @@ export function writeFileSync(path: fs.PathOrFileDescriptor, data: FileContents,
263
263
  const options = normalizeOptions(_options, 'utf8', 'w+', 0o644);
264
264
  const flag = parseFlag(options.flag);
265
265
  if (!isWriteable(flag)) {
266
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed to writeFile must allow for writing.');
266
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed to writeFile must allow for writing.');
267
267
  }
268
268
  if (typeof data != 'string' && !options.encoding) {
269
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
269
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
270
270
  }
271
271
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
272
272
  if (!encodedData) {
273
- throw new ApiError(ErrorCode.EINVAL, 'Data not specified');
273
+ throw new ErrnoError(Errno.EINVAL, 'Data not specified');
274
274
  }
275
275
  _writeFileSync(typeof path == 'number' ? fd2file(path).path! : path.toString(), encodedData, options.flag, options.mode, true);
276
276
  }
@@ -304,10 +304,10 @@ export function appendFileSync(filename: fs.PathOrFileDescriptor, data: FileCont
304
304
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
305
305
  const flag = parseFlag(options.flag);
306
306
  if (!isAppendable(flag)) {
307
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed to appendFile must allow for appending.');
307
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.');
308
308
  }
309
309
  if (typeof data != 'string' && !options.encoding) {
310
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
310
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
311
311
  }
312
312
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
313
313
  _appendFileSync(typeof filename == 'number' ? fd2file(filename).path! : filename.toString(), encodedData, options.flag, options.mode, true);
@@ -346,7 +346,7 @@ closeSync satisfies typeof fs.closeSync;
346
346
  export function ftruncateSync(fd: number, len: number | null = 0): void {
347
347
  len ||= 0;
348
348
  if (len < 0) {
349
- throw new ApiError(ErrorCode.EINVAL);
349
+ throw new ErrnoError(Errno.EINVAL);
350
350
  }
351
351
  fd2file(fd).truncateSync(len);
352
352
  }
@@ -458,7 +458,7 @@ fchownSync satisfies typeof fs.fchownSync;
458
458
  export function fchmodSync(fd: number, mode: number | string): void {
459
459
  const numMode = normalizeMode(mode, -1);
460
460
  if (numMode < 0) {
461
- throw new ApiError(ErrorCode.EINVAL, `Invalid mode.`);
461
+ throw new ErrnoError(Errno.EINVAL, `Invalid mode.`);
462
462
  }
463
463
  fd2file(fd).chmodSync(numMode);
464
464
  }
@@ -561,10 +561,10 @@ linkSync satisfies typeof fs.linkSync;
561
561
  */
562
562
  export function symlinkSync(target: fs.PathLike, path: fs.PathLike, type: fs.symlink.Type | null = 'file'): void {
563
563
  if (!['file', 'dir', 'junction'].includes(type!)) {
564
- throw new ApiError(ErrorCode.EINVAL, 'Invalid type: ' + type);
564
+ throw new ErrnoError(Errno.EINVAL, 'Invalid type: ' + type);
565
565
  }
566
566
  if (existsSync(path)) {
567
- throw ApiError.With('EEXIST', path.toString(), 'symlink');
567
+ throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
568
568
  }
569
569
 
570
570
  writeFileSync(path, target.toString());
@@ -691,7 +691,7 @@ export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs
691
691
 
692
692
  return realpathSync(mountPoint + readlinkSync(lpath));
693
693
  } catch (e) {
694
- throw fixError(<Error>e, { [resolvedPath]: lpath });
694
+ throw fixError(e as Error, { [resolvedPath]: lpath });
695
695
  }
696
696
  }
697
697
  realpathSync satisfies Omit<typeof fs.realpathSync, 'native'>;
@@ -704,7 +704,7 @@ realpathSync satisfies Omit<typeof fs.realpathSync, 'native'>;
704
704
  export function accessSync(path: fs.PathLike, mode: number = 0o600): void {
705
705
  const stats = statSync(path);
706
706
  if (!stats.hasAccess(mode, cred)) {
707
- throw new ApiError(ErrorCode.EACCES);
707
+ throw new ErrnoError(Errno.EACCES);
708
708
  }
709
709
  }
710
710
  accessSync satisfies typeof fs.accessSync;
@@ -737,7 +737,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions): void {
737
737
  case S_IFIFO:
738
738
  case S_IFSOCK:
739
739
  default:
740
- throw new ApiError(ErrorCode.EPERM, 'File type not supported', path, 'rm');
740
+ throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
741
741
  }
742
742
  }
743
743
  rmSync satisfies typeof fs.rmSync;
@@ -773,7 +773,7 @@ export function copyFileSync(src: fs.PathLike, dest: fs.PathLike, flags?: number
773
773
  dest = normalizePath(dest);
774
774
 
775
775
  if (flags && flags & COPYFILE_EXCL && existsSync(dest)) {
776
- throw new ApiError(ErrorCode.EEXIST, 'Destination file already exists.', dest, 'copyFile');
776
+ throw new ErrnoError(Errno.EEXIST, 'Destination file already exists.', dest, 'copyFile');
777
777
  }
778
778
 
779
779
  writeFileSync(dest, readFileSync(src));
@@ -849,13 +849,13 @@ export function cpSync(source: fs.PathLike, destination: fs.PathLike, opts?: fs.
849
849
  const srcStats = lstatSync(source); // Use lstat to follow symlinks if not dereferencing
850
850
 
851
851
  if (opts?.errorOnExist && existsSync(destination)) {
852
- throw new ApiError(ErrorCode.EEXIST, 'Destination file or directory already exists.', destination, 'cp');
852
+ throw new ErrnoError(Errno.EEXIST, 'Destination file or directory already exists.', destination, 'cp');
853
853
  }
854
854
 
855
855
  switch (srcStats.mode & S_IFMT) {
856
856
  case S_IFDIR:
857
857
  if (!opts?.recursive) {
858
- throw new ApiError(ErrorCode.EISDIR, source + ' is a directory (not copied)', source, 'cp');
858
+ throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp');
859
859
  }
860
860
  mkdirSync(destination, { recursive: true }); // Ensure the destination directory exists
861
861
  for (const dirent of readdirSync(source, { withFileTypes: true })) {
@@ -874,7 +874,7 @@ export function cpSync(source: fs.PathLike, destination: fs.PathLike, opts?: fs.
874
874
  case S_IFIFO:
875
875
  case S_IFSOCK:
876
876
  default:
877
- throw new ApiError(ErrorCode.EPERM, 'File type not supported', source, 'rm');
877
+ throw new ErrnoError(Errno.EPERM, 'File type not supported', source, 'rm');
878
878
  }
879
879
 
880
880
  // Optionally preserve timestamps
@@ -894,5 +894,5 @@ export function statfsSync(path: fs.PathLike, options?: fs.StatFsOptions & { big
894
894
  export function statfsSync(path: fs.PathLike, options: fs.StatFsOptions & { bigint: true }): BigIntStatsFs;
895
895
  export function statfsSync(path: fs.PathLike, options?: fs.StatFsOptions): StatsFs | BigIntStatsFs;
896
896
  export function statfsSync(path: fs.PathLike, options?: fs.StatFsOptions): StatsFs | BigIntStatsFs {
897
- throw ApiError.With('ENOSYS', path.toString(), 'statfs');
897
+ throw ErrnoError.With('ENOSYS', path.toString(), 'statfs');
898
898
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Standard libc error codes. More will be added to this enum and ErrorStrings as they are
2
+ * Standard libc error codes. More will be added to this enum and error strings as they are
3
3
  * needed.
4
4
  * @url https://en.wikipedia.org/wiki/Errno.h
5
5
  */
6
- export enum ErrorCode {
6
+ export enum Errno {
7
7
  /** Operation not permitted */
8
8
  EPERM = 1,
9
9
  /** No such file or directory */
@@ -157,88 +157,88 @@ export enum ErrorCode {
157
157
  * Strings associated with each error code.
158
158
  * @internal
159
159
  */
160
- export const errorMessages: { [K in ErrorCode]: string } = {
161
- [ErrorCode.EPERM]: 'Operation not permitted',
162
- [ErrorCode.ENOENT]: 'No such file or directory',
163
- [ErrorCode.EINTR]: 'Interrupted system call',
164
- [ErrorCode.EIO]: 'Input/output error',
165
- [ErrorCode.ENXIO]: 'No such device or address',
166
- [ErrorCode.EBADF]: 'Bad file descriptor',
167
- [ErrorCode.EAGAIN]: 'Resource temporarily unavailable',
168
- [ErrorCode.ENOMEM]: 'Cannot allocate memory',
169
- [ErrorCode.EACCES]: 'Permission denied',
170
- [ErrorCode.EFAULT]: 'Bad address',
171
- [ErrorCode.ENOTBLK]: 'Block device required',
172
- [ErrorCode.EBUSY]: 'Resource busy or locked',
173
- [ErrorCode.EEXIST]: 'File exists',
174
- [ErrorCode.EXDEV]: 'Invalid cross-device link',
175
- [ErrorCode.ENODEV]: 'No such device',
176
- [ErrorCode.ENOTDIR]: 'File is not a directory',
177
- [ErrorCode.EISDIR]: 'File is a directory',
178
- [ErrorCode.EINVAL]: 'Invalid argument',
179
- [ErrorCode.ENFILE]: 'Too many open files in system',
180
- [ErrorCode.EMFILE]: 'Too many open files',
181
- [ErrorCode.ETXTBSY]: 'Text file busy',
182
- [ErrorCode.EFBIG]: 'File is too big',
183
- [ErrorCode.ENOSPC]: 'No space left on disk',
184
- [ErrorCode.ESPIPE]: 'Illegal seek',
185
- [ErrorCode.EROFS]: 'Cannot modify a read-only file system',
186
- [ErrorCode.EMLINK]: 'Too many links',
187
- [ErrorCode.EPIPE]: 'Broken pipe',
188
- [ErrorCode.EDOM]: 'Numerical argument out of domain',
189
- [ErrorCode.ERANGE]: 'Numerical result out of range',
190
- [ErrorCode.EDEADLK]: 'Resource deadlock would occur',
191
- [ErrorCode.ENAMETOOLONG]: 'File name too long',
192
- [ErrorCode.ENOLCK]: 'No locks available',
193
- [ErrorCode.ENOSYS]: 'Function not implemented',
194
- [ErrorCode.ENOTEMPTY]: 'Directory is not empty',
195
- [ErrorCode.ELOOP]: 'Too many levels of symbolic links',
196
- [ErrorCode.ENOMSG]: 'No message of desired type',
197
- [ErrorCode.EBADE]: 'Invalid exchange',
198
- [ErrorCode.EBADR]: 'Invalid request descriptor',
199
- [ErrorCode.EXFULL]: 'Exchange full',
200
- [ErrorCode.ENOANO]: 'No anode',
201
- [ErrorCode.EBADRQC]: 'Invalid request code',
202
- [ErrorCode.ENOSTR]: 'Device not a stream',
203
- [ErrorCode.ENODATA]: 'No data available',
204
- [ErrorCode.ETIME]: 'Timer expired',
205
- [ErrorCode.ENOSR]: 'Out of streams resources',
206
- [ErrorCode.ENONET]: 'Machine is not on the network',
207
- [ErrorCode.EREMOTE]: 'Object is remote',
208
- [ErrorCode.ENOLINK]: 'Link has been severed',
209
- [ErrorCode.ECOMM]: 'Communication error on send',
210
- [ErrorCode.EPROTO]: 'Protocol error',
211
- [ErrorCode.EBADMSG]: 'Bad message',
212
- [ErrorCode.EOVERFLOW]: 'Value too large for defined data type',
213
- [ErrorCode.EBADFD]: 'File descriptor in bad state',
214
- [ErrorCode.ESTRPIPE]: 'Streams pipe error',
215
- [ErrorCode.ENOTSOCK]: 'Socket operation on non-socket',
216
- [ErrorCode.EDESTADDRREQ]: 'Destination address required',
217
- [ErrorCode.EMSGSIZE]: 'Message too long',
218
- [ErrorCode.EPROTOTYPE]: 'Protocol wrong type for socket',
219
- [ErrorCode.ENOPROTOOPT]: 'Protocol not available',
220
- [ErrorCode.EPROTONOSUPPORT]: 'Protocol not supported',
221
- [ErrorCode.ESOCKTNOSUPPORT]: 'Socket type not supported',
222
- [ErrorCode.ENOTSUP]: 'Operation is not supported',
223
- [ErrorCode.ENETDOWN]: 'Network is down',
224
- [ErrorCode.ENETUNREACH]: 'Network is unreachable',
225
- [ErrorCode.ENETRESET]: 'Network dropped connection on reset',
226
- [ErrorCode.ETIMEDOUT]: 'Connection timed out',
227
- [ErrorCode.ECONNREFUSED]: 'Connection refused',
228
- [ErrorCode.EHOSTDOWN]: 'Host is down',
229
- [ErrorCode.EHOSTUNREACH]: 'No route to host',
230
- [ErrorCode.EALREADY]: 'Operation already in progress',
231
- [ErrorCode.EINPROGRESS]: 'Operation now in progress',
232
- [ErrorCode.ESTALE]: 'Stale file handle',
233
- [ErrorCode.EREMOTEIO]: 'Remote I/O error',
234
- [ErrorCode.EDQUOT]: 'Disk quota exceeded',
160
+ export const errorMessages: { [K in Errno]: string } = {
161
+ [Errno.EPERM]: 'Operation not permitted',
162
+ [Errno.ENOENT]: 'No such file or directory',
163
+ [Errno.EINTR]: 'Interrupted system call',
164
+ [Errno.EIO]: 'Input/output error',
165
+ [Errno.ENXIO]: 'No such device or address',
166
+ [Errno.EBADF]: 'Bad file descriptor',
167
+ [Errno.EAGAIN]: 'Resource temporarily unavailable',
168
+ [Errno.ENOMEM]: 'Cannot allocate memory',
169
+ [Errno.EACCES]: 'Permission denied',
170
+ [Errno.EFAULT]: 'Bad address',
171
+ [Errno.ENOTBLK]: 'Block device required',
172
+ [Errno.EBUSY]: 'Resource busy or locked',
173
+ [Errno.EEXIST]: 'File exists',
174
+ [Errno.EXDEV]: 'Invalid cross-device link',
175
+ [Errno.ENODEV]: 'No such device',
176
+ [Errno.ENOTDIR]: 'File is not a directory',
177
+ [Errno.EISDIR]: 'File is a directory',
178
+ [Errno.EINVAL]: 'Invalid argument',
179
+ [Errno.ENFILE]: 'Too many open files in system',
180
+ [Errno.EMFILE]: 'Too many open files',
181
+ [Errno.ETXTBSY]: 'Text file busy',
182
+ [Errno.EFBIG]: 'File is too big',
183
+ [Errno.ENOSPC]: 'No space left on disk',
184
+ [Errno.ESPIPE]: 'Illegal seek',
185
+ [Errno.EROFS]: 'Cannot modify a read-only file system',
186
+ [Errno.EMLINK]: 'Too many links',
187
+ [Errno.EPIPE]: 'Broken pipe',
188
+ [Errno.EDOM]: 'Numerical argument out of domain',
189
+ [Errno.ERANGE]: 'Numerical result out of range',
190
+ [Errno.EDEADLK]: 'Resource deadlock would occur',
191
+ [Errno.ENAMETOOLONG]: 'File name too long',
192
+ [Errno.ENOLCK]: 'No locks available',
193
+ [Errno.ENOSYS]: 'Function not implemented',
194
+ [Errno.ENOTEMPTY]: 'Directory is not empty',
195
+ [Errno.ELOOP]: 'Too many levels of symbolic links',
196
+ [Errno.ENOMSG]: 'No message of desired type',
197
+ [Errno.EBADE]: 'Invalid exchange',
198
+ [Errno.EBADR]: 'Invalid request descriptor',
199
+ [Errno.EXFULL]: 'Exchange full',
200
+ [Errno.ENOANO]: 'No anode',
201
+ [Errno.EBADRQC]: 'Invalid request code',
202
+ [Errno.ENOSTR]: 'Device not a stream',
203
+ [Errno.ENODATA]: 'No data available',
204
+ [Errno.ETIME]: 'Timer expired',
205
+ [Errno.ENOSR]: 'Out of streams resources',
206
+ [Errno.ENONET]: 'Machine is not on the network',
207
+ [Errno.EREMOTE]: 'Object is remote',
208
+ [Errno.ENOLINK]: 'Link has been severed',
209
+ [Errno.ECOMM]: 'Communication error on send',
210
+ [Errno.EPROTO]: 'Protocol error',
211
+ [Errno.EBADMSG]: 'Bad message',
212
+ [Errno.EOVERFLOW]: 'Value too large for defined data type',
213
+ [Errno.EBADFD]: 'File descriptor in bad state',
214
+ [Errno.ESTRPIPE]: 'Streams pipe error',
215
+ [Errno.ENOTSOCK]: 'Socket operation on non-socket',
216
+ [Errno.EDESTADDRREQ]: 'Destination address required',
217
+ [Errno.EMSGSIZE]: 'Message too long',
218
+ [Errno.EPROTOTYPE]: 'Protocol wrong type for socket',
219
+ [Errno.ENOPROTOOPT]: 'Protocol not available',
220
+ [Errno.EPROTONOSUPPORT]: 'Protocol not supported',
221
+ [Errno.ESOCKTNOSUPPORT]: 'Socket type not supported',
222
+ [Errno.ENOTSUP]: 'Operation is not supported',
223
+ [Errno.ENETDOWN]: 'Network is down',
224
+ [Errno.ENETUNREACH]: 'Network is unreachable',
225
+ [Errno.ENETRESET]: 'Network dropped connection on reset',
226
+ [Errno.ETIMEDOUT]: 'Connection timed out',
227
+ [Errno.ECONNREFUSED]: 'Connection refused',
228
+ [Errno.EHOSTDOWN]: 'Host is down',
229
+ [Errno.EHOSTUNREACH]: 'No route to host',
230
+ [Errno.EALREADY]: 'Operation already in progress',
231
+ [Errno.EINPROGRESS]: 'Operation now in progress',
232
+ [Errno.ESTALE]: 'Stale file handle',
233
+ [Errno.EREMOTEIO]: 'Remote I/O error',
234
+ [Errno.EDQUOT]: 'Disk quota exceeded',
235
235
  };
236
236
 
237
- interface ApiErrorJSON {
238
- errno: ErrorCode;
237
+ export interface ErrnoErrorJSON {
238
+ errno: Errno;
239
239
  message: string;
240
240
  path?: string;
241
- code: keyof typeof ErrorCode;
241
+ code: keyof typeof Errno;
242
242
  stack: string;
243
243
  syscall: string;
244
244
  }
@@ -247,19 +247,19 @@ interface ApiErrorJSON {
247
247
  * Represents a ZenFS error. Passed back to applications after a failed
248
248
  * call to the ZenFS API.
249
249
  */
250
- export class ApiError extends Error implements NodeJS.ErrnoException {
251
- public static fromJSON(json: ApiErrorJSON): ApiError {
252
- const err = new ApiError(json.errno, json.message, json.path, json.syscall);
250
+ export class ErrnoError extends Error implements NodeJS.ErrnoException {
251
+ public static fromJSON(json: ErrnoErrorJSON): ErrnoError {
252
+ const err = new ErrnoError(json.errno, json.message, json.path, json.syscall);
253
253
  err.code = json.code;
254
254
  err.stack = json.stack;
255
255
  return err;
256
256
  }
257
257
 
258
- public static With(code: keyof typeof ErrorCode, path?: string, syscall?: string): ApiError {
259
- return new ApiError(ErrorCode[code], errorMessages[ErrorCode[code]], path, syscall);
258
+ public static With(code: keyof typeof Errno, path?: string, syscall?: string): ErrnoError {
259
+ return new ErrnoError(Errno[code], errorMessages[Errno[code]], path, syscall);
260
260
  }
261
261
 
262
- public code: keyof typeof ErrorCode;
262
+ public code: keyof typeof Errno;
263
263
 
264
264
  public declare stack: string;
265
265
 
@@ -274,13 +274,13 @@ export class ApiError extends Error implements NodeJS.ErrnoException {
274
274
  * @param message A descriptive error message.
275
275
  */
276
276
  constructor(
277
- public errno: ErrorCode,
277
+ public errno: Errno,
278
278
  message: string = errorMessages[errno],
279
279
  public path?: string,
280
280
  public syscall: string = ''
281
281
  ) {
282
282
  super(message);
283
- this.code = <keyof typeof ErrorCode>ErrorCode[errno];
283
+ this.code = <keyof typeof Errno>Errno[errno];
284
284
  this.message = `${this.code}: ${message}${this.path ? `, '${this.path}'` : ''}`;
285
285
  }
286
286
 
@@ -291,7 +291,7 @@ export class ApiError extends Error implements NodeJS.ErrnoException {
291
291
  return this.message;
292
292
  }
293
293
 
294
- public toJSON(): ApiErrorJSON {
294
+ public toJSON(): ErrnoErrorJSON {
295
295
  return {
296
296
  errno: this.errno,
297
297
  code: this.code,
package/src/file.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { FileReadResult } from 'node:fs/promises';
2
- import { ApiError, ErrorCode } from './ApiError.js';
2
+ import { ErrnoError, Errno } from './error.js';
3
3
  import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT } from './emulation/constants.js';
4
4
  import type { FileSystem } from './filesystem.js';
5
5
  import { size_max } from './inode.js';
@@ -208,6 +208,14 @@ export abstract class File {
208
208
  */
209
209
  public abstract closeSync(): void;
210
210
 
211
+ public [Symbol.asyncDispose](): Promise<void> {
212
+ return this.close();
213
+ }
214
+
215
+ public [Symbol.dispose](): void {
216
+ return this.closeSync();
217
+ }
218
+
211
219
  /**
212
220
  * Asynchronous truncate.
213
221
  */
@@ -482,7 +490,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
482
490
  public truncateSync(len: number): void {
483
491
  this._dirty = true;
484
492
  if (!isWriteable(this.flag)) {
485
- throw new ApiError(ErrorCode.EPERM, 'File not opened with a writeable mode.');
493
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
486
494
  }
487
495
  this.stats.mtimeMs = Date.now();
488
496
  if (len > this._buffer.length) {
@@ -537,7 +545,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
537
545
  this._dirty = true;
538
546
  position ??= this.position;
539
547
  if (!isWriteable(this.flag)) {
540
- throw new ApiError(ErrorCode.EPERM, 'File not opened with a writeable mode.');
548
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
541
549
  }
542
550
  const endFp = position + length;
543
551
  if (endFp > this.stats.size) {
@@ -598,7 +606,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
598
606
  */
599
607
  public readSync(buffer: ArrayBufferView, offset: number = 0, length: number = this.stats.size, position?: number): number {
600
608
  if (!isReadable(this.flag)) {
601
- throw new ApiError(ErrorCode.EPERM, 'File not opened with a readable mode.');
609
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a readable mode.');
602
610
  }
603
611
  position ??= this.position;
604
612
  let end = position + length;