@zenfs/core 0.9.6 → 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 (82) hide show
  1. package/dist/backends/AsyncStore.d.ts +3 -2
  2. package/dist/backends/AsyncStore.js +40 -33
  3. package/dist/backends/Fetch.d.ts +84 -0
  4. package/dist/backends/Fetch.js +171 -0
  5. package/dist/backends/InMemory.d.ts +1 -1
  6. package/dist/backends/Index.d.ts +7 -10
  7. package/dist/backends/Index.js +26 -24
  8. package/dist/backends/Locked.d.ts +11 -11
  9. package/dist/backends/Locked.js +50 -49
  10. package/dist/backends/Overlay.js +22 -22
  11. package/dist/backends/SyncStore.d.ts +6 -6
  12. package/dist/backends/SyncStore.js +30 -30
  13. package/dist/backends/backend.d.ts +5 -4
  14. package/dist/backends/backend.js +6 -6
  15. package/dist/backends/port/fs.d.ts +124 -0
  16. package/dist/backends/port/fs.js +241 -0
  17. package/dist/backends/port/rpc.d.ts +60 -0
  18. package/dist/backends/port/rpc.js +71 -0
  19. package/dist/backends/port/store.d.ts +30 -0
  20. package/dist/backends/port/store.js +142 -0
  21. package/dist/browser.min.js +4 -4
  22. package/dist/browser.min.js.map +4 -4
  23. package/dist/config.d.ts +9 -11
  24. package/dist/config.js +13 -13
  25. package/dist/emulation/async.d.ts +76 -77
  26. package/dist/emulation/async.js +45 -45
  27. package/dist/emulation/dir.js +8 -7
  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 +112 -113
  33. package/dist/emulation/promises.js +167 -173
  34. package/dist/emulation/shared.d.ts +6 -17
  35. package/dist/emulation/shared.js +9 -9
  36. package/dist/emulation/streams.js +3 -2
  37. package/dist/emulation/sync.d.ts +71 -64
  38. package/dist/emulation/sync.js +62 -63
  39. package/dist/{ApiError.d.ts → error.d.ts} +15 -15
  40. package/dist/error.js +292 -0
  41. package/dist/file.d.ts +6 -4
  42. package/dist/file.js +17 -9
  43. package/dist/filesystem.d.ts +1 -1
  44. package/dist/filesystem.js +18 -15
  45. package/dist/index.d.ts +4 -1
  46. package/dist/index.js +4 -1
  47. package/dist/mutex.js +4 -3
  48. package/dist/stats.d.ts +7 -7
  49. package/dist/stats.js +50 -10
  50. package/dist/utils.d.ts +10 -9
  51. package/dist/utils.js +15 -15
  52. package/package.json +5 -5
  53. package/readme.md +19 -11
  54. package/src/backends/AsyncStore.ts +42 -36
  55. package/src/backends/Fetch.ts +230 -0
  56. package/src/backends/Index.ts +33 -29
  57. package/src/backends/Locked.ts +50 -49
  58. package/src/backends/Overlay.ts +24 -24
  59. package/src/backends/SyncStore.ts +34 -34
  60. package/src/backends/backend.ts +13 -11
  61. package/src/backends/port/fs.ts +308 -0
  62. package/src/backends/port/readme.md +59 -0
  63. package/src/backends/port/rpc.ts +144 -0
  64. package/src/backends/port/store.ts +187 -0
  65. package/src/config.ts +25 -29
  66. package/src/emulation/async.ts +191 -199
  67. package/src/emulation/dir.ts +8 -8
  68. package/src/emulation/index.ts +1 -1
  69. package/src/emulation/path.ts +25 -49
  70. package/src/emulation/promises.ts +286 -287
  71. package/src/emulation/shared.ts +14 -23
  72. package/src/emulation/streams.ts +9 -8
  73. package/src/emulation/sync.ts +182 -182
  74. package/src/{ApiError.ts → error.ts} +91 -89
  75. package/src/file.ts +23 -13
  76. package/src/filesystem.ts +26 -22
  77. package/src/index.ts +4 -1
  78. package/src/mutex.ts +6 -4
  79. package/src/stats.ts +32 -23
  80. package/src/utils.ts +23 -24
  81. package/tsconfig.json +4 -3
  82. package/dist/ApiError.js +0 -292
@@ -1,27 +1,19 @@
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';
6
6
  import * as constants from './constants.js';
7
7
  import { Dir, Dirent } from './dir.js';
8
8
  import { dirname, join, parse } from './path.js';
9
- import { cred, fd2file, fdMap, fixError, getFdForFile, mounts, resolveMount } from './shared.js';
9
+ import { cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
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
  }
@@ -57,8 +49,9 @@ export class FileHandle {
57
49
  * @param len If not specified, defaults to `0`.
58
50
  */
59
51
  truncate(len) {
52
+ len || (len = 0);
60
53
  if (len < 0) {
61
- throw new ApiError(ErrorCode.EINVAL);
54
+ throw new ErrnoError(Errno.EINVAL);
62
55
  }
63
56
  return this.file.truncate(len);
64
57
  }
@@ -80,14 +73,14 @@ export class FileHandle {
80
73
  * If `mode` is a string, it is parsed as an octal integer.
81
74
  * If `flag` is not supplied, the default of `'a'` is used.
82
75
  */
83
- async appendFile(data, _options) {
76
+ async appendFile(data, _options = {}) {
84
77
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
85
78
  const flag = parseFlag(options.flag);
86
79
  if (!isAppendable(flag)) {
87
- 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.');
88
81
  }
89
82
  if (typeof data != 'string' && !options.encoding) {
90
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
83
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
91
84
  }
92
85
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
93
86
  await this.file.write(encodedData, 0, encodedData.length, null);
@@ -110,7 +103,7 @@ export class FileHandle {
110
103
  const options = normalizeOptions(_options, null, 'r', 0o444);
111
104
  const flag = parseFlag(options.flag);
112
105
  if (!isReadable(flag)) {
113
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for reading.');
106
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed must allow for reading.');
114
107
  }
115
108
  const { size } = await this.stat();
116
109
  const data = new Uint8Array(size);
@@ -129,14 +122,14 @@ export class FileHandle {
129
122
  * @since v17.0.0
130
123
  * @experimental
131
124
  */
132
- readableWebStream(options) {
125
+ readableWebStream(options = {}) {
133
126
  // Note: using an arrow function to preserve `this`
134
127
  const start = async ({ close, enqueue, error }) => {
135
128
  try {
136
129
  const chunkSize = 64 * 1024, maxChunks = 1e7;
137
- let i = 0, position = 0, result;
138
- while (result.bytesRead > 0) {
139
- result = await this.read(new Uint8Array(chunkSize), 0, chunkSize, position);
130
+ let i = 0, position = 0, bytesRead = NaN;
131
+ while (bytesRead > 0) {
132
+ const result = await this.read(new Uint8Array(chunkSize), 0, chunkSize, position);
140
133
  if (!result.bytesRead) {
141
134
  close();
142
135
  return;
@@ -144,8 +137,9 @@ export class FileHandle {
144
137
  enqueue(result.buffer.slice(0, result.bytesRead));
145
138
  position += result.bytesRead;
146
139
  if (++i >= maxChunks) {
147
- 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');
148
141
  }
142
+ bytesRead = result.bytesRead;
149
143
  }
150
144
  }
151
145
  catch (e) {
@@ -155,7 +149,7 @@ export class FileHandle {
155
149
  return new globalThis.ReadableStream({ start, type: options.type });
156
150
  }
157
151
  readLines(options) {
158
- throw ApiError.With('ENOSYS', this.file.path, 'FileHandle.readLines');
152
+ throw ErrnoError.With('ENOSYS', this.file.path, 'FileHandle.readLines');
159
153
  }
160
154
  [Symbol.asyncDispose]() {
161
155
  return this.close();
@@ -165,7 +159,7 @@ export class FileHandle {
165
159
  return opts?.bigint ? new BigIntStats(stats) : stats;
166
160
  }
167
161
  async write(data, posOrOff, lenOrEnc, position) {
168
- let buffer, offset = 0, length;
162
+ let buffer, offset, length;
169
163
  if (typeof data === 'string') {
170
164
  // Signature 1: (fd, string, [position?, [encoding?]])
171
165
  position = typeof posOrOff === 'number' ? posOrOff : null;
@@ -176,7 +170,7 @@ export class FileHandle {
176
170
  }
177
171
  else {
178
172
  // Signature 2: (fd, buffer, offset, length, position?)
179
- buffer = data;
173
+ buffer = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
180
174
  offset = posOrOff;
181
175
  length = lenOrEnc;
182
176
  position = typeof position === 'number' ? position : null;
@@ -196,14 +190,14 @@ export class FileHandle {
196
190
  * If `mode` is a string, it is parsed as an octal integer.
197
191
  * If `flag` is not supplied, the default of `'w'` is used.
198
192
  */
199
- async writeFile(data, _options) {
193
+ async writeFile(data, _options = {}) {
200
194
  const options = normalizeOptions(_options, 'utf8', 'w', 0o644);
201
195
  const flag = parseFlag(options.flag);
202
196
  if (!isWriteable(flag)) {
203
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for writing.');
197
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed must allow for writing.');
204
198
  }
205
199
  if (typeof data != 'string' && !options.encoding) {
206
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
200
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
207
201
  }
208
202
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
209
203
  await this.file.write(encodedData, 0, encodedData.length, 0);
@@ -248,9 +242,9 @@ export class FileHandle {
248
242
  * @returns A `ReadStream` object.
249
243
  */
250
244
  createReadStream(options) {
251
- const streamOptions = {
245
+ const stream = new ReadStream({
252
246
  highWaterMark: options?.highWaterMark || 64 * 1024,
253
- encoding: options?.encoding,
247
+ encoding: options.encoding,
254
248
  read: async (size) => {
255
249
  try {
256
250
  const result = await this.read(new Uint8Array(size), 0, size, this.file.position);
@@ -261,8 +255,7 @@ export class FileHandle {
261
255
  stream.destroy(error);
262
256
  }
263
257
  },
264
- };
265
- const stream = new ReadStream(streamOptions);
258
+ });
266
259
  stream.path = this.file.path;
267
260
  return stream;
268
261
  }
@@ -291,31 +284,6 @@ export class FileHandle {
291
284
  return stream;
292
285
  }
293
286
  }
294
- /**
295
- * Utility for FS ops. It handles
296
- * - path normalization (for the first parameter to the FS op)
297
- * - path translation for errors
298
- * - FS/mount point resolution
299
- *
300
- * It can't be used for functions which may operate on multiple mounted FSs or paths (e.g. `rename`)
301
- * @param name the function name
302
- * @param resolveSymlinks whether to resolve symlinks
303
- * @param args the rest of the parameters are passed to the FS function. Note that the first parameter is required to be a path
304
- * @returns
305
- */
306
- async function doOp(...[name, resolveSymlinks, rawPath, ...args]) {
307
- rawPath = normalizePath(rawPath);
308
- const _path = resolveSymlinks && (await exists(rawPath)) ? await realpath(rawPath) : rawPath;
309
- const { fs, path } = resolveMount(_path);
310
- try {
311
- // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
312
- return fs[name](path, ...args);
313
- }
314
- catch (e) {
315
- throw fixError(e, { [path]: rawPath });
316
- }
317
- }
318
- // fs.promises
319
287
  /**
320
288
  * Renames a file
321
289
  * @param oldPath
@@ -341,28 +309,42 @@ export async function rename(oldPath, newPath) {
341
309
  rename;
342
310
  /**
343
311
  * Test whether or not the given path exists by checking with the file system.
344
- * @param _path
312
+ * @param path
345
313
  */
346
- export async function exists(_path) {
314
+ export async function exists(path) {
347
315
  try {
348
- const { fs, path } = resolveMount(await realpath(_path));
349
- return await fs.exists(path, cred);
316
+ const { fs, path: resolved } = resolveMount(await realpath(path));
317
+ return await fs.exists(resolved, cred);
350
318
  }
351
319
  catch (e) {
352
- if (e.errno == ErrorCode.ENOENT) {
320
+ if (e instanceof ErrnoError && e.code == 'ENOENT') {
353
321
  return false;
354
322
  }
355
323
  throw e;
356
324
  }
357
325
  }
358
326
  export async function stat(path, options) {
359
- const stats = await doOp('stat', true, path, cred);
360
- 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
+ }
361
336
  }
362
337
  stat;
363
338
  export async function lstat(path, options) {
364
- const stats = await doOp('stat', false, path, cred);
365
- 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
+ }
366
348
  }
367
349
  lstat;
368
350
  // FILE-ONLY METHODS
@@ -386,19 +368,29 @@ truncate;
386
368
  * @param path
387
369
  */
388
370
  export async function unlink(path) {
389
- return doOp('unlink', false, path, 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
+ }
390
379
  }
391
380
  unlink;
392
381
  /**
393
382
  * Opens a file. This helper handles the complexity of file flags.
394
383
  * @internal
395
384
  */
396
- async function _open(_path, _flag, _mode = 0o644, resolveSymlinks) {
397
- 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);
398
390
  try {
399
391
  switch (pathExistsAction(flag)) {
400
392
  case ActionType.THROW:
401
- throw ApiError.With('EEXIST', path, '_open');
393
+ throw ErrnoError.With('EEXIST', path, '_open');
402
394
  case ActionType.TRUNCATE:
403
395
  /*
404
396
  In a previous implementation, we deleted the file and
@@ -406,33 +398,30 @@ async function _open(_path, _flag, _mode = 0o644, resolveSymlinks) {
406
398
  asynchronous request was trying to read the file, as the file
407
399
  would not exist for a small period of time.
408
400
  */
409
- const file = await doOp('openFile', resolveSymlinks, path, flag, cred);
410
- if (!file) {
411
- throw new ApiError(ErrorCode.EIO, 'Impossible code path reached');
412
- }
401
+ const file = await fs.openFile(resolved, flag, cred);
413
402
  await file.truncate(0);
414
403
  await file.sync();
415
- return file;
404
+ return new FileHandle(file);
416
405
  case ActionType.NOP:
417
406
  // Must await so thrown errors are caught by the catch below
418
- return await doOp('openFile', resolveSymlinks, path, flag, cred);
407
+ return new FileHandle(await fs.openFile(resolved, flag, cred));
419
408
  default:
420
- throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag');
409
+ throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
421
410
  }
422
411
  }
423
412
  catch (e) {
424
413
  switch (pathNotExistsAction(flag)) {
425
414
  case ActionType.CREATE:
426
415
  // Ensure parent exists.
427
- const parentStats = await doOp('stat', resolveSymlinks, dirname(path), cred);
416
+ const parentStats = await fs.stat(dirname(resolved), cred);
428
417
  if (parentStats && !parentStats.isDirectory()) {
429
- throw ApiError.With('ENOTDIR', dirname(path), '_open');
418
+ throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
430
419
  }
431
- return await doOp('createFile', resolveSymlinks, path, flag, mode, cred);
420
+ return new FileHandle(await fs.createFile(resolved, flag, mode, cred));
432
421
  case ActionType.THROW:
433
- throw ApiError.With('ENOENT', path, '_open');
422
+ throw ErrnoError.With('ENOENT', path, '_open');
434
423
  default:
435
- throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag');
424
+ throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
436
425
  }
437
426
  }
438
427
  }
@@ -442,102 +431,74 @@ async function _open(_path, _flag, _mode = 0o644, resolveSymlinks) {
442
431
  * @param flags Handles the complexity of the various file modes. See its API for more details.
443
432
  * @param mode Mode to use to open the file. Can be ignored if the filesystem doesn't support permissions.
444
433
  */
445
- export async function open(path, flag, mode = 0o644) {
446
- const file = await _open(path, flag, mode, true);
447
- return new FileHandle(getFdForFile(file));
434
+ export async function open(path, flag = 'r', mode = 0o644) {
435
+ return await _open(path, flag, mode, true);
448
436
  }
449
437
  open;
450
- /**
451
- * Opens a file without resolving symlinks
452
- * @internal
453
- */
454
- export async function lopen(path, flag, mode = 0o644) {
455
- const file = await _open(path, flag, mode, false);
456
- return new FileHandle(getFdForFile(file));
457
- }
458
- /**
459
- * Asynchronously reads the entire contents of a file.
460
- */
461
- async function _readFile(fname, flag, resolveSymlinks) {
462
- 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);
463
441
  try {
464
- const stat = await file.stat();
465
- const data = new Uint8Array(stat.size);
466
- await file.read(data, 0, stat.size, 0);
467
- await file.close();
468
- return data;
469
- }
470
- catch (e) {
471
- await file.close();
472
- throw e;
442
+ return await handle.readFile(options);
473
443
  }
474
- }
475
- export async function readFile(filename, _options) {
476
- const options = normalizeOptions(_options, null, 'r', 0);
477
- const flag = parseFlag(options.flag);
478
- if (!isReadable(flag)) {
479
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for reading.');
444
+ finally {
445
+ await handle.close();
480
446
  }
481
- const data = Buffer.from(await _readFile(filename, options.flag, true));
482
- return options.encoding ? data.toString(options.encoding) : data;
483
447
  }
484
448
  readFile;
485
449
  /**
486
450
  * Asynchronously writes data to a file, replacing the file if it already exists.
487
451
  *
488
452
  * The encoding option is ignored if data is a buffer.
489
- * @param filename
490
- * @param data
453
+ * @param path
454
+ * @param data Note:
491
455
  * @param _options
492
456
  * @option options encoding Defaults to `'utf8'`.
493
457
  * @option options mode Defaults to `0644`.
494
458
  * @option options flag Defaults to `'w'`.
495
459
  */
496
- export async function writeFile(filename, data, _options) {
460
+ export async function writeFile(path, data, _options) {
497
461
  const options = normalizeOptions(_options, 'utf8', 'w+', 0o644);
498
- const handle = await open(filename, options.flag, options.mode);
462
+ const handle = path instanceof FileHandle ? path : await open(path.toString(), options.flag, options.mode);
499
463
  try {
500
- await handle.writeFile(data, options);
464
+ const _data = typeof data == 'string' ? data : data;
465
+ if (typeof _data != 'string' && !(_data instanceof Uint8Array)) {
466
+ throw new ErrnoError(Errno.EINVAL, 'Iterables and streams not supported', handle.file.path, 'writeFile');
467
+ }
468
+ await handle.writeFile(_data, options);
501
469
  }
502
470
  finally {
503
471
  await handle.close();
504
472
  }
505
473
  }
506
474
  writeFile;
507
- /**
508
- * Asynchronously append data to a file, creating the file if
509
- * it not yet exists.
510
- */
511
- async function _appendFile(fname, data, flag, mode, resolveSymlinks) {
512
- const file = await _open(fname, flag, mode, resolveSymlinks);
513
- try {
514
- await file.write(data, 0, data.length, null);
515
- }
516
- finally {
517
- await file.close();
518
- }
519
- }
520
475
  /**
521
476
  * Asynchronously append data to a file, creating the file if it not yet
522
477
  * exists.
523
- * @param filename
478
+ * @param path
524
479
  * @param data
525
480
  * @param options
526
481
  * @option options encoding Defaults to `'utf8'`.
527
482
  * @option options mode Defaults to `0644`.
528
483
  * @option options flag Defaults to `'a'`.
529
484
  */
530
- export async function appendFile(filename, data, _options) {
485
+ export async function appendFile(path, data, _options) {
531
486
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
532
487
  const flag = parseFlag(options.flag);
533
488
  if (!isAppendable(flag)) {
534
- 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.');
535
490
  }
536
491
  if (typeof data != 'string' && !options.encoding) {
537
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
492
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
493
+ }
494
+ const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
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();
538
501
  }
539
- const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
540
- await _appendFile(filename, encodedData, options.flag, options.mode, true);
541
502
  }
542
503
  appendFile;
543
504
  // DIRECTORY-ONLY METHODS
@@ -546,18 +507,41 @@ appendFile;
546
507
  * @param path
547
508
  */
548
509
  export async function rmdir(path) {
549
- return doOp('rmdir', true, path, 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
+ }
550
519
  }
551
520
  rmdir;
552
- export async function mkdir(path, mode) {
553
- await doOp('mkdir', true, path, normalizeMode(typeof mode == 'object' ? mode?.mode : mode, 0o777), cred);
521
+ export async function mkdir(path, options) {
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
+ }
554
531
  }
555
532
  mkdir;
556
533
  export async function readdir(path, options) {
557
534
  path = normalizePath(path);
558
- const entries = await doOp('readdir', true, path, cred);
559
- const points = [...mounts.keys()];
560
- 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()) {
561
545
  if (point.startsWith(path)) {
562
546
  const entry = point.slice(path.length);
563
547
  if (entry.includes('/') || entry.length == 0) {
@@ -581,8 +565,15 @@ readdir;
581
565
  * @param newpath
582
566
  */
583
567
  export async function link(existing, newpath) {
568
+ existing = normalizePath(existing);
584
569
  newpath = normalizePath(newpath);
585
- return doOp('link', false, existing, 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
+ }
586
577
  }
587
578
  link;
588
579
  /**
@@ -593,23 +584,26 @@ link;
593
584
  */
594
585
  export async function symlink(target, path, type = 'file') {
595
586
  if (!['file', 'dir', 'junction'].includes(type)) {
596
- throw new ApiError(ErrorCode.EINVAL, 'Invalid symlink type: ' + type);
587
+ throw new ErrnoError(Errno.EINVAL, 'Invalid symlink type: ' + type);
597
588
  }
598
589
  if (await exists(path)) {
599
- throw ApiError.With('EEXIST', path, 'symlink');
590
+ throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
600
591
  }
601
- await writeFile(path, target);
602
- const file = await _open(path, 'r+', 0o644, false);
603
- await file._setType(FileType.SYMLINK);
592
+ await writeFile(path, target.toString());
593
+ const handle = await _open(path, 'r+', 0o644, false);
594
+ await handle.file._setType(FileType.SYMLINK);
604
595
  }
605
596
  symlink;
606
597
  export async function readlink(path, options) {
607
- const value = Buffer.from(await _readFile(path, 'r', false));
608
- const encoding = typeof options == 'object' ? options.encoding : options;
609
- if (encoding == 'buffer') {
610
- return value;
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();
611
606
  }
612
- return value.toString(encoding);
613
607
  }
614
608
  readlink;
615
609
  // PROPERTY OPERATIONS
@@ -636,7 +630,7 @@ chown;
636
630
  * @param gid
637
631
  */
638
632
  export async function lchown(path, uid, gid) {
639
- const handle = await lopen(path, 'r+');
633
+ const handle = await _open(path, 'r+', 0o644, false);
640
634
  try {
641
635
  await handle.chown(uid, gid);
642
636
  }
@@ -666,7 +660,7 @@ chmod;
666
660
  * @param mode
667
661
  */
668
662
  export async function lchmod(path, mode) {
669
- const handle = await lopen(path, 'r+');
663
+ const handle = await _open(path, 'r+', 0o644, false);
670
664
  try {
671
665
  await handle.chmod(mode);
672
666
  }
@@ -698,9 +692,9 @@ utimes;
698
692
  * @param mtime
699
693
  */
700
694
  export async function lutimes(path, atime, mtime) {
701
- const handle = await lopen(path, 'r+');
695
+ const handle = await _open(path, 'r+', 0o644, false);
702
696
  try {
703
- await handle.utimes(atime, mtime);
697
+ await handle.utimes(new Date(atime), new Date(mtime));
704
698
  }
705
699
  finally {
706
700
  await handle.close();
@@ -724,8 +718,8 @@ export async function realpath(path, options) {
724
718
  }
725
719
  }
726
720
  realpath;
727
- export function watch(filename, options) {
728
- throw ApiError.With('ENOSYS', filename, 'watch');
721
+ export function watch(filename, options = {}) {
722
+ throw ErrnoError.With('ENOSYS', filename.toString(), 'watch');
729
723
  }
730
724
  watch;
731
725
  /**
@@ -736,7 +730,7 @@ watch;
736
730
  export async function access(path, mode = constants.F_OK) {
737
731
  const stats = await stat(path);
738
732
  if (!stats.hasAccess(mode, cred)) {
739
- throw new ApiError(ErrorCode.EACCES);
733
+ throw new ErrnoError(Errno.EACCES);
740
734
  }
741
735
  }
742
736
  access;
@@ -765,12 +759,12 @@ export async function rm(path, options) {
765
759
  case constants.S_IFIFO:
766
760
  case constants.S_IFSOCK:
767
761
  default:
768
- throw new ApiError(ErrorCode.EPERM, 'File type not supported', path, 'rm');
762
+ throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
769
763
  }
770
764
  }
771
765
  rm;
772
766
  export async function mkdtemp(prefix, options) {
773
- const encoding = typeof options === 'object' ? options.encoding : options || 'utf8';
767
+ const encoding = typeof options === 'object' ? options?.encoding : options || 'utf8';
774
768
  const fsName = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`;
775
769
  const resolvedPath = '/tmp/' + fsName;
776
770
  await mkdir(resolvedPath);
@@ -788,7 +782,7 @@ export async function copyFile(src, dest, mode) {
788
782
  src = normalizePath(src);
789
783
  dest = normalizePath(dest);
790
784
  if (mode && mode & constants.COPYFILE_EXCL && (await exists(dest))) {
791
- throw new ApiError(ErrorCode.EEXIST, 'Destination file already exists.', dest, 'copyFile');
785
+ throw new ErrnoError(Errno.EEXIST, 'Destination file already exists.', dest, 'copyFile');
792
786
  }
793
787
  await writeFile(dest, await readFile(src));
794
788
  }
@@ -821,12 +815,12 @@ export async function cp(source, destination, opts) {
821
815
  destination = normalizePath(destination);
822
816
  const srcStats = await lstat(source); // Use lstat to follow symlinks if not dereferencing
823
817
  if (opts?.errorOnExist && (await exists(destination))) {
824
- 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');
825
819
  }
826
820
  switch (srcStats.mode & constants.S_IFMT) {
827
821
  case constants.S_IFDIR:
828
822
  if (!opts?.recursive) {
829
- 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');
830
824
  }
831
825
  await mkdir(destination, { recursive: true }); // Ensure the destination directory exists
832
826
  for (const dirent of await readdir(source, { withFileTypes: true })) {
@@ -845,7 +839,7 @@ export async function cp(source, destination, opts) {
845
839
  case constants.S_IFIFO:
846
840
  case constants.S_IFSOCK:
847
841
  default:
848
- throw new ApiError(ErrorCode.EPERM, 'File type not supported', source, 'rm');
842
+ throw new ErrnoError(Errno.EPERM, 'File type not supported', source, 'rm');
849
843
  }
850
844
  // Optionally preserve timestamps
851
845
  if (opts?.preserveTimestamps) {
@@ -854,5 +848,5 @@ export async function cp(source, destination, opts) {
854
848
  }
855
849
  cp;
856
850
  export async function statfs(path, opts) {
857
- throw ApiError.With('ENOSYS', path, 'statfs');
851
+ throw ErrnoError.With('ENOSYS', path.toString(), 'statfs');
858
852
  }