@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,5 +1,5 @@
1
1
  import { Buffer } from 'buffer';
2
- import { ApiError, ErrorCode } from '../ApiError.js';
2
+ import { ErrnoError, Errno } from '../error.js';
3
3
  import { ActionType, isAppendable, isReadable, isWriteable, parseFlag, pathExistsAction, pathNotExistsAction } from '../file.js';
4
4
  import { BigIntStats, FileType } from '../stats.js';
5
5
  import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
@@ -10,18 +10,10 @@ import { cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from '.
10
10
  import { ReadStream, WriteStream } from './streams.js';
11
11
  export * as constants from './constants.js';
12
12
  export class FileHandle {
13
- constructor(
14
- /**
15
- * Gets the file descriptor for this file handle.
16
- */
17
- fd) {
18
- this.fd = fd;
19
- }
20
- /**
21
- * @internal
22
- */
23
- get file() {
24
- return fd2file(this.fd);
13
+ constructor(fdOrFile) {
14
+ const isFile = typeof fdOrFile != 'number';
15
+ this.fd = isFile ? file2fd(fdOrFile) : fdOrFile;
16
+ this.file = isFile ? fdOrFile : fd2file(fdOrFile);
25
17
  }
26
18
  /**
27
19
  * Asynchronous fchown(2) - Change ownership of a file.
@@ -36,7 +28,7 @@ export class FileHandle {
36
28
  chmod(mode) {
37
29
  const numMode = normalizeMode(mode, -1);
38
30
  if (numMode < 0) {
39
- throw new ApiError(ErrorCode.EINVAL, 'Invalid mode.');
31
+ throw new ErrnoError(Errno.EINVAL, 'Invalid mode.');
40
32
  }
41
33
  return this.file.chmod(numMode);
42
34
  }
@@ -59,7 +51,7 @@ export class FileHandle {
59
51
  truncate(len) {
60
52
  len || (len = 0);
61
53
  if (len < 0) {
62
- throw new ApiError(ErrorCode.EINVAL);
54
+ throw new ErrnoError(Errno.EINVAL);
63
55
  }
64
56
  return this.file.truncate(len);
65
57
  }
@@ -85,10 +77,10 @@ export class FileHandle {
85
77
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
86
78
  const flag = parseFlag(options.flag);
87
79
  if (!isAppendable(flag)) {
88
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed to appendFile must allow for appending.');
80
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.');
89
81
  }
90
82
  if (typeof data != 'string' && !options.encoding) {
91
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
83
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
92
84
  }
93
85
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
94
86
  await this.file.write(encodedData, 0, encodedData.length, null);
@@ -107,11 +99,11 @@ export class FileHandle {
107
99
  }
108
100
  return this.file.read(buffer, offset, length, position);
109
101
  }
110
- async readFile(_options = {}) {
102
+ async readFile(_options) {
111
103
  const options = normalizeOptions(_options, null, 'r', 0o444);
112
104
  const flag = parseFlag(options.flag);
113
105
  if (!isReadable(flag)) {
114
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for reading.');
106
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed must allow for reading.');
115
107
  }
116
108
  const { size } = await this.stat();
117
109
  const data = new Uint8Array(size);
@@ -145,7 +137,7 @@ export class FileHandle {
145
137
  enqueue(result.buffer.slice(0, result.bytesRead));
146
138
  position += result.bytesRead;
147
139
  if (++i >= maxChunks) {
148
- throw new ApiError(ErrorCode.EFBIG, 'Too many iterations on readable stream', this.file.path, 'FileHandle.readableWebStream');
140
+ throw new ErrnoError(Errno.EFBIG, 'Too many iterations on readable stream', this.file.path, 'FileHandle.readableWebStream');
149
141
  }
150
142
  bytesRead = result.bytesRead;
151
143
  }
@@ -157,7 +149,7 @@ export class FileHandle {
157
149
  return new globalThis.ReadableStream({ start, type: options.type });
158
150
  }
159
151
  readLines(options) {
160
- throw ApiError.With('ENOSYS', this.file.path, 'FileHandle.readLines');
152
+ throw ErrnoError.With('ENOSYS', this.file.path, 'FileHandle.readLines');
161
153
  }
162
154
  [Symbol.asyncDispose]() {
163
155
  return this.close();
@@ -202,10 +194,10 @@ export class FileHandle {
202
194
  const options = normalizeOptions(_options, 'utf8', 'w', 0o644);
203
195
  const flag = parseFlag(options.flag);
204
196
  if (!isWriteable(flag)) {
205
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for writing.');
197
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed must allow for writing.');
206
198
  }
207
199
  if (typeof data != 'string' && !options.encoding) {
208
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
200
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
209
201
  }
210
202
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
211
203
  await this.file.write(encodedData, 0, encodedData.length, 0);
@@ -292,31 +284,6 @@ export class FileHandle {
292
284
  return stream;
293
285
  }
294
286
  }
295
- /**
296
- * Utility for FS ops. It handles
297
- * - path normalization (for the first parameter to the FS op)
298
- * - path translation for errors
299
- * - FS/mount point resolution
300
- *
301
- * It can't be used for functions which may operate on multiple mounted FSs or paths (e.g. `rename`)
302
- * @param name the function name
303
- * @param resolveSymlinks whether to resolve symlinks
304
- * @param args the rest of the parameters are passed to the FS function. Note that the first parameter is required to be a path
305
- * @returns
306
- */
307
- async function doOp(...[name, resolveSymlinks, rawPath, ...args]) {
308
- rawPath = normalizePath(rawPath);
309
- const _path = resolveSymlinks && (await exists(rawPath)) ? await realpath(rawPath) : rawPath;
310
- const { fs, path } = resolveMount(_path);
311
- try {
312
- // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
313
- return fs[name](path, ...args);
314
- }
315
- catch (e) {
316
- throw fixError(e, { [path]: rawPath });
317
- }
318
- }
319
- // fs.promises
320
287
  /**
321
288
  * Renames a file
322
289
  * @param oldPath
@@ -342,28 +309,42 @@ export async function rename(oldPath, newPath) {
342
309
  rename;
343
310
  /**
344
311
  * Test whether or not the given path exists by checking with the file system.
345
- * @param _path
312
+ * @param path
346
313
  */
347
- export async function exists(_path) {
314
+ export async function exists(path) {
348
315
  try {
349
- const { fs, path } = resolveMount(await realpath(_path));
350
- return await fs.exists(path, cred);
316
+ const { fs, path: resolved } = resolveMount(await realpath(path));
317
+ return await fs.exists(resolved, cred);
351
318
  }
352
319
  catch (e) {
353
- if (e.errno == ErrorCode.ENOENT) {
320
+ if (e instanceof ErrnoError && e.code == 'ENOENT') {
354
321
  return false;
355
322
  }
356
323
  throw e;
357
324
  }
358
325
  }
359
326
  export async function stat(path, options) {
360
- const stats = await doOp('stat', true, path.toString(), cred);
361
- return options?.bigint ? new BigIntStats(stats) : stats;
327
+ path = normalizePath(path);
328
+ const { fs, path: resolved } = resolveMount((await exists(path)) ? await realpath(path) : path);
329
+ try {
330
+ const stats = await fs.stat(resolved, cred);
331
+ return options?.bigint ? new BigIntStats(stats) : stats;
332
+ }
333
+ catch (e) {
334
+ throw fixError(e, { [resolved]: path });
335
+ }
362
336
  }
363
337
  stat;
364
338
  export async function lstat(path, options) {
365
- const stats = await doOp('stat', false, path.toString(), cred);
366
- return options?.bigint ? new BigIntStats(stats) : stats;
339
+ path = normalizePath(path);
340
+ const { fs, path: resolved } = resolveMount(path);
341
+ try {
342
+ const stats = await fs.stat(resolved, cred);
343
+ return options?.bigint ? new BigIntStats(stats) : stats;
344
+ }
345
+ catch (e) {
346
+ throw fixError(e, { [resolved]: path });
347
+ }
367
348
  }
368
349
  lstat;
369
350
  // FILE-ONLY METHODS
@@ -387,19 +368,29 @@ truncate;
387
368
  * @param path
388
369
  */
389
370
  export async function unlink(path) {
390
- return doOp('unlink', false, path.toString(), cred);
371
+ path = normalizePath(path);
372
+ const { fs, path: resolved } = resolveMount(path);
373
+ try {
374
+ await fs.unlink(resolved, cred);
375
+ }
376
+ catch (e) {
377
+ throw fixError(e, { [resolved]: path });
378
+ }
391
379
  }
392
380
  unlink;
393
381
  /**
394
382
  * Opens a file. This helper handles the complexity of file flags.
395
383
  * @internal
396
384
  */
397
- async function _open(_path, _flag, _mode = 0o644, resolveSymlinks) {
398
- const path = normalizePath(_path), mode = normalizeMode(_mode, 0o644), flag = parseFlag(_flag);
385
+ async function _open(path, _flag, _mode = 0o644, resolveSymlinks) {
386
+ path = normalizePath(path);
387
+ const mode = normalizeMode(_mode, 0o644), flag = parseFlag(_flag);
388
+ path = resolveSymlinks && (await exists(path)) ? await realpath(path) : path;
389
+ const { fs, path: resolved } = resolveMount(path);
399
390
  try {
400
391
  switch (pathExistsAction(flag)) {
401
392
  case ActionType.THROW:
402
- throw ApiError.With('EEXIST', path, '_open');
393
+ throw ErrnoError.With('EEXIST', path, '_open');
403
394
  case ActionType.TRUNCATE:
404
395
  /*
405
396
  In a previous implementation, we deleted the file and
@@ -407,33 +398,30 @@ async function _open(_path, _flag, _mode = 0o644, resolveSymlinks) {
407
398
  asynchronous request was trying to read the file, as the file
408
399
  would not exist for a small period of time.
409
400
  */
410
- const file = await doOp('openFile', resolveSymlinks, path, flag, cred);
411
- if (!file) {
412
- throw new ApiError(ErrorCode.EIO, 'Impossible code path reached');
413
- }
401
+ const file = await fs.openFile(resolved, flag, cred);
414
402
  await file.truncate(0);
415
403
  await file.sync();
416
- return file;
404
+ return new FileHandle(file);
417
405
  case ActionType.NOP:
418
406
  // Must await so thrown errors are caught by the catch below
419
- return await doOp('openFile', resolveSymlinks, path, flag, cred);
407
+ return new FileHandle(await fs.openFile(resolved, flag, cred));
420
408
  default:
421
- throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag');
409
+ throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
422
410
  }
423
411
  }
424
412
  catch (e) {
425
413
  switch (pathNotExistsAction(flag)) {
426
414
  case ActionType.CREATE:
427
415
  // Ensure parent exists.
428
- const parentStats = await doOp('stat', resolveSymlinks, dirname(path), cred);
416
+ const parentStats = await fs.stat(dirname(resolved), cred);
429
417
  if (parentStats && !parentStats.isDirectory()) {
430
- throw ApiError.With('ENOTDIR', dirname(path), '_open');
418
+ throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
431
419
  }
432
- return await doOp('createFile', resolveSymlinks, path, flag, mode, cred);
420
+ return new FileHandle(await fs.createFile(resolved, flag, mode, cred));
433
421
  case ActionType.THROW:
434
- throw ApiError.With('ENOENT', path, '_open');
422
+ throw ErrnoError.With('ENOENT', path, '_open');
435
423
  default:
436
- throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag');
424
+ throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
437
425
  }
438
426
  }
439
427
  }
@@ -444,36 +432,18 @@ async function _open(_path, _flag, _mode = 0o644, resolveSymlinks) {
444
432
  * @param mode Mode to use to open the file. Can be ignored if the filesystem doesn't support permissions.
445
433
  */
446
434
  export async function open(path, flag = 'r', mode = 0o644) {
447
- const file = await _open(path, flag, mode, true);
448
- return new FileHandle(file2fd(file));
435
+ return await _open(path, flag, mode, true);
449
436
  }
450
437
  open;
451
- /**
452
- * Asynchronously reads the entire contents of a file.
453
- */
454
- async function _readFile(fname, flag, resolveSymlinks) {
455
- const file = await _open(normalizePath(fname), flag, 0o644, resolveSymlinks);
438
+ export async function readFile(path, _options) {
439
+ const options = normalizeOptions(_options, null, 'r', 0o644);
440
+ const handle = typeof path == 'object' && 'fd' in path ? path : await open(path, options.flag, options.mode);
456
441
  try {
457
- const stat = await file.stat();
458
- const data = new Uint8Array(stat.size);
459
- await file.read(data, 0, stat.size, 0);
460
- await file.close();
461
- return data;
462
- }
463
- catch (e) {
464
- await file.close();
465
- throw e;
442
+ return await handle.readFile(options);
466
443
  }
467
- }
468
- export async function readFile(path, _options) {
469
- const options = normalizeOptions(_options, null, 'r', 0);
470
- const flag = parseFlag(options.flag);
471
- if (!isReadable(flag)) {
472
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for reading.');
444
+ finally {
445
+ await handle.close();
473
446
  }
474
- path = path instanceof FileHandle ? path.file.path : path.toString();
475
- const data = Buffer.from(await _readFile(path, options.flag, true));
476
- return options.encoding ? data.toString(options.encoding) : data;
477
447
  }
478
448
  readFile;
479
449
  /**
@@ -493,7 +463,7 @@ export async function writeFile(path, data, _options) {
493
463
  try {
494
464
  const _data = typeof data == 'string' ? data : data;
495
465
  if (typeof _data != 'string' && !(_data instanceof Uint8Array)) {
496
- throw new ApiError(ErrorCode.EINVAL, 'Iterables and streams not supported', handle.file.path, 'writeFile');
466
+ throw new ErrnoError(Errno.EINVAL, 'Iterables and streams not supported', handle.file.path, 'writeFile');
497
467
  }
498
468
  await handle.writeFile(_data, options);
499
469
  }
@@ -502,19 +472,6 @@ export async function writeFile(path, data, _options) {
502
472
  }
503
473
  }
504
474
  writeFile;
505
- /**
506
- * Asynchronously append data to a file, creating the file if
507
- * it not yet exists.
508
- */
509
- async function _appendFile(path, data, flag, mode, resolveSymlinks) {
510
- const file = await _open(path, flag, mode, resolveSymlinks);
511
- try {
512
- await file.write(data, 0, data.length, null);
513
- }
514
- finally {
515
- await file.close();
516
- }
517
- }
518
475
  /**
519
476
  * Asynchronously append data to a file, creating the file if it not yet
520
477
  * exists.
@@ -529,13 +486,19 @@ export async function appendFile(path, data, _options) {
529
486
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
530
487
  const flag = parseFlag(options.flag);
531
488
  if (!isAppendable(flag)) {
532
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed to appendFile must allow for appending.');
489
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed to appendFile must allow for appending.');
533
490
  }
534
491
  if (typeof data != 'string' && !options.encoding) {
535
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
492
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
536
493
  }
537
494
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
538
- await _appendFile(path instanceof FileHandle ? path.file.path : path.toString(), encodedData, options.flag, options.mode, true);
495
+ const handle = typeof path == 'object' && 'fd' in path ? path : await open(path, options.flag, options.mode);
496
+ try {
497
+ await handle.appendFile(encodedData, options);
498
+ }
499
+ finally {
500
+ await handle.close();
501
+ }
539
502
  }
540
503
  appendFile;
541
504
  // DIRECTORY-ONLY METHODS
@@ -544,19 +507,41 @@ appendFile;
544
507
  * @param path
545
508
  */
546
509
  export async function rmdir(path) {
547
- return doOp('rmdir', true, path.toString(), cred);
510
+ path = normalizePath(path);
511
+ path = (await exists(path)) ? await realpath(path) : path;
512
+ const { fs, path: resolved } = resolveMount(path);
513
+ try {
514
+ await fs.rmdir(resolved, cred);
515
+ }
516
+ catch (e) {
517
+ throw fixError(e, { [resolved]: path });
518
+ }
548
519
  }
549
520
  rmdir;
550
521
  export async function mkdir(path, options) {
551
- await doOp('mkdir', true, path.toString(), normalizeMode(typeof options == 'object' ? options?.mode : options, 0o777), cred);
552
- return;
522
+ path = normalizePath(path);
523
+ path = (await exists(path)) ? await realpath(path) : path;
524
+ const { fs, path: resolved } = resolveMount(path);
525
+ try {
526
+ await fs.mkdir(resolved, normalizeMode(typeof options == 'object' ? options?.mode : options, 0o777), cred);
527
+ }
528
+ catch (e) {
529
+ throw fixError(e, { [resolved]: path });
530
+ }
553
531
  }
554
532
  mkdir;
555
533
  export async function readdir(path, options) {
556
534
  path = normalizePath(path);
557
- const entries = await doOp('readdir', true, path, cred);
558
- const points = [...mounts.keys()];
559
- for (const point of points) {
535
+ path = (await exists(path)) ? await realpath(path) : path;
536
+ const { fs, path: resolved } = resolveMount(path);
537
+ let entries;
538
+ try {
539
+ entries = await fs.readdir(resolved, cred);
540
+ }
541
+ catch (e) {
542
+ throw fixError(e, { [resolved]: path });
543
+ }
544
+ for (const point of mounts.keys()) {
560
545
  if (point.startsWith(path)) {
561
546
  const entry = point.slice(path.length);
562
547
  if (entry.includes('/') || entry.length == 0) {
@@ -580,8 +565,15 @@ readdir;
580
565
  * @param newpath
581
566
  */
582
567
  export async function link(existing, newpath) {
568
+ existing = normalizePath(existing);
583
569
  newpath = normalizePath(newpath);
584
- return doOp('link', false, existing.toString(), newpath, cred);
570
+ const { fs, path: resolved } = resolveMount(newpath);
571
+ try {
572
+ return await fs.link(existing, newpath, cred);
573
+ }
574
+ catch (e) {
575
+ throw fixError(e, { [resolved]: newpath });
576
+ }
585
577
  }
586
578
  link;
587
579
  /**
@@ -592,20 +584,26 @@ link;
592
584
  */
593
585
  export async function symlink(target, path, type = 'file') {
594
586
  if (!['file', 'dir', 'junction'].includes(type)) {
595
- throw new ApiError(ErrorCode.EINVAL, 'Invalid symlink type: ' + type);
587
+ throw new ErrnoError(Errno.EINVAL, 'Invalid symlink type: ' + type);
596
588
  }
597
589
  if (await exists(path)) {
598
- throw ApiError.With('EEXIST', path.toString(), 'symlink');
590
+ throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
599
591
  }
600
592
  await writeFile(path, target.toString());
601
- const file = await _open(path, 'r+', 0o644, false);
602
- await file._setType(FileType.SYMLINK);
593
+ const handle = await _open(path, 'r+', 0o644, false);
594
+ await handle.file._setType(FileType.SYMLINK);
603
595
  }
604
596
  symlink;
605
597
  export async function readlink(path, options) {
606
- const value = Buffer.from(await _readFile(path.toString(), 'r', false));
607
- const encoding = typeof options == 'object' ? options?.encoding : options;
608
- return encoding == 'buffer' ? value : value.toString(encoding);
598
+ const handle = await _open(normalizePath(path), 'r', 0o644, false);
599
+ try {
600
+ const value = await handle.readFile();
601
+ const encoding = typeof options == 'object' ? options?.encoding : options;
602
+ return encoding == 'buffer' ? value : value.toString(encoding);
603
+ }
604
+ finally {
605
+ await handle.close();
606
+ }
609
607
  }
610
608
  readlink;
611
609
  // PROPERTY OPERATIONS
@@ -632,12 +630,12 @@ chown;
632
630
  * @param gid
633
631
  */
634
632
  export async function lchown(path, uid, gid) {
635
- const file = await _open(path, 'r+', 0o644, false);
633
+ const handle = await _open(path, 'r+', 0o644, false);
636
634
  try {
637
- await file.chown(uid, gid);
635
+ await handle.chown(uid, gid);
638
636
  }
639
637
  finally {
640
- await file.close();
638
+ await handle.close();
641
639
  }
642
640
  }
643
641
  lchown;
@@ -662,12 +660,12 @@ chmod;
662
660
  * @param mode
663
661
  */
664
662
  export async function lchmod(path, mode) {
665
- const file = await _open(path, 'r+', 0o644, false);
663
+ const handle = await _open(path, 'r+', 0o644, false);
666
664
  try {
667
- await new FileHandle(file2fd(file)).chmod(mode);
665
+ await handle.chmod(mode);
668
666
  }
669
667
  finally {
670
- await file.close();
668
+ await handle.close();
671
669
  }
672
670
  }
673
671
  lchmod;
@@ -694,12 +692,12 @@ utimes;
694
692
  * @param mtime
695
693
  */
696
694
  export async function lutimes(path, atime, mtime) {
697
- const file = await _open(path, 'r+', 0o644, false);
695
+ const handle = await _open(path, 'r+', 0o644, false);
698
696
  try {
699
- await file.utimes(new Date(atime), new Date(mtime));
697
+ await handle.utimes(new Date(atime), new Date(mtime));
700
698
  }
701
699
  finally {
702
- await file.close();
700
+ await handle.close();
703
701
  }
704
702
  }
705
703
  lutimes;
@@ -721,7 +719,7 @@ export async function realpath(path, options) {
721
719
  }
722
720
  realpath;
723
721
  export function watch(filename, options = {}) {
724
- throw ApiError.With('ENOSYS', filename.toString(), 'watch');
722
+ throw ErrnoError.With('ENOSYS', filename.toString(), 'watch');
725
723
  }
726
724
  watch;
727
725
  /**
@@ -732,7 +730,7 @@ watch;
732
730
  export async function access(path, mode = constants.F_OK) {
733
731
  const stats = await stat(path);
734
732
  if (!stats.hasAccess(mode, cred)) {
735
- throw new ApiError(ErrorCode.EACCES);
733
+ throw new ErrnoError(Errno.EACCES);
736
734
  }
737
735
  }
738
736
  access;
@@ -761,7 +759,7 @@ export async function rm(path, options) {
761
759
  case constants.S_IFIFO:
762
760
  case constants.S_IFSOCK:
763
761
  default:
764
- throw new ApiError(ErrorCode.EPERM, 'File type not supported', path, 'rm');
762
+ throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
765
763
  }
766
764
  }
767
765
  rm;
@@ -784,7 +782,7 @@ export async function copyFile(src, dest, mode) {
784
782
  src = normalizePath(src);
785
783
  dest = normalizePath(dest);
786
784
  if (mode && mode & constants.COPYFILE_EXCL && (await exists(dest))) {
787
- throw new ApiError(ErrorCode.EEXIST, 'Destination file already exists.', dest, 'copyFile');
785
+ throw new ErrnoError(Errno.EEXIST, 'Destination file already exists.', dest, 'copyFile');
788
786
  }
789
787
  await writeFile(dest, await readFile(src));
790
788
  }
@@ -817,12 +815,12 @@ export async function cp(source, destination, opts) {
817
815
  destination = normalizePath(destination);
818
816
  const srcStats = await lstat(source); // Use lstat to follow symlinks if not dereferencing
819
817
  if (opts?.errorOnExist && (await exists(destination))) {
820
- throw new ApiError(ErrorCode.EEXIST, 'Destination file or directory already exists.', destination, 'cp');
818
+ throw new ErrnoError(Errno.EEXIST, 'Destination file or directory already exists.', destination, 'cp');
821
819
  }
822
820
  switch (srcStats.mode & constants.S_IFMT) {
823
821
  case constants.S_IFDIR:
824
822
  if (!opts?.recursive) {
825
- throw new ApiError(ErrorCode.EISDIR, source + ' is a directory (not copied)', source, 'cp');
823
+ throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp');
826
824
  }
827
825
  await mkdir(destination, { recursive: true }); // Ensure the destination directory exists
828
826
  for (const dirent of await readdir(source, { withFileTypes: true })) {
@@ -841,7 +839,7 @@ export async function cp(source, destination, opts) {
841
839
  case constants.S_IFIFO:
842
840
  case constants.S_IFSOCK:
843
841
  default:
844
- throw new ApiError(ErrorCode.EPERM, 'File type not supported', source, 'rm');
842
+ throw new ErrnoError(Errno.EPERM, 'File type not supported', source, 'rm');
845
843
  }
846
844
  // Optionally preserve timestamps
847
845
  if (opts?.preserveTimestamps) {
@@ -850,5 +848,5 @@ export async function cp(source, destination, opts) {
850
848
  }
851
849
  cp;
852
850
  export async function statfs(path, opts) {
853
- throw ApiError.With('ENOSYS', path.toString(), 'statfs');
851
+ throw ErrnoError.With('ENOSYS', path.toString(), 'statfs');
854
852
  }
@@ -1,14 +1,13 @@
1
1
  import { Cred } from '../cred.js';
2
2
  import type { File } from '../file.js';
3
3
  import { FileSystem } from '../filesystem.js';
4
+ import { type AbsolutePath } from './path.js';
4
5
  export declare let cred: Cred;
5
6
  export declare function setCred(val: Cred): void;
6
7
  export declare const fdMap: Map<number, File>;
7
8
  export declare function file2fd(file: File): number;
8
9
  export declare function fd2file(fd: number): File;
9
- export interface MountMapping {
10
- [point: string]: FileSystem;
11
- }
10
+ export type MountObject = Record<AbsolutePath, FileSystem>;
12
11
  /**
13
12
  * The map of mount points
14
13
  * @internal
@@ -33,10 +32,6 @@ export declare function resolveMount(path: string): {
33
32
  /**
34
33
  * Reverse maps the paths in text from the mounted FileSystem to the global path
35
34
  */
36
- export declare function fixPaths(text: string, paths: {
37
- [from: string]: string;
38
- }): string;
39
- export declare function fixError<E extends Error>(e: E, paths: {
40
- [from: string]: string;
41
- }): E;
42
- export declare function mountMapping(mountMapping: MountMapping): void;
35
+ export declare function fixPaths(text: string, paths: Record<string, string>): string;
36
+ export declare function fixError<E extends Error>(e: E, paths: Record<string, string>): E;
37
+ export declare function mountObject(mounts: MountObject): void;
@@ -1,5 +1,5 @@
1
1
  // Utilities and shared data
2
- import { ApiError, ErrorCode } from '../ApiError.js';
2
+ import { ErrnoError, Errno } from '../error.js';
3
3
  import { InMemory } from '../backends/InMemory.js';
4
4
  import { rootCred } from '../cred.js';
5
5
  import { normalizePath } from '../utils.js';
@@ -19,7 +19,7 @@ export function file2fd(file) {
19
19
  }
20
20
  export function fd2file(fd) {
21
21
  if (!fdMap.has(fd)) {
22
- throw new ApiError(ErrorCode.EBADF);
22
+ throw new ErrnoError(Errno.EBADF);
23
23
  }
24
24
  return fdMap.get(fd);
25
25
  }
@@ -41,7 +41,7 @@ export function mount(mountPoint, fs) {
41
41
  }
42
42
  mountPoint = resolve(mountPoint);
43
43
  if (mounts.has(mountPoint)) {
44
- throw new ApiError(ErrorCode.EINVAL, 'Mount point ' + mountPoint + ' is already in use.');
44
+ throw new ErrnoError(Errno.EINVAL, 'Mount point ' + mountPoint + ' is already in use.');
45
45
  }
46
46
  mounts.set(mountPoint, fs);
47
47
  }
@@ -54,7 +54,7 @@ export function umount(mountPoint) {
54
54
  }
55
55
  mountPoint = resolve(mountPoint);
56
56
  if (!mounts.has(mountPoint)) {
57
- throw new ApiError(ErrorCode.EINVAL, 'Mount point ' + mountPoint + ' is already unmounted.');
57
+ throw new ErrnoError(Errno.EINVAL, 'Mount point ' + mountPoint + ' is already unmounted.');
58
58
  }
59
59
  mounts.delete(mountPoint);
60
60
  }
@@ -74,7 +74,7 @@ export function resolveMount(path) {
74
74
  return { fs, path, mountPoint };
75
75
  }
76
76
  }
77
- throw new ApiError(ErrorCode.EIO, 'ZenFS not initialized with a file system');
77
+ throw new ErrnoError(Errno.EIO, 'ZenFS not initialized with a file system');
78
78
  }
79
79
  /**
80
80
  * Reverse maps the paths in text from the mounted FileSystem to the global path
@@ -92,11 +92,11 @@ export function fixError(e, paths) {
92
92
  e.message = fixPaths(e.message, paths);
93
93
  return e;
94
94
  }
95
- export function mountMapping(mountMapping) {
96
- if ('/' in mountMapping) {
95
+ export function mountObject(mounts) {
96
+ if ('/' in mounts) {
97
97
  umount('/');
98
98
  }
99
- for (const [point, fs] of Object.entries(mountMapping)) {
99
+ for (const [point, fs] of Object.entries(mounts)) {
100
100
  mount(point, fs);
101
101
  }
102
102
  }