@zenfs/core 0.9.7 → 0.11.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 (90) hide show
  1. package/dist/backends/Index.d.ts +3 -3
  2. package/dist/backends/Index.js +23 -23
  3. package/dist/backends/backend.js +6 -5
  4. package/dist/backends/fetch.d.ts +84 -0
  5. package/dist/backends/fetch.js +170 -0
  6. package/dist/backends/{Locked.d.ts → locked.d.ts} +13 -13
  7. package/dist/backends/{Locked.js → locked.js} +54 -54
  8. package/dist/backends/{InMemory.d.ts → memory.d.ts} +7 -9
  9. package/dist/backends/memory.js +38 -0
  10. package/dist/backends/{Overlay.d.ts → overlay.d.ts} +12 -13
  11. package/dist/backends/{Overlay.js → overlay.js} +105 -110
  12. package/dist/backends/port/fs.d.ts +123 -0
  13. package/dist/backends/port/fs.js +239 -0
  14. package/dist/backends/port/rpc.d.ts +60 -0
  15. package/dist/backends/port/rpc.js +71 -0
  16. package/dist/backends/store/fs.d.ts +169 -0
  17. package/dist/backends/store/fs.js +743 -0
  18. package/dist/backends/store/simple.d.ts +64 -0
  19. package/dist/backends/store/simple.js +111 -0
  20. package/dist/backends/store/store.d.ts +111 -0
  21. package/dist/backends/store/store.js +62 -0
  22. package/dist/browser.min.js +4 -4
  23. package/dist/browser.min.js.map +4 -4
  24. package/dist/config.d.ts +8 -10
  25. package/dist/config.js +11 -11
  26. package/dist/emulation/async.js +6 -6
  27. package/dist/emulation/dir.js +2 -2
  28. package/dist/emulation/index.d.ts +1 -1
  29. package/dist/emulation/index.js +1 -1
  30. package/dist/emulation/path.d.ts +3 -2
  31. package/dist/emulation/path.js +19 -45
  32. package/dist/emulation/promises.d.ts +7 -12
  33. package/dist/emulation/promises.js +144 -146
  34. package/dist/emulation/shared.d.ts +5 -10
  35. package/dist/emulation/shared.js +9 -9
  36. package/dist/emulation/streams.js +3 -3
  37. package/dist/emulation/sync.js +25 -25
  38. package/dist/{ApiError.d.ts → error.d.ts} +13 -15
  39. package/dist/error.js +291 -0
  40. package/dist/file.d.ts +2 -0
  41. package/dist/file.js +11 -5
  42. package/dist/filesystem.d.ts +3 -3
  43. package/dist/filesystem.js +41 -44
  44. package/dist/index.d.ts +7 -6
  45. package/dist/index.js +7 -6
  46. package/dist/inode.d.ts +1 -1
  47. package/dist/mutex.js +2 -1
  48. package/dist/utils.d.ts +8 -7
  49. package/dist/utils.js +11 -12
  50. package/package.json +3 -3
  51. package/readme.md +17 -9
  52. package/src/backends/Index.ts +23 -23
  53. package/src/backends/backend.ts +8 -7
  54. package/src/backends/fetch.ts +229 -0
  55. package/src/backends/{Locked.ts → locked.ts} +55 -55
  56. package/src/backends/memory.ts +44 -0
  57. package/src/backends/{Overlay.ts → overlay.ts} +108 -114
  58. package/src/backends/port/fs.ts +306 -0
  59. package/src/backends/port/readme.md +59 -0
  60. package/src/backends/port/rpc.ts +144 -0
  61. package/src/backends/store/fs.ts +881 -0
  62. package/src/backends/store/readme.md +9 -0
  63. package/src/backends/store/simple.ts +144 -0
  64. package/src/backends/store/store.ts +164 -0
  65. package/src/config.ts +21 -25
  66. package/src/emulation/async.ts +6 -6
  67. package/src/emulation/dir.ts +2 -2
  68. package/src/emulation/index.ts +1 -1
  69. package/src/emulation/path.ts +25 -49
  70. package/src/emulation/promises.ts +150 -159
  71. package/src/emulation/shared.ts +13 -15
  72. package/src/emulation/streams.ts +3 -3
  73. package/src/emulation/sync.ts +28 -28
  74. package/src/{ApiError.ts → error.ts} +89 -90
  75. package/src/file.ts +13 -5
  76. package/src/filesystem.ts +44 -47
  77. package/src/index.ts +7 -6
  78. package/src/inode.ts +1 -1
  79. package/src/mutex.ts +3 -1
  80. package/src/utils.ts +16 -18
  81. package/tsconfig.json +2 -2
  82. package/dist/ApiError.js +0 -292
  83. package/dist/backends/AsyncStore.d.ts +0 -204
  84. package/dist/backends/AsyncStore.js +0 -509
  85. package/dist/backends/InMemory.js +0 -49
  86. package/dist/backends/SyncStore.d.ts +0 -213
  87. package/dist/backends/SyncStore.js +0 -445
  88. package/src/backends/AsyncStore.ts +0 -655
  89. package/src/backends/InMemory.ts +0 -56
  90. package/src/backends/SyncStore.ts +0 -589
@@ -6,9 +6,9 @@ import type { Stream } from 'node:stream';
6
6
  import type { ReadableStream as TReadableStream } from 'node:stream/web';
7
7
  import type { Interface as ReadlineInterface } from 'readline';
8
8
  import type { ReadableStreamController } from 'stream/web';
9
- import { ApiError, ErrorCode } from '../ApiError.js';
9
+ import { ErrnoError, Errno } from '../error.js';
10
10
  import { ActionType, File, isAppendable, isReadable, isWriteable, parseFlag, pathExistsAction, pathNotExistsAction } from '../file.js';
11
- import { FileContents, FileSystem } from '../filesystem.js';
11
+ import type { FileContents } from '../filesystem.js';
12
12
  import { BigIntStats, FileType, type BigIntStatsFs, type Stats, type StatsFs } from '../stats.js';
13
13
  import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
14
14
  import * as constants from './constants.js';
@@ -19,18 +19,21 @@ import { ReadStream, WriteStream } from './streams.js';
19
19
  export * as constants from './constants.js';
20
20
 
21
21
  export class FileHandle implements promises.FileHandle {
22
- public constructor(
23
- /**
24
- * Gets the file descriptor for this file handle.
25
- */
26
- public readonly fd: number
27
- ) {}
22
+ /**
23
+ * The file descriptor for this file handle.
24
+ */
25
+ public readonly fd: number;
28
26
 
29
27
  /**
30
28
  * @internal
29
+ * The file for this file handle
31
30
  */
32
- public get file(): File {
33
- return fd2file(this.fd);
31
+ public readonly file: File;
32
+
33
+ public constructor(fdOrFile: number | File) {
34
+ const isFile = typeof fdOrFile != 'number';
35
+ this.fd = isFile ? file2fd(fdOrFile) : fdOrFile;
36
+ this.file = isFile ? fdOrFile : fd2file(fdOrFile);
34
37
  }
35
38
 
36
39
  /**
@@ -47,7 +50,7 @@ export class FileHandle implements promises.FileHandle {
47
50
  public chmod(mode: fs.Mode): Promise<void> {
48
51
  const numMode = normalizeMode(mode, -1);
49
52
  if (numMode < 0) {
50
- throw new ApiError(ErrorCode.EINVAL, 'Invalid mode.');
53
+ throw new ErrnoError(Errno.EINVAL, 'Invalid mode.');
51
54
  }
52
55
  return this.file.chmod(numMode);
53
56
  }
@@ -73,7 +76,7 @@ export class FileHandle implements promises.FileHandle {
73
76
  public truncate(len?: number | null): Promise<void> {
74
77
  len ||= 0;
75
78
  if (len < 0) {
76
- throw new ApiError(ErrorCode.EINVAL);
79
+ throw new ErrnoError(Errno.EINVAL);
77
80
  }
78
81
  return this.file.truncate(len);
79
82
  }
@@ -101,10 +104,10 @@ export class FileHandle implements promises.FileHandle {
101
104
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
102
105
  const flag = parseFlag(options.flag);
103
106
  if (!isAppendable(flag)) {
104
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed to appendFile must allow for appending.');
107
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.');
105
108
  }
106
109
  if (typeof data != 'string' && !options.encoding) {
107
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
110
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
108
111
  }
109
112
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : data;
110
113
  await this.file.write(encodedData, 0, encodedData.length, null);
@@ -133,11 +136,11 @@ export class FileHandle implements promises.FileHandle {
133
136
  */
134
137
  public async readFile(_options?: { flag?: fs.OpenMode }): Promise<Buffer>;
135
138
  public async readFile(_options: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<string>;
136
- public async readFile(_options: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding = {}): Promise<string | Buffer> {
139
+ public async readFile(_options?: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<string | Buffer> {
137
140
  const options = normalizeOptions(_options, null, 'r', 0o444);
138
141
  const flag = parseFlag(options.flag);
139
142
  if (!isReadable(flag)) {
140
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for reading.');
143
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed must allow for reading.');
141
144
  }
142
145
 
143
146
  const { size } = await this.stat();
@@ -177,7 +180,7 @@ export class FileHandle implements promises.FileHandle {
177
180
  enqueue(result.buffer.slice(0, result.bytesRead));
178
181
  position += result.bytesRead;
179
182
  if (++i >= maxChunks) {
180
- throw new ApiError(ErrorCode.EFBIG, 'Too many iterations on readable stream', this.file.path, 'FileHandle.readableWebStream');
183
+ throw new ErrnoError(Errno.EFBIG, 'Too many iterations on readable stream', this.file.path, 'FileHandle.readableWebStream');
181
184
  }
182
185
  bytesRead = result.bytesRead;
183
186
  }
@@ -190,7 +193,7 @@ export class FileHandle implements promises.FileHandle {
190
193
  }
191
194
 
192
195
  public readLines(options?: promises.CreateReadStreamOptions): ReadlineInterface {
193
- throw ApiError.With('ENOSYS', this.file.path, 'FileHandle.readLines');
196
+ throw ErrnoError.With('ENOSYS', this.file.path, 'FileHandle.readLines');
194
197
  }
195
198
 
196
199
  public [Symbol.asyncDispose](): Promise<void> {
@@ -262,10 +265,10 @@ export class FileHandle implements promises.FileHandle {
262
265
  const options = normalizeOptions(_options, 'utf8', 'w', 0o644);
263
266
  const flag = parseFlag(options.flag);
264
267
  if (!isWriteable(flag)) {
265
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for writing.');
268
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed must allow for writing.');
266
269
  }
267
270
  if (typeof data != 'string' && !options.encoding) {
268
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
271
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
269
272
  }
270
273
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : data;
271
274
  await this.file.write(encodedData, 0, encodedData.length, 0);
@@ -328,7 +331,7 @@ export class FileHandle implements promises.FileHandle {
328
331
  stream.push(!result.bytesRead ? null : result.buffer.slice(0, result.bytesRead)); // Push data or null for EOF
329
332
  this.file.position += result.bytesRead;
330
333
  } catch (error) {
331
- stream.destroy(<Error>error);
334
+ stream.destroy(error as Error);
332
335
  }
333
336
  },
334
337
  });
@@ -353,7 +356,7 @@ export class FileHandle implements promises.FileHandle {
353
356
  const { bytesWritten } = await this.write(chunk, null, encoding);
354
357
  callback(bytesWritten == chunk.length ? null : new Error('Failed to write full chunk'));
355
358
  } catch (error) {
356
- callback(<Error>error);
359
+ callback(error as Error);
357
360
  }
358
361
  },
359
362
  };
@@ -364,38 +367,6 @@ export class FileHandle implements promises.FileHandle {
364
367
  }
365
368
  }
366
369
 
367
- type FileSystemMethod = {
368
- [K in keyof FileSystem]: FileSystem[K] extends (...args: any[]) => unknown
369
- ? (name: K, resolveSymlinks: boolean, ...args: Parameters<FileSystem[K]>) => ReturnType<FileSystem[K]>
370
- : never;
371
- }[keyof FileSystem]; // https://stackoverflow.com/a/76335220/17637456
372
-
373
- /**
374
- * Utility for FS ops. It handles
375
- * - path normalization (for the first parameter to the FS op)
376
- * - path translation for errors
377
- * - FS/mount point resolution
378
- *
379
- * It can't be used for functions which may operate on multiple mounted FSs or paths (e.g. `rename`)
380
- * @param name the function name
381
- * @param resolveSymlinks whether to resolve symlinks
382
- * @param args the rest of the parameters are passed to the FS function. Note that the first parameter is required to be a path
383
- * @returns
384
- */
385
- async function doOp<M extends FileSystemMethod, RT extends ReturnType<M> = ReturnType<M>>(...[name, resolveSymlinks, rawPath, ...args]: Parameters<M>): Promise<RT> {
386
- rawPath = normalizePath(rawPath!);
387
- const _path = resolveSymlinks && (await exists(rawPath)) ? await realpath(rawPath) : rawPath;
388
- const { fs, path } = resolveMount(_path);
389
- try {
390
- // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
391
- return fs[name](path, ...args) as Promise<RT>;
392
- } catch (e) {
393
- throw fixError(<Error>e, { [path]: rawPath });
394
- }
395
- }
396
-
397
- // fs.promises
398
-
399
370
  /**
400
371
  * Renames a file
401
372
  * @param oldPath
@@ -414,21 +385,21 @@ export async function rename(oldPath: fs.PathLike, newPath: fs.PathLike): Promis
414
385
  await writeFile(newPath, await readFile(oldPath));
415
386
  await unlink(oldPath);
416
387
  } catch (e) {
417
- throw fixError(<Error>e, { [src.path]: oldPath, [dst.path]: newPath });
388
+ throw fixError(e as Error, { [src.path]: oldPath, [dst.path]: newPath });
418
389
  }
419
390
  }
420
391
  rename satisfies typeof promises.rename;
421
392
 
422
393
  /**
423
394
  * Test whether or not the given path exists by checking with the file system.
424
- * @param _path
395
+ * @param path
425
396
  */
426
- export async function exists(_path: fs.PathLike): Promise<boolean> {
397
+ export async function exists(path: fs.PathLike): Promise<boolean> {
427
398
  try {
428
- const { fs, path } = resolveMount(await realpath(_path));
429
- return await fs.exists(path, cred);
399
+ const { fs, path: resolved } = resolveMount(await realpath(path));
400
+ return await fs.exists(resolved, cred);
430
401
  } catch (e) {
431
- if ((e as ApiError).errno == ErrorCode.ENOENT) {
402
+ if (e instanceof ErrnoError && e.code == 'ENOENT') {
432
403
  return false;
433
404
  }
434
405
 
@@ -445,8 +416,14 @@ export async function stat(path: fs.PathLike, options: fs.BigIntOptions): Promis
445
416
  export async function stat(path: fs.PathLike, options?: { bigint?: false }): Promise<Stats>;
446
417
  export async function stat(path: fs.PathLike, options?: fs.StatOptions): Promise<Stats | BigIntStats>;
447
418
  export async function stat(path: fs.PathLike, options?: fs.StatOptions): Promise<Stats | BigIntStats> {
448
- const stats: Stats = await doOp('stat', true, path.toString(), cred);
449
- return options?.bigint ? new BigIntStats(stats) : stats;
419
+ path = normalizePath(path);
420
+ const { fs, path: resolved } = resolveMount((await exists(path)) ? await realpath(path) : path);
421
+ try {
422
+ const stats = await fs.stat(resolved, cred);
423
+ return options?.bigint ? new BigIntStats(stats) : stats;
424
+ } catch (e) {
425
+ throw fixError(e as Error, { [resolved]: path });
426
+ }
450
427
  }
451
428
  stat satisfies typeof promises.stat;
452
429
 
@@ -460,8 +437,14 @@ stat satisfies typeof promises.stat;
460
437
  export async function lstat(path: fs.PathLike, options?: { bigint?: boolean }): Promise<Stats>;
461
438
  export async function lstat(path: fs.PathLike, options: { bigint: true }): Promise<BigIntStats>;
462
439
  export async function lstat(path: fs.PathLike, options?: fs.StatOptions): Promise<Stats | BigIntStats> {
463
- const stats: Stats = await doOp('stat', false, path.toString(), cred);
464
- return options?.bigint ? new BigIntStats(stats) : stats;
440
+ path = normalizePath(path);
441
+ const { fs, path: resolved } = resolveMount(path);
442
+ try {
443
+ const stats = await fs.stat(resolved, cred);
444
+ return options?.bigint ? new BigIntStats(stats) : stats;
445
+ } catch (e) {
446
+ throw fixError(e as Error, { [resolved]: path });
447
+ }
465
448
  }
466
449
  lstat satisfies typeof promises.lstat;
467
450
 
@@ -487,7 +470,13 @@ truncate satisfies typeof promises.truncate;
487
470
  * @param path
488
471
  */
489
472
  export async function unlink(path: fs.PathLike): Promise<void> {
490
- return doOp('unlink', false, path.toString(), cred);
473
+ path = normalizePath(path);
474
+ const { fs, path: resolved } = resolveMount(path);
475
+ try {
476
+ await fs.unlink(resolved, cred);
477
+ } catch (e) {
478
+ throw fixError(e as Error, { [resolved]: path });
479
+ }
491
480
  }
492
481
  unlink satisfies typeof promises.unlink;
493
482
 
@@ -495,15 +484,18 @@ unlink satisfies typeof promises.unlink;
495
484
  * Opens a file. This helper handles the complexity of file flags.
496
485
  * @internal
497
486
  */
498
- async function _open(_path: fs.PathLike, _flag: fs.OpenMode, _mode: fs.Mode = 0o644, resolveSymlinks: boolean): Promise<File> {
499
- const path = normalizePath(_path),
500
- mode = normalizeMode(_mode, 0o644),
487
+ async function _open(path: fs.PathLike, _flag: fs.OpenMode, _mode: fs.Mode = 0o644, resolveSymlinks: boolean): Promise<FileHandle> {
488
+ path = normalizePath(path);
489
+ const mode = normalizeMode(_mode, 0o644),
501
490
  flag = parseFlag(_flag);
502
491
 
492
+ path = resolveSymlinks && (await exists(path)) ? await realpath(path) : path;
493
+ const { fs, path: resolved } = resolveMount(path);
494
+
503
495
  try {
504
496
  switch (pathExistsAction(flag)) {
505
497
  case ActionType.THROW:
506
- throw ApiError.With('EEXIST', path, '_open');
498
+ throw ErrnoError.With('EEXIST', path, '_open');
507
499
  case ActionType.TRUNCATE:
508
500
  /*
509
501
  In a previous implementation, we deleted the file and
@@ -511,32 +503,29 @@ async function _open(_path: fs.PathLike, _flag: fs.OpenMode, _mode: fs.Mode = 0o
511
503
  asynchronous request was trying to read the file, as the file
512
504
  would not exist for a small period of time.
513
505
  */
514
- const file: File = await doOp('openFile', resolveSymlinks, path, flag, cred);
515
- if (!file) {
516
- throw new ApiError(ErrorCode.EIO, 'Impossible code path reached');
517
- }
506
+ const file: File = await fs.openFile(resolved, flag, cred);
518
507
  await file.truncate(0);
519
508
  await file.sync();
520
- return file;
509
+ return new FileHandle(file);
521
510
  case ActionType.NOP:
522
511
  // Must await so thrown errors are caught by the catch below
523
- return await doOp('openFile', resolveSymlinks, path, flag, cred);
512
+ return new FileHandle(await fs.openFile(resolved, flag, cred));
524
513
  default:
525
- throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag');
514
+ throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
526
515
  }
527
516
  } catch (e) {
528
517
  switch (pathNotExistsAction(flag)) {
529
518
  case ActionType.CREATE:
530
519
  // Ensure parent exists.
531
- const parentStats: Stats = await doOp('stat', resolveSymlinks, dirname(path), cred);
520
+ const parentStats: Stats = await fs.stat(dirname(resolved), cred);
532
521
  if (parentStats && !parentStats.isDirectory()) {
533
- throw ApiError.With('ENOTDIR', dirname(path), '_open');
522
+ throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
534
523
  }
535
- return await doOp('createFile', resolveSymlinks, path, flag, mode, cred);
524
+ return new FileHandle(await fs.createFile(resolved, flag, mode, cred));
536
525
  case ActionType.THROW:
537
- throw ApiError.With('ENOENT', path, '_open');
526
+ throw ErrnoError.With('ENOENT', path, '_open');
538
527
  default:
539
- throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag');
528
+ throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
540
529
  }
541
530
  }
542
531
  }
@@ -548,29 +537,10 @@ async function _open(_path: fs.PathLike, _flag: fs.OpenMode, _mode: fs.Mode = 0o
548
537
  * @param mode Mode to use to open the file. Can be ignored if the filesystem doesn't support permissions.
549
538
  */
550
539
  export async function open(path: fs.PathLike, flag: fs.OpenMode = 'r', mode: fs.Mode = 0o644): Promise<FileHandle> {
551
- const file = await _open(path, flag, mode, true);
552
- return new FileHandle(file2fd(file));
540
+ return await _open(path, flag, mode, true);
553
541
  }
554
542
  open satisfies typeof promises.open;
555
543
 
556
- /**
557
- * Asynchronously reads the entire contents of a file.
558
- */
559
- async function _readFile(fname: string, flag: string, resolveSymlinks: boolean): Promise<Uint8Array> {
560
- const file = await _open(normalizePath(fname), flag, 0o644, resolveSymlinks);
561
-
562
- try {
563
- const stat = await file.stat();
564
- const data = new Uint8Array(stat.size);
565
- await file.read(data, 0, stat.size, 0);
566
- await file.close();
567
- return data;
568
- } catch (e) {
569
- await file.close();
570
- throw e;
571
- }
572
- }
573
-
574
544
  /**
575
545
  * Asynchronously reads the entire contents of a file.
576
546
  * @param filename
@@ -589,14 +559,14 @@ export async function readFile(
589
559
  path: fs.PathLike | promises.FileHandle,
590
560
  _options?: (fs.ObjectEncodingOptions & { flag?: fs.OpenMode }) | BufferEncoding | null
591
561
  ): Promise<Buffer | string> {
592
- const options = normalizeOptions(_options, null, 'r', 0);
593
- const flag = parseFlag(options.flag);
594
- if (!isReadable(flag)) {
595
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for reading.');
562
+ const options = normalizeOptions(_options, null, 'r', 0o644);
563
+ const handle: FileHandle | promises.FileHandle = typeof path == 'object' && 'fd' in path ? path : await open(path as string, options.flag, options.mode);
564
+
565
+ try {
566
+ return await handle.readFile(options);
567
+ } finally {
568
+ await handle.close();
596
569
  }
597
- path = path instanceof FileHandle ? path.file.path : path.toString();
598
- const data: Buffer = Buffer.from(await _readFile(path, options.flag, true));
599
- return options.encoding ? data.toString(options.encoding) : data;
600
570
  }
601
571
  readFile satisfies typeof promises.readFile;
602
572
 
@@ -621,7 +591,7 @@ export async function writeFile(
621
591
  try {
622
592
  const _data = typeof data == 'string' ? data : data;
623
593
  if (typeof _data != 'string' && !(_data instanceof Uint8Array)) {
624
- throw new ApiError(ErrorCode.EINVAL, 'Iterables and streams not supported', handle.file.path, 'writeFile');
594
+ throw new ErrnoError(Errno.EINVAL, 'Iterables and streams not supported', handle.file.path, 'writeFile');
625
595
  }
626
596
  await handle.writeFile(_data, options);
627
597
  } finally {
@@ -630,19 +600,6 @@ export async function writeFile(
630
600
  }
631
601
  writeFile satisfies typeof promises.writeFile;
632
602
 
633
- /**
634
- * Asynchronously append data to a file, creating the file if
635
- * it not yet exists.
636
- */
637
- async function _appendFile(path: fs.PathLike, data: Uint8Array, flag: string, mode: number, resolveSymlinks: boolean): Promise<void> {
638
- const file = await _open(path, flag, mode, resolveSymlinks);
639
- try {
640
- await file.write(data, 0, data.length, null);
641
- } finally {
642
- await file.close();
643
- }
644
- }
645
-
646
603
  /**
647
604
  * Asynchronously append data to a file, creating the file if it not yet
648
605
  * exists.
@@ -661,13 +618,19 @@ export async function appendFile(
661
618
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
662
619
  const flag = parseFlag(options.flag);
663
620
  if (!isAppendable(flag)) {
664
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed to appendFile must allow for appending.');
621
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.');
665
622
  }
666
623
  if (typeof data != 'string' && !options.encoding) {
667
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
624
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
668
625
  }
669
626
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
670
- await _appendFile(path instanceof FileHandle ? path.file.path : path.toString(), encodedData, options.flag, options.mode, true);
627
+ const handle: FileHandle | promises.FileHandle = typeof path == 'object' && 'fd' in path ? path : await open(path as string, options.flag, options.mode);
628
+
629
+ try {
630
+ await handle.appendFile(encodedData, options);
631
+ } finally {
632
+ await handle.close();
633
+ }
671
634
  }
672
635
  appendFile satisfies typeof promises.appendFile;
673
636
 
@@ -678,7 +641,14 @@ appendFile satisfies typeof promises.appendFile;
678
641
  * @param path
679
642
  */
680
643
  export async function rmdir(path: fs.PathLike): Promise<void> {
681
- return doOp('rmdir', true, path.toString(), cred);
644
+ path = normalizePath(path);
645
+ path = (await exists(path)) ? await realpath(path) : path;
646
+ const { fs, path: resolved } = resolveMount(path);
647
+ try {
648
+ await fs.rmdir(resolved, cred);
649
+ } catch (e) {
650
+ throw fixError(e as Error, { [resolved]: path });
651
+ }
682
652
  }
683
653
  rmdir satisfies typeof promises.rmdir;
684
654
 
@@ -692,8 +662,14 @@ export async function mkdir(path: fs.PathLike, options: fs.MakeDirectoryOptions
692
662
  export async function mkdir(path: fs.PathLike, options?: fs.Mode | (fs.MakeDirectoryOptions & { recursive?: false | undefined }) | null): Promise<void>;
693
663
  export async function mkdir(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): Promise<string | undefined>;
694
664
  export async function mkdir(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): Promise<string | undefined | void> {
695
- await doOp('mkdir', true, path.toString(), normalizeMode(typeof options == 'object' ? options?.mode : options, 0o777), cred);
696
- return;
665
+ path = normalizePath(path);
666
+ path = (await exists(path)) ? await realpath(path) : path;
667
+ const { fs, path: resolved } = resolveMount(path);
668
+ try {
669
+ await fs.mkdir(resolved, normalizeMode(typeof options == 'object' ? options?.mode : options, 0o777), cred);
670
+ } catch (e) {
671
+ throw fixError(e as Error, { [resolved]: path });
672
+ }
697
673
  }
698
674
  mkdir satisfies typeof promises.mkdir;
699
675
 
@@ -714,9 +690,15 @@ export async function readdir(
714
690
  options?: { withFileTypes?: boolean; recursive?: boolean; encoding?: BufferEncoding | 'buffer' | null } | BufferEncoding | 'buffer' | null
715
691
  ): Promise<string[] | Dirent[] | Buffer[]> {
716
692
  path = normalizePath(path);
717
- const entries: string[] = await doOp('readdir', true, path, cred);
718
- const points = [...mounts.keys()];
719
- for (const point of points) {
693
+ path = (await exists(path)) ? await realpath(path) : path;
694
+ const { fs, path: resolved } = resolveMount(path);
695
+ let entries: string[];
696
+ try {
697
+ entries = await fs.readdir(resolved, cred);
698
+ } catch (e) {
699
+ throw fixError(e as Error, { [resolved]: path });
700
+ }
701
+ for (const point of mounts.keys()) {
720
702
  if (point.startsWith(path)) {
721
703
  const entry = point.slice(path.length);
722
704
  if (entry.includes('/') || entry.length == 0) {
@@ -742,8 +724,14 @@ readdir satisfies typeof promises.readdir;
742
724
  * @param newpath
743
725
  */
744
726
  export async function link(existing: fs.PathLike, newpath: fs.PathLike): Promise<void> {
727
+ existing = normalizePath(existing);
745
728
  newpath = normalizePath(newpath);
746
- return doOp('link', false, existing.toString(), newpath, cred);
729
+ const { fs, path: resolved } = resolveMount(newpath);
730
+ try {
731
+ return await fs.link(existing, newpath, cred);
732
+ } catch (e) {
733
+ throw fixError(e as Error, { [resolved]: newpath });
734
+ }
747
735
  }
748
736
  link satisfies typeof promises.link;
749
737
 
@@ -755,16 +743,16 @@ link satisfies typeof promises.link;
755
743
  */
756
744
  export async function symlink(target: fs.PathLike, path: fs.PathLike, type: fs.symlink.Type | string | null = 'file'): Promise<void> {
757
745
  if (!['file', 'dir', 'junction'].includes(type!)) {
758
- throw new ApiError(ErrorCode.EINVAL, 'Invalid symlink type: ' + type);
746
+ throw new ErrnoError(Errno.EINVAL, 'Invalid symlink type: ' + type);
759
747
  }
760
748
 
761
749
  if (await exists(path)) {
762
- throw ApiError.With('EEXIST', path.toString(), 'symlink');
750
+ throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
763
751
  }
764
752
 
765
753
  await writeFile(path, target.toString());
766
- const file = await _open(path, 'r+', 0o644, false);
767
- await file._setType(FileType.SYMLINK);
754
+ const handle = await _open(path, 'r+', 0o644, false);
755
+ await handle.file._setType(FileType.SYMLINK);
768
756
  }
769
757
  symlink satisfies typeof promises.symlink;
770
758
 
@@ -776,9 +764,14 @@ export async function readlink(path: fs.PathLike, options: fs.BufferEncodingOpti
776
764
  export async function readlink(path: fs.PathLike, options?: fs.EncodingOption | null): Promise<string>;
777
765
  export async function readlink(path: fs.PathLike, options?: fs.BufferEncodingOption | fs.EncodingOption | string | null): Promise<string | Buffer>;
778
766
  export async function readlink(path: fs.PathLike, options?: fs.BufferEncodingOption | fs.EncodingOption | string | null): Promise<string | Buffer> {
779
- const value: Buffer = Buffer.from(await _readFile(path.toString(), 'r', false));
780
- const encoding = typeof options == 'object' ? options?.encoding : options;
781
- return encoding == 'buffer' ? value : value.toString(encoding! as BufferEncoding);
767
+ const handle = await _open(normalizePath(path), 'r', 0o644, false);
768
+ try {
769
+ const value = await handle.readFile();
770
+ const encoding = typeof options == 'object' ? options?.encoding : options;
771
+ return encoding == 'buffer' ? value : value.toString(encoding! as BufferEncoding);
772
+ } finally {
773
+ await handle.close();
774
+ }
782
775
  }
783
776
  readlink satisfies typeof promises.readlink;
784
777
 
@@ -807,11 +800,11 @@ chown satisfies typeof promises.chown;
807
800
  * @param gid
808
801
  */
809
802
  export async function lchown(path: fs.PathLike, uid: number, gid: number): Promise<void> {
810
- const file: File = await _open(path, 'r+', 0o644, false);
803
+ const handle: FileHandle = await _open(path, 'r+', 0o644, false);
811
804
  try {
812
- await file.chown(uid, gid);
805
+ await handle.chown(uid, gid);
813
806
  } finally {
814
- await file.close();
807
+ await handle.close();
815
808
  }
816
809
  }
817
810
  lchown satisfies typeof promises.lchown;
@@ -837,11 +830,11 @@ chmod satisfies typeof promises.chmod;
837
830
  * @param mode
838
831
  */
839
832
  export async function lchmod(path: fs.PathLike, mode: fs.Mode): Promise<void> {
840
- const file: File = await _open(path, 'r+', 0o644, false);
833
+ const handle: FileHandle = await _open(path, 'r+', 0o644, false);
841
834
  try {
842
- await new FileHandle(file2fd(file)).chmod(mode);
835
+ await handle.chmod(mode);
843
836
  } finally {
844
- await file.close();
837
+ await handle.close();
845
838
  }
846
839
  }
847
840
  lchmod satisfies typeof promises.lchmod;
@@ -869,11 +862,11 @@ utimes satisfies typeof promises.utimes;
869
862
  * @param mtime
870
863
  */
871
864
  export async function lutimes(path: fs.PathLike, atime: fs.TimeLike, mtime: fs.TimeLike): Promise<void> {
872
- const file: File = await _open(path, 'r+', 0o644, false);
865
+ const handle: FileHandle = await _open(path, 'r+', 0o644, false);
873
866
  try {
874
- await file.utimes(new Date(atime), new Date(mtime));
867
+ await handle.utimes(new Date(atime), new Date(mtime));
875
868
  } finally {
876
- await file.close();
869
+ await handle.close();
877
870
  }
878
871
  }
879
872
  lutimes satisfies typeof promises.lutimes;
@@ -882,8 +875,6 @@ lutimes satisfies typeof promises.lutimes;
882
875
  * Asynchronous realpath(3) - return the canonicalized absolute pathname.
883
876
  * @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
884
877
  * @param options The encoding (or an object specifying the encoding), used as the encoding of the result. If not provided, `'utf8'` is used.
885
- *
886
- * Note: This *Can not* use doOp since doOp depends on it
887
878
  */
888
879
  export async function realpath(path: fs.PathLike, options: fs.BufferEncodingOption): Promise<Buffer>;
889
880
  export async function realpath(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding): Promise<string>;
@@ -901,7 +892,7 @@ export async function realpath(path: fs.PathLike, options?: fs.EncodingOption |
901
892
 
902
893
  return realpath(mountPoint + (await readlink(lpath)));
903
894
  } catch (e) {
904
- throw fixError(<Error>e, { [resolvedPath]: lpath });
895
+ throw fixError(e as Error, { [resolvedPath]: lpath });
905
896
  }
906
897
  }
907
898
  realpath satisfies typeof promises.realpath;
@@ -913,7 +904,7 @@ export function watch(filename: fs.PathLike, options?: fs.WatchOptions | BufferE
913
904
  export function watch(filename: fs.PathLike, options: fs.WatchOptions | fs.BufferEncodingOption): AsyncIterable<FileChangeInfo<Buffer>>;
914
905
  export function watch(filename: fs.PathLike, options?: fs.WatchOptions | string): AsyncIterable<FileChangeInfo<string>> | AsyncIterable<FileChangeInfo<Buffer>>;
915
906
  export function watch(filename: fs.PathLike, options: fs.WatchOptions | string = {}): AsyncIterable<FileChangeInfo<string>> | AsyncIterable<FileChangeInfo<Buffer>> {
916
- throw ApiError.With('ENOSYS', filename.toString(), 'watch');
907
+ throw ErrnoError.With('ENOSYS', filename.toString(), 'watch');
917
908
  }
918
909
  watch satisfies typeof promises.watch;
919
910
 
@@ -925,7 +916,7 @@ watch satisfies typeof promises.watch;
925
916
  export async function access(path: fs.PathLike, mode: number = constants.F_OK): Promise<void> {
926
917
  const stats = await stat(path);
927
918
  if (!stats.hasAccess(mode, cred)) {
928
- throw new ApiError(ErrorCode.EACCES);
919
+ throw new ErrnoError(Errno.EACCES);
929
920
  }
930
921
  }
931
922
  access satisfies typeof promises.access;
@@ -958,7 +949,7 @@ export async function rm(path: fs.PathLike, options?: fs.RmOptions) {
958
949
  case constants.S_IFIFO:
959
950
  case constants.S_IFSOCK:
960
951
  default:
961
- throw new ApiError(ErrorCode.EPERM, 'File type not supported', path, 'rm');
952
+ throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
962
953
  }
963
954
  }
964
955
  rm satisfies typeof promises.rm;
@@ -994,7 +985,7 @@ export async function copyFile(src: fs.PathLike, dest: fs.PathLike, mode?: numbe
994
985
  dest = normalizePath(dest);
995
986
 
996
987
  if (mode && mode & constants.COPYFILE_EXCL && (await exists(dest))) {
997
- throw new ApiError(ErrorCode.EEXIST, 'Destination file already exists.', dest, 'copyFile');
988
+ throw new ErrnoError(Errno.EEXIST, 'Destination file already exists.', dest, 'copyFile');
998
989
  }
999
990
 
1000
991
  await writeFile(dest, await readFile(src));
@@ -1032,13 +1023,13 @@ export async function cp(source: fs.PathLike, destination: fs.PathLike, opts?: f
1032
1023
  const srcStats = await lstat(source); // Use lstat to follow symlinks if not dereferencing
1033
1024
 
1034
1025
  if (opts?.errorOnExist && (await exists(destination))) {
1035
- throw new ApiError(ErrorCode.EEXIST, 'Destination file or directory already exists.', destination, 'cp');
1026
+ throw new ErrnoError(Errno.EEXIST, 'Destination file or directory already exists.', destination, 'cp');
1036
1027
  }
1037
1028
 
1038
1029
  switch (srcStats.mode & constants.S_IFMT) {
1039
1030
  case constants.S_IFDIR:
1040
1031
  if (!opts?.recursive) {
1041
- throw new ApiError(ErrorCode.EISDIR, source + ' is a directory (not copied)', source, 'cp');
1032
+ throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp');
1042
1033
  }
1043
1034
  await mkdir(destination, { recursive: true }); // Ensure the destination directory exists
1044
1035
  for (const dirent of await readdir(source, { withFileTypes: true })) {
@@ -1057,7 +1048,7 @@ export async function cp(source: fs.PathLike, destination: fs.PathLike, opts?: f
1057
1048
  case constants.S_IFIFO:
1058
1049
  case constants.S_IFSOCK:
1059
1050
  default:
1060
- throw new ApiError(ErrorCode.EPERM, 'File type not supported', source, 'rm');
1051
+ throw new ErrnoError(Errno.EPERM, 'File type not supported', source, 'rm');
1061
1052
  }
1062
1053
 
1063
1054
  // Optionally preserve timestamps
@@ -1075,5 +1066,5 @@ export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions & { bigi
1075
1066
  export async function statfs(path: fs.PathLike, opts: fs.StatFsOptions & { bigint: true }): Promise<BigIntStatsFs>;
1076
1067
  export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions): Promise<StatsFs | BigIntStatsFs>;
1077
1068
  export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions): Promise<StatsFs | BigIntStatsFs> {
1078
- throw ApiError.With('ENOSYS', path.toString(), 'statfs');
1069
+ throw ErrnoError.With('ENOSYS', path.toString(), 'statfs');
1079
1070
  }