@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,35 +1,39 @@
1
1
  import { Buffer } from 'buffer';
2
- import type * as Node from 'node:fs';
2
+ import type * as fs from 'node:fs';
3
3
  import type * as promises from 'node:fs/promises';
4
4
  import type { CreateReadStreamOptions, CreateWriteStreamOptions, FileChangeInfo, FileReadResult, FlagAndOpenMode } from 'node:fs/promises';
5
+ import type { Stream } from 'node:stream';
5
6
  import type { ReadableStream as TReadableStream } from 'node:stream/web';
6
7
  import type { Interface as ReadlineInterface } from 'readline';
7
- import { ApiError, ErrorCode } from '../ApiError.js';
8
+ import type { ReadableStreamController } from 'stream/web';
9
+ import { ErrnoError, Errno } from '../error.js';
8
10
  import { ActionType, File, isAppendable, isReadable, isWriteable, parseFlag, pathExistsAction, pathNotExistsAction } from '../file.js';
9
- import { FileContents, FileSystem } from '../filesystem.js';
11
+ import type { FileContents } from '../filesystem.js';
10
12
  import { BigIntStats, FileType, type BigIntStatsFs, type Stats, type StatsFs } from '../stats.js';
11
13
  import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
12
14
  import * as constants from './constants.js';
13
15
  import { Dir, Dirent } from './dir.js';
14
16
  import { dirname, join, parse } from './path.js';
15
- import type { PathLike } from './shared.js';
16
- import { cred, fd2file, fdMap, fixError, getFdForFile, mounts, resolveMount } from './shared.js';
17
+ import { cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
17
18
  import { ReadStream, WriteStream } from './streams.js';
18
19
  export * as constants from './constants.js';
19
20
 
20
21
  export class FileHandle implements promises.FileHandle {
21
- public constructor(
22
- /**
23
- * Gets the file descriptor for this file handle.
24
- */
25
- public readonly fd: number
26
- ) {}
22
+ /**
23
+ * The file descriptor for this file handle.
24
+ */
25
+ public readonly fd: number;
27
26
 
28
27
  /**
29
28
  * @internal
29
+ * The file for this file handle
30
30
  */
31
- public get file(): File {
32
- 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);
33
37
  }
34
38
 
35
39
  /**
@@ -43,10 +47,10 @@ export class FileHandle implements promises.FileHandle {
43
47
  * Asynchronous fchmod(2) - Change permissions of a file.
44
48
  * @param mode A file mode. If a string is passed, it is parsed as an octal integer.
45
49
  */
46
- public chmod(mode: Node.Mode): Promise<void> {
50
+ public chmod(mode: fs.Mode): Promise<void> {
47
51
  const numMode = normalizeMode(mode, -1);
48
52
  if (numMode < 0) {
49
- throw new ApiError(ErrorCode.EINVAL, 'Invalid mode.');
53
+ throw new ErrnoError(Errno.EINVAL, 'Invalid mode.');
50
54
  }
51
55
  return this.file.chmod(numMode);
52
56
  }
@@ -69,9 +73,10 @@ export class FileHandle implements promises.FileHandle {
69
73
  * Asynchronous ftruncate(2) - Truncate a file to a specified length.
70
74
  * @param len If not specified, defaults to `0`.
71
75
  */
72
- public truncate(len?: number): Promise<void> {
76
+ public truncate(len?: number | null): Promise<void> {
77
+ len ||= 0;
73
78
  if (len < 0) {
74
- throw new ApiError(ErrorCode.EINVAL);
79
+ throw new ErrnoError(Errno.EINVAL);
75
80
  }
76
81
  return this.file.truncate(len);
77
82
  }
@@ -95,16 +100,16 @@ export class FileHandle implements promises.FileHandle {
95
100
  * If `mode` is a string, it is parsed as an octal integer.
96
101
  * If `flag` is not supplied, the default of `'a'` is used.
97
102
  */
98
- public async appendFile(data: string | Uint8Array, _options?: (Node.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<void> {
103
+ public async appendFile(data: string | Uint8Array, _options: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding = {}): Promise<void> {
99
104
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
100
105
  const flag = parseFlag(options.flag);
101
106
  if (!isAppendable(flag)) {
102
- 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.');
103
108
  }
104
109
  if (typeof data != 'string' && !options.encoding) {
105
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
110
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
106
111
  }
107
- const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
112
+ const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : data;
108
113
  await this.file.write(encodedData, 0, encodedData.length, null);
109
114
  }
110
115
 
@@ -116,11 +121,11 @@ export class FileHandle implements promises.FileHandle {
116
121
  * @param length The number of bytes to read.
117
122
  * @param position The offset from the beginning of the file from which data should be read. If `null`, data will be read from the current position.
118
123
  */
119
- public read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<FileReadResult<TBuffer>> {
120
- if (isNaN(+position)) {
121
- position = this.file.position!;
124
+ public read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number | null): Promise<FileReadResult<TBuffer>> {
125
+ if (isNaN(+position!)) {
126
+ position = this.file.position;
122
127
  }
123
- return this.file.read(buffer, offset, length, position);
128
+ return this.file.read(buffer, offset, length, position!);
124
129
  }
125
130
 
126
131
  /**
@@ -129,13 +134,13 @@ export class FileHandle implements promises.FileHandle {
129
134
  * @param _options An object that may contain an optional flag.
130
135
  * If a flag is not provided, it defaults to `'r'`.
131
136
  */
132
- public async readFile(_options?: { flag?: Node.OpenMode }): Promise<Buffer>;
133
- public async readFile(_options: (Node.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<string>;
134
- public async readFile(_options?: (Node.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<string | Buffer> {
137
+ public async readFile(_options?: { flag?: fs.OpenMode }): Promise<Buffer>;
138
+ public async readFile(_options: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<string>;
139
+ public async readFile(_options?: (fs.ObjectEncodingOptions & FlagAndOpenMode) | BufferEncoding): Promise<string | Buffer> {
135
140
  const options = normalizeOptions(_options, null, 'r', 0o444);
136
141
  const flag = parseFlag(options.flag);
137
142
  if (!isReadable(flag)) {
138
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for reading.');
143
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed must allow for reading.');
139
144
  }
140
145
 
141
146
  const { size } = await this.stat();
@@ -156,18 +161,18 @@ export class FileHandle implements promises.FileHandle {
156
161
  * @since v17.0.0
157
162
  * @experimental
158
163
  */
159
- public readableWebStream(options?: promises.ReadableWebStreamOptions): TReadableStream<Uint8Array> {
164
+ public readableWebStream(options: promises.ReadableWebStreamOptions = {}): TReadableStream<Uint8Array> {
160
165
  // Note: using an arrow function to preserve `this`
161
- const start = async ({ close, enqueue, error }) => {
166
+ const start = async ({ close, enqueue, error }: ReadableStreamController<Uint8Array>) => {
162
167
  try {
163
168
  const chunkSize = 64 * 1024,
164
169
  maxChunks = 1e7;
165
170
  let i = 0,
166
171
  position = 0,
167
- result: FileReadResult<Uint8Array>;
172
+ bytesRead = NaN;
168
173
 
169
- while (result.bytesRead > 0) {
170
- result = await this.read(new Uint8Array(chunkSize), 0, chunkSize, position);
174
+ while (bytesRead > 0) {
175
+ const result = await this.read(new Uint8Array(chunkSize), 0, chunkSize, position);
171
176
  if (!result.bytesRead) {
172
177
  close();
173
178
  return;
@@ -175,19 +180,20 @@ export class FileHandle implements promises.FileHandle {
175
180
  enqueue(result.buffer.slice(0, result.bytesRead));
176
181
  position += result.bytesRead;
177
182
  if (++i >= maxChunks) {
178
- 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');
179
184
  }
185
+ bytesRead = result.bytesRead;
180
186
  }
181
187
  } catch (e) {
182
188
  error(e);
183
189
  }
184
190
  };
185
191
 
186
- return new globalThis.ReadableStream({ start, type: options.type });
192
+ return new (globalThis as any).ReadableStream({ start, type: options.type });
187
193
  }
188
194
 
189
195
  public readLines(options?: promises.CreateReadStreamOptions): ReadlineInterface {
190
- throw ApiError.With('ENOSYS', this.file.path, 'FileHandle.readLines');
196
+ throw ErrnoError.With('ENOSYS', this.file.path, 'FileHandle.readLines');
191
197
  }
192
198
 
193
199
  public [Symbol.asyncDispose](): Promise<void> {
@@ -197,40 +203,34 @@ export class FileHandle implements promises.FileHandle {
197
203
  /**
198
204
  * Asynchronous fstat(2) - Get file status.
199
205
  */
200
- public async stat(opts: Node.BigIntOptions): Promise<BigIntStats>;
201
- public async stat(opts?: Node.StatOptions & { bigint?: false }): Promise<Stats>;
202
- public async stat(opts?: Node.StatOptions): Promise<Stats | BigIntStats> {
206
+ public async stat(opts: fs.BigIntOptions): Promise<BigIntStats>;
207
+ public async stat(opts?: fs.StatOptions & { bigint?: false }): Promise<Stats>;
208
+ public async stat(opts?: fs.StatOptions): Promise<Stats | BigIntStats> {
203
209
  const stats = await this.file.stat();
204
210
  return opts?.bigint ? new BigIntStats(stats) : stats;
205
211
  }
206
212
 
207
- public async write(data: FileContents, posOrOff?: number, lenOrEnc?: BufferEncoding | number, position?: number): Promise<{ bytesWritten: number; buffer: FileContents }>;
208
-
209
- /**
210
- * Asynchronously writes `buffer` to the file.
211
- * The `FileHandle` must have been opened for writing.
212
- * @param buffer The buffer that the data will be written to.
213
- * @param offset The part of the buffer to be written. If not supplied, defaults to `0`.
214
- * @param length The number of bytes to write. If not supplied, defaults to `buffer.length - offset`.
215
- * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position.
216
- */
217
- public async write<TBuffer extends Uint8Array>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<{ bytesWritten: number; buffer: TBuffer }>;
218
-
219
213
  /**
220
214
  * Asynchronously writes `string` to the file.
221
215
  * The `FileHandle` must have been opened for writing.
222
216
  * It is unsafe to call `write()` multiple times on the same file without waiting for the `Promise`
223
217
  * to be resolved (or rejected). For this scenario, `fs.createWriteStream` is strongly recommended.
224
- * @param string A string to write.
225
- * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position.
226
- * @param encoding The expected string encoding.
227
218
  */
219
+ public async write(
220
+ data: FileContents,
221
+ posOrOff?: number | null,
222
+ lenOrEnc?: BufferEncoding | number,
223
+ position?: number | null
224
+ ): Promise<{ bytesWritten: number; buffer: FileContents }>;
225
+ public async write<TBuffer extends Uint8Array>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<{ bytesWritten: number; buffer: TBuffer }>;
228
226
  public async write(data: string, position?: number, encoding?: BufferEncoding): Promise<{ bytesWritten: number; buffer: string }>;
229
-
230
- public async write(data: FileContents, posOrOff?: number, lenOrEnc?: BufferEncoding | number, position?: number): Promise<{ bytesWritten: number; buffer: FileContents }> {
231
- let buffer: Uint8Array,
232
- offset: number = 0,
233
- length: number;
227
+ public async write(
228
+ data: FileContents,
229
+ posOrOff?: number,
230
+ lenOrEnc?: BufferEncoding | number,
231
+ position?: number | null
232
+ ): Promise<{ bytesWritten: number; buffer: FileContents }> {
233
+ let buffer: Uint8Array, offset: number | null | undefined, length: number;
234
234
  if (typeof data === 'string') {
235
235
  // Signature 1: (fd, string, [position?, [encoding?]])
236
236
  position = typeof posOrOff === 'number' ? posOrOff : null;
@@ -240,13 +240,12 @@ export class FileHandle implements promises.FileHandle {
240
240
  length = buffer.length;
241
241
  } else {
242
242
  // Signature 2: (fd, buffer, offset, length, position?)
243
- buffer = data;
243
+ buffer = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
244
244
  offset = posOrOff;
245
245
  length = lenOrEnc as number;
246
246
  position = typeof position === 'number' ? position : null;
247
247
  }
248
-
249
- position ??= this.file.position!;
248
+ position ??= this.file.position;
250
249
  const bytesWritten = await this.file.write(buffer, offset, length, position);
251
250
  return { buffer, bytesWritten };
252
251
  }
@@ -262,16 +261,16 @@ export class FileHandle implements promises.FileHandle {
262
261
  * If `mode` is a string, it is parsed as an octal integer.
263
262
  * If `flag` is not supplied, the default of `'w'` is used.
264
263
  */
265
- public async writeFile(data: string | Uint8Array, _options?: Node.WriteFileOptions): Promise<void> {
264
+ public async writeFile(data: string | Uint8Array, _options: fs.WriteFileOptions = {}): Promise<void> {
266
265
  const options = normalizeOptions(_options, 'utf8', 'w', 0o644);
267
266
  const flag = parseFlag(options.flag);
268
267
  if (!isWriteable(flag)) {
269
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for writing.');
268
+ throw new ErrnoError(Errno.EINVAL, 'Flag passed must allow for writing.');
270
269
  }
271
270
  if (typeof data != 'string' && !options.encoding) {
272
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
271
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
273
272
  }
274
- const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
273
+ const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : data;
275
274
  await this.file.write(encodedData, 0, encodedData.length, 0);
276
275
  }
277
276
 
@@ -289,11 +288,11 @@ export class FileHandle implements promises.FileHandle {
289
288
  * @param position The position in the file where to begin writing.
290
289
  * @returns The number of bytes written.
291
290
  */
292
- public async writev(buffers: Uint8Array[], position?: number): Promise<Node.WriteVResult> {
291
+ public async writev(buffers: Uint8Array[], position?: number): Promise<fs.WriteVResult> {
293
292
  let bytesWritten = 0;
294
293
 
295
294
  for (const buffer of buffers) {
296
- bytesWritten += (await this.write(buffer, 0, buffer.length, position + bytesWritten)).bytesWritten;
295
+ bytesWritten += (await this.write(buffer, 0, buffer.length, position! + bytesWritten)).bytesWritten;
297
296
  }
298
297
 
299
298
  return { bytesWritten, buffers };
@@ -305,11 +304,11 @@ export class FileHandle implements promises.FileHandle {
305
304
  * @param position The position in the file where to begin reading.
306
305
  * @returns The number of bytes read.
307
306
  */
308
- public async readv(buffers: NodeJS.ArrayBufferView[], position?: number): Promise<Node.ReadVResult> {
307
+ public async readv(buffers: NodeJS.ArrayBufferView[], position?: number): Promise<fs.ReadVResult> {
309
308
  let bytesRead = 0;
310
309
 
311
310
  for (const buffer of buffers) {
312
- bytesRead += (await this.read(buffer, 0, buffer.byteLength, position + bytesRead)).bytesRead;
311
+ bytesRead += (await this.read(buffer, 0, buffer.byteLength, position! + bytesRead)).bytesRead;
313
312
  }
314
313
 
315
314
  return { bytesRead, buffers };
@@ -322,9 +321,9 @@ export class FileHandle implements promises.FileHandle {
322
321
  * @returns A `ReadStream` object.
323
322
  */
324
323
  public createReadStream(options?: CreateReadStreamOptions): ReadStream {
325
- const streamOptions = {
324
+ const stream = new ReadStream({
326
325
  highWaterMark: options?.highWaterMark || 64 * 1024,
327
- encoding: options?.encoding,
326
+ encoding: options!.encoding!,
328
327
 
329
328
  read: async (size: number) => {
330
329
  try {
@@ -332,12 +331,11 @@ export class FileHandle implements promises.FileHandle {
332
331
  stream.push(!result.bytesRead ? null : result.buffer.slice(0, result.bytesRead)); // Push data or null for EOF
333
332
  this.file.position += result.bytesRead;
334
333
  } catch (error) {
335
- stream.destroy(error);
334
+ stream.destroy(error as Error);
336
335
  }
337
336
  },
338
- };
337
+ });
339
338
 
340
- const stream = new ReadStream(streamOptions);
341
339
  stream.path = this.file.path;
342
340
  return stream;
343
341
  }
@@ -358,7 +356,7 @@ export class FileHandle implements promises.FileHandle {
358
356
  const { bytesWritten } = await this.write(chunk, null, encoding);
359
357
  callback(bytesWritten == chunk.length ? null : new Error('Failed to write full chunk'));
360
358
  } catch (error) {
361
- callback(error);
359
+ callback(error as Error);
362
360
  }
363
361
  },
364
362
  };
@@ -369,44 +367,12 @@ export class FileHandle implements promises.FileHandle {
369
367
  }
370
368
  }
371
369
 
372
- type FileSystemMethod = {
373
- [K in keyof FileSystem]: FileSystem[K] extends (...args) => unknown
374
- ? (name: K, resolveSymlinks: boolean, ...args: Parameters<FileSystem[K]>) => ReturnType<FileSystem[K]>
375
- : never;
376
- }[keyof FileSystem]; // https://stackoverflow.com/a/76335220/17637456
377
-
378
- /**
379
- * Utility for FS ops. It handles
380
- * - path normalization (for the first parameter to the FS op)
381
- * - path translation for errors
382
- * - FS/mount point resolution
383
- *
384
- * It can't be used for functions which may operate on multiple mounted FSs or paths (e.g. `rename`)
385
- * @param name the function name
386
- * @param resolveSymlinks whether to resolve symlinks
387
- * @param args the rest of the parameters are passed to the FS function. Note that the first parameter is required to be a path
388
- * @returns
389
- */
390
- async function doOp<M extends FileSystemMethod, RT extends ReturnType<M> = ReturnType<M>>(...[name, resolveSymlinks, rawPath, ...args]: Parameters<M>): Promise<RT> {
391
- rawPath = normalizePath(rawPath);
392
- const _path = resolveSymlinks && (await exists(rawPath)) ? await realpath(rawPath) : rawPath;
393
- const { fs, path } = resolveMount(_path);
394
- try {
395
- // @ts-expect-error 2556 (since ...args is not correctly picked up as being a tuple)
396
- return fs[name](path, ...args) as Promise<RT>;
397
- } catch (e) {
398
- throw fixError(e, { [path]: rawPath });
399
- }
400
- }
401
-
402
- // fs.promises
403
-
404
370
  /**
405
371
  * Renames a file
406
372
  * @param oldPath
407
373
  * @param newPath
408
374
  */
409
- export async function rename(oldPath: PathLike, newPath: PathLike): Promise<void> {
375
+ export async function rename(oldPath: fs.PathLike, newPath: fs.PathLike): Promise<void> {
410
376
  oldPath = normalizePath(oldPath);
411
377
  newPath = normalizePath(newPath);
412
378
  const src = resolveMount(oldPath);
@@ -419,21 +385,21 @@ export async function rename(oldPath: PathLike, newPath: PathLike): Promise<void
419
385
  await writeFile(newPath, await readFile(oldPath));
420
386
  await unlink(oldPath);
421
387
  } catch (e) {
422
- throw fixError(e, { [src.path]: oldPath, [dst.path]: newPath });
388
+ throw fixError(e as Error, { [src.path]: oldPath, [dst.path]: newPath });
423
389
  }
424
390
  }
425
391
  rename satisfies typeof promises.rename;
426
392
 
427
393
  /**
428
394
  * Test whether or not the given path exists by checking with the file system.
429
- * @param _path
395
+ * @param path
430
396
  */
431
- export async function exists(_path: PathLike): Promise<boolean> {
397
+ export async function exists(path: fs.PathLike): Promise<boolean> {
432
398
  try {
433
- const { fs, path } = resolveMount(await realpath(_path));
434
- return await fs.exists(path, cred);
399
+ const { fs, path: resolved } = resolveMount(await realpath(path));
400
+ return await fs.exists(resolved, cred);
435
401
  } catch (e) {
436
- if ((e as ApiError).errno == ErrorCode.ENOENT) {
402
+ if (e instanceof ErrnoError && e.code == 'ENOENT') {
437
403
  return false;
438
404
  }
439
405
 
@@ -446,12 +412,18 @@ export async function exists(_path: PathLike): Promise<boolean> {
446
412
  * @param path
447
413
  * @returns Stats
448
414
  */
449
- export async function stat(path: PathLike, options: Node.BigIntOptions): Promise<BigIntStats>;
450
- export async function stat(path: PathLike, options?: { bigint?: false }): Promise<Stats>;
451
- export async function stat(path: PathLike, options?: Node.StatOptions): Promise<Stats | BigIntStats>;
452
- export async function stat(path: PathLike, options?: Node.StatOptions): Promise<Stats | BigIntStats> {
453
- const stats: Stats = await doOp('stat', true, path, cred);
454
- return options?.bigint ? new BigIntStats(stats) : stats;
415
+ export async function stat(path: fs.PathLike, options: fs.BigIntOptions): Promise<BigIntStats>;
416
+ export async function stat(path: fs.PathLike, options?: { bigint?: false }): Promise<Stats>;
417
+ export async function stat(path: fs.PathLike, options?: fs.StatOptions): Promise<Stats | BigIntStats>;
418
+ export async function stat(path: fs.PathLike, options?: fs.StatOptions): Promise<Stats | BigIntStats> {
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
+ }
455
427
  }
456
428
  stat satisfies typeof promises.stat;
457
429
 
@@ -462,11 +434,17 @@ stat satisfies typeof promises.stat;
462
434
  * @param path
463
435
  * @return
464
436
  */
465
- export async function lstat(path: PathLike, options?: { bigint?: false }): Promise<Stats>;
466
- export async function lstat(path: PathLike, options: { bigint: true }): Promise<BigIntStats>;
467
- export async function lstat(path: PathLike, options?: Node.StatOptions): Promise<Stats | BigIntStats> {
468
- const stats: Stats = await doOp('stat', false, path, cred);
469
- return options?.bigint ? new BigIntStats(stats) : stats;
437
+ export async function lstat(path: fs.PathLike, options?: { bigint?: boolean }): Promise<Stats>;
438
+ export async function lstat(path: fs.PathLike, options: { bigint: true }): Promise<BigIntStats>;
439
+ export async function lstat(path: fs.PathLike, options?: fs.StatOptions): Promise<Stats | BigIntStats> {
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
+ }
470
448
  }
471
449
  lstat satisfies typeof promises.lstat;
472
450
 
@@ -477,7 +455,7 @@ lstat satisfies typeof promises.lstat;
477
455
  * @param path
478
456
  * @param len
479
457
  */
480
- export async function truncate(path: PathLike, len: number = 0): Promise<void> {
458
+ export async function truncate(path: fs.PathLike, len: number = 0): Promise<void> {
481
459
  const handle = await open(path, 'r+');
482
460
  try {
483
461
  await handle.truncate(len);
@@ -491,8 +469,14 @@ truncate satisfies typeof promises.truncate;
491
469
  * `unlink`.
492
470
  * @param path
493
471
  */
494
- export async function unlink(path: PathLike): Promise<void> {
495
- return doOp('unlink', false, path, cred);
472
+ export async function unlink(path: fs.PathLike): Promise<void> {
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
+ }
496
480
  }
497
481
  unlink satisfies typeof promises.unlink;
498
482
 
@@ -500,15 +484,18 @@ unlink satisfies typeof promises.unlink;
500
484
  * Opens a file. This helper handles the complexity of file flags.
501
485
  * @internal
502
486
  */
503
- async function _open(_path: PathLike, _flag: string, _mode: Node.Mode = 0o644, resolveSymlinks: boolean): Promise<File> {
504
- const path = normalizePath(_path),
505
- 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),
506
490
  flag = parseFlag(_flag);
507
491
 
492
+ path = resolveSymlinks && (await exists(path)) ? await realpath(path) : path;
493
+ const { fs, path: resolved } = resolveMount(path);
494
+
508
495
  try {
509
496
  switch (pathExistsAction(flag)) {
510
497
  case ActionType.THROW:
511
- throw ApiError.With('EEXIST', path, '_open');
498
+ throw ErrnoError.With('EEXIST', path, '_open');
512
499
  case ActionType.TRUNCATE:
513
500
  /*
514
501
  In a previous implementation, we deleted the file and
@@ -516,32 +503,29 @@ async function _open(_path: PathLike, _flag: string, _mode: Node.Mode = 0o644, r
516
503
  asynchronous request was trying to read the file, as the file
517
504
  would not exist for a small period of time.
518
505
  */
519
- const file: File = await doOp('openFile', resolveSymlinks, path, flag, cred);
520
- if (!file) {
521
- throw new ApiError(ErrorCode.EIO, 'Impossible code path reached');
522
- }
506
+ const file: File = await fs.openFile(resolved, flag, cred);
523
507
  await file.truncate(0);
524
508
  await file.sync();
525
- return file;
509
+ return new FileHandle(file);
526
510
  case ActionType.NOP:
527
511
  // Must await so thrown errors are caught by the catch below
528
- return await doOp('openFile', resolveSymlinks, path, flag, cred);
512
+ return new FileHandle(await fs.openFile(resolved, flag, cred));
529
513
  default:
530
- throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag');
514
+ throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
531
515
  }
532
516
  } catch (e) {
533
517
  switch (pathNotExistsAction(flag)) {
534
518
  case ActionType.CREATE:
535
519
  // Ensure parent exists.
536
- const parentStats: Stats = await doOp('stat', resolveSymlinks, dirname(path), cred);
520
+ const parentStats: Stats = await fs.stat(dirname(resolved), cred);
537
521
  if (parentStats && !parentStats.isDirectory()) {
538
- throw ApiError.With('ENOTDIR', dirname(path), '_open');
522
+ throw ErrnoError.With('ENOTDIR', dirname(path), '_open');
539
523
  }
540
- return await doOp('createFile', resolveSymlinks, path, flag, mode, cred);
524
+ return new FileHandle(await fs.createFile(resolved, flag, mode, cred));
541
525
  case ActionType.THROW:
542
- throw ApiError.With('ENOENT', path, '_open');
526
+ throw ErrnoError.With('ENOENT', path, '_open');
543
527
  default:
544
- throw new ApiError(ErrorCode.EINVAL, 'Invalid file flag');
528
+ throw new ErrnoError(Errno.EINVAL, 'Invalid file flag');
545
529
  }
546
530
  }
547
531
  }
@@ -552,39 +536,11 @@ async function _open(_path: PathLike, _flag: string, _mode: Node.Mode = 0o644, r
552
536
  * @param flags Handles the complexity of the various file modes. See its API for more details.
553
537
  * @param mode Mode to use to open the file. Can be ignored if the filesystem doesn't support permissions.
554
538
  */
555
- export async function open(path: PathLike, flag: string, mode: Node.Mode = 0o644): Promise<FileHandle> {
556
- const file = await _open(path, flag, mode, true);
557
- return new FileHandle(getFdForFile(file));
539
+ export async function open(path: fs.PathLike, flag: fs.OpenMode = 'r', mode: fs.Mode = 0o644): Promise<FileHandle> {
540
+ return await _open(path, flag, mode, true);
558
541
  }
559
542
  open satisfies typeof promises.open;
560
543
 
561
- /**
562
- * Opens a file without resolving symlinks
563
- * @internal
564
- */
565
- export async function lopen(path: PathLike, flag: string, mode: Node.Mode = 0o644): Promise<FileHandle> {
566
- const file: File = await _open(path, flag, mode, false);
567
- return new FileHandle(getFdForFile(file));
568
- }
569
-
570
- /**
571
- * Asynchronously reads the entire contents of a file.
572
- */
573
- async function _readFile(fname: string, flag: string, resolveSymlinks: boolean): Promise<Uint8Array> {
574
- const file = await _open(normalizePath(fname), flag, 0o644, resolveSymlinks);
575
-
576
- try {
577
- const stat = await file.stat();
578
- const data = new Uint8Array(stat.size);
579
- await file.read(data, 0, stat.size, 0);
580
- await file.close();
581
- return data;
582
- } catch (e) {
583
- await file.close();
584
- throw e;
585
- }
586
- }
587
-
588
544
  /**
589
545
  * Asynchronously reads the entire contents of a file.
590
546
  * @param filename
@@ -593,17 +549,24 @@ async function _readFile(fname: string, flag: string, resolveSymlinks: boolean):
593
549
  * options.flag Defaults to `'r'`.
594
550
  * @returns file data
595
551
  */
596
- export async function readFile(filename: PathLike, options?: { flag?: Node.OpenMode }): Promise<Buffer>;
597
- export async function readFile(filename: PathLike, options: (Node.EncodingOption & { flag?: Node.OpenMode }) | BufferEncoding): Promise<string>;
598
- export async function readFile(filename: PathLike, _options?: (Node.EncodingOption & { flag?: Node.OpenMode }) | BufferEncoding): Promise<Buffer | string> {
599
- const options = normalizeOptions(_options, null, 'r', 0);
600
- const flag = parseFlag(options.flag);
601
- if (!isReadable(flag)) {
602
- throw new ApiError(ErrorCode.EINVAL, 'Flag passed must allow for reading.');
603
- }
552
+ export async function readFile(path: fs.PathLike | promises.FileHandle, options?: { encoding?: null; flag?: fs.OpenMode } | null): Promise<Buffer>;
553
+ export async function readFile(path: fs.PathLike | promises.FileHandle, options: { encoding: BufferEncoding; flag?: fs.OpenMode } | BufferEncoding): Promise<string>;
554
+ export async function readFile(
555
+ path: fs.PathLike | promises.FileHandle,
556
+ options?: (fs.ObjectEncodingOptions & { flag?: fs.OpenMode }) | BufferEncoding | null
557
+ ): Promise<string | Buffer>;
558
+ export async function readFile(
559
+ path: fs.PathLike | promises.FileHandle,
560
+ _options?: (fs.ObjectEncodingOptions & { flag?: fs.OpenMode }) | BufferEncoding | null
561
+ ): Promise<Buffer | string> {
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);
604
564
 
605
- const data: Buffer = Buffer.from(await _readFile(filename, options.flag, true));
606
- return options.encoding ? data.toString(options.encoding) : data;
565
+ try {
566
+ return await handle.readFile(options);
567
+ } finally {
568
+ await handle.close();
569
+ }
607
570
  }
608
571
  readFile satisfies typeof promises.readFile;
609
572
 
@@ -611,41 +574,36 @@ readFile satisfies typeof promises.readFile;
611
574
  * Asynchronously writes data to a file, replacing the file if it already exists.
612
575
  *
613
576
  * The encoding option is ignored if data is a buffer.
614
- * @param filename
615
- * @param data
577
+ * @param path
578
+ * @param data Note:
616
579
  * @param _options
617
580
  * @option options encoding Defaults to `'utf8'`.
618
581
  * @option options mode Defaults to `0644`.
619
582
  * @option options flag Defaults to `'w'`.
620
583
  */
621
- export async function writeFile(filename: PathLike, data: FileContents, _options?: Node.WriteFileOptions): Promise<void> {
584
+ export async function writeFile(
585
+ path: fs.PathLike | promises.FileHandle,
586
+ data: FileContents | Stream | Iterable<string | ArrayBufferView> | AsyncIterable<string | ArrayBufferView>,
587
+ _options?: (fs.ObjectEncodingOptions & { mode?: fs.Mode; flag?: fs.OpenMode; flush?: boolean }) | BufferEncoding | null
588
+ ): Promise<void> {
622
589
  const options = normalizeOptions(_options, 'utf8', 'w+', 0o644);
623
- const handle = await open(filename, options.flag, options.mode);
590
+ const handle = path instanceof FileHandle ? path : await open(path.toString(), options.flag, options.mode);
624
591
  try {
625
- await handle.writeFile(data, options);
592
+ const _data = typeof data == 'string' ? data : data;
593
+ if (typeof _data != 'string' && !(_data instanceof Uint8Array)) {
594
+ throw new ErrnoError(Errno.EINVAL, 'Iterables and streams not supported', handle.file.path, 'writeFile');
595
+ }
596
+ await handle.writeFile(_data, options);
626
597
  } finally {
627
598
  await handle.close();
628
599
  }
629
600
  }
630
601
  writeFile satisfies typeof promises.writeFile;
631
602
 
632
- /**
633
- * Asynchronously append data to a file, creating the file if
634
- * it not yet exists.
635
- */
636
- async function _appendFile(fname: string, data: Uint8Array, flag: string, mode: number, resolveSymlinks: boolean): Promise<void> {
637
- const file = await _open(fname, flag, mode, resolveSymlinks);
638
- try {
639
- await file.write(data, 0, data.length, null);
640
- } finally {
641
- await file.close();
642
- }
643
- }
644
-
645
603
  /**
646
604
  * Asynchronously append data to a file, creating the file if it not yet
647
605
  * exists.
648
- * @param filename
606
+ * @param path
649
607
  * @param data
650
608
  * @param options
651
609
  * @option options encoding Defaults to `'utf8'`.
@@ -653,20 +611,26 @@ async function _appendFile(fname: string, data: Uint8Array, flag: string, mode:
653
611
  * @option options flag Defaults to `'a'`.
654
612
  */
655
613
  export async function appendFile(
656
- filename: PathLike,
614
+ path: fs.PathLike | promises.FileHandle,
657
615
  data: FileContents,
658
- _options?: BufferEncoding | (Node.EncodingOption & { mode?: Node.Mode; flag?: Node.OpenMode })
616
+ _options?: BufferEncoding | (fs.EncodingOption & { mode?: fs.Mode; flag?: fs.OpenMode }) | null
659
617
  ): Promise<void> {
660
618
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
661
619
  const flag = parseFlag(options.flag);
662
620
  if (!isAppendable(flag)) {
663
- 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.');
664
622
  }
665
623
  if (typeof data != 'string' && !options.encoding) {
666
- throw new ApiError(ErrorCode.EINVAL, 'Encoding not specified');
624
+ throw new ErrnoError(Errno.EINVAL, 'Encoding not specified');
625
+ }
626
+ const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding!) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
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();
667
633
  }
668
- const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : data;
669
- await _appendFile(filename, encodedData, options.flag, options.mode, true);
670
634
  }
671
635
  appendFile satisfies typeof promises.appendFile;
672
636
 
@@ -676,38 +640,65 @@ appendFile satisfies typeof promises.appendFile;
676
640
  * `rmdir`.
677
641
  * @param path
678
642
  */
679
- export async function rmdir(path: PathLike): Promise<void> {
680
- return doOp('rmdir', true, path, cred);
643
+ export async function rmdir(path: fs.PathLike): Promise<void> {
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
+ }
681
652
  }
682
653
  rmdir satisfies typeof promises.rmdir;
683
654
 
684
655
  /**
685
- * `mkdir`.
686
- * @param path
687
- * @param mode defaults to `0777`
656
+ * Asynchronous mkdir(2) - create a directory.
657
+ * @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
658
+ * @param options Either the file mode, or an object optionally specifying the file mode and whether parent folders
659
+ * should be created. If a string is passed, it is parsed as an octal integer. If not specified, defaults to `0o777`.
688
660
  */
689
- export async function mkdir(path: PathLike, mode?: Node.Mode | (Node.MakeDirectoryOptions & { recursive?: false })): Promise<void>;
690
- export async function mkdir(path: PathLike, mode: Node.MakeDirectoryOptions & { recursive: true }): Promise<string>;
691
- export async function mkdir(path: PathLike, mode?: Node.Mode | Node.MakeDirectoryOptions): Promise<string | void> {
692
- await doOp('mkdir', true, path, normalizeMode(typeof mode == 'object' ? mode?.mode : mode, 0o777), cred);
661
+ export async function mkdir(path: fs.PathLike, options: fs.MakeDirectoryOptions & { recursive: true }): Promise<string | undefined>;
662
+ export async function mkdir(path: fs.PathLike, options?: fs.Mode | (fs.MakeDirectoryOptions & { recursive?: false | undefined }) | null): Promise<void>;
663
+ export async function mkdir(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): Promise<string | undefined>;
664
+ export async function mkdir(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): Promise<string | undefined | void> {
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
+ }
693
673
  }
694
674
  mkdir satisfies typeof promises.mkdir;
695
675
 
696
676
  /**
697
- * `readdir`. Reads the contents of a directory.
698
- * @param path
677
+ * Asynchronous readdir(3) - read a directory.
678
+ * @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
679
+ * @param options The encoding (or an object specifying the encoding), used as the encoding of the result. If not provided, `'utf8'` is used.
699
680
  */
700
- export async function readdir(path: PathLike, options?: (Node.EncodingOption & { withFileTypes?: false }) | BufferEncoding): Promise<string[]>;
701
- export async function readdir(path: PathLike, options: Node.BufferEncodingOption & { withFileTypes?: false }): Promise<Buffer[]>;
702
- export async function readdir(path: PathLike, options: Node.EncodingOption & { withFileTypes: true }): Promise<Dirent[]>;
681
+ export async function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & { withFileTypes?: false; recursive?: boolean }) | BufferEncoding | null): Promise<string[]>;
682
+ export async function readdir(path: fs.PathLike, options: fs.BufferEncodingOption & { withFileTypes?: false; recursive?: boolean }): Promise<Buffer[]>;
683
+ export async function readdir(
684
+ path: fs.PathLike,
685
+ options?: (fs.ObjectEncodingOptions & { withFileTypes?: false; recursive?: boolean }) | BufferEncoding | null
686
+ ): Promise<string[] | Buffer[]>;
687
+ export async function readdir(path: fs.PathLike, options: fs.ObjectEncodingOptions & { withFileTypes: true; recursive?: boolean }): Promise<Dirent[]>;
703
688
  export async function readdir(
704
- path: PathLike,
705
- options?: (Node.EncodingOption & { withFileTypes?: boolean }) | BufferEncoding | (Node.BufferEncodingOption & { withFileTypes?: boolean })
689
+ path: fs.PathLike,
690
+ options?: { withFileTypes?: boolean; recursive?: boolean; encoding?: BufferEncoding | 'buffer' | null } | BufferEncoding | 'buffer' | null
706
691
  ): Promise<string[] | Dirent[] | Buffer[]> {
707
692
  path = normalizePath(path);
708
- const entries: string[] = await doOp('readdir', true, path, cred);
709
- const points = [...mounts.keys()];
710
- 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()) {
711
702
  if (point.startsWith(path)) {
712
703
  const entry = point.slice(path.length);
713
704
  if (entry.includes('/') || entry.length == 0) {
@@ -732,9 +723,15 @@ readdir satisfies typeof promises.readdir;
732
723
  * @param existing
733
724
  * @param newpath
734
725
  */
735
- export async function link(existing: PathLike, newpath: PathLike): Promise<void> {
726
+ export async function link(existing: fs.PathLike, newpath: fs.PathLike): Promise<void> {
727
+ existing = normalizePath(existing);
736
728
  newpath = normalizePath(newpath);
737
- return doOp('link', false, existing, 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
+ }
738
735
  }
739
736
  link satisfies typeof promises.link;
740
737
 
@@ -744,18 +741,18 @@ link satisfies typeof promises.link;
744
741
  * @param path link path
745
742
  * @param type can be either `'dir'` or `'file'` (default is `'file'`)
746
743
  */
747
- export async function symlink(target: PathLike, path: PathLike, type: Node.symlink.Type = 'file'): Promise<void> {
748
- if (!['file', 'dir', 'junction'].includes(type)) {
749
- throw new ApiError(ErrorCode.EINVAL, 'Invalid symlink type: ' + type);
744
+ export async function symlink(target: fs.PathLike, path: fs.PathLike, type: fs.symlink.Type | string | null = 'file'): Promise<void> {
745
+ if (!['file', 'dir', 'junction'].includes(type!)) {
746
+ throw new ErrnoError(Errno.EINVAL, 'Invalid symlink type: ' + type);
750
747
  }
751
748
 
752
749
  if (await exists(path)) {
753
- throw ApiError.With('EEXIST', path, 'symlink');
750
+ throw ErrnoError.With('EEXIST', path.toString(), 'symlink');
754
751
  }
755
752
 
756
- await writeFile(path, target);
757
- const file = await _open(path, 'r+', 0o644, false);
758
- await file._setType(FileType.SYMLINK);
753
+ await writeFile(path, target.toString());
754
+ const handle = await _open(path, 'r+', 0o644, false);
755
+ await handle.file._setType(FileType.SYMLINK);
759
756
  }
760
757
  symlink satisfies typeof promises.symlink;
761
758
 
@@ -763,15 +760,18 @@ symlink satisfies typeof promises.symlink;
763
760
  * readlink.
764
761
  * @param path
765
762
  */
766
- export async function readlink(path: PathLike, options: Node.BufferEncodingOption): Promise<Buffer>;
767
- export async function readlink(path: PathLike, options?: Node.EncodingOption | BufferEncoding): Promise<string>;
768
- export async function readlink(path: PathLike, options?: Node.BufferEncodingOption | Node.EncodingOption | BufferEncoding): Promise<string | Buffer> {
769
- const value: Buffer = Buffer.from(await _readFile(path, 'r', false));
770
- const encoding: BufferEncoding | 'buffer' = typeof options == 'object' ? options.encoding : options;
771
- if (encoding == 'buffer') {
772
- return value;
763
+ export async function readlink(path: fs.PathLike, options: fs.BufferEncodingOption): Promise<Buffer>;
764
+ export async function readlink(path: fs.PathLike, options?: fs.EncodingOption | null): Promise<string>;
765
+ export async function readlink(path: fs.PathLike, options?: fs.BufferEncodingOption | fs.EncodingOption | string | null): Promise<string | Buffer>;
766
+ export async function readlink(path: fs.PathLike, options?: fs.BufferEncodingOption | fs.EncodingOption | string | null): Promise<string | Buffer> {
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();
773
774
  }
774
- return value.toString(encoding);
775
775
  }
776
776
  readlink satisfies typeof promises.readlink;
777
777
 
@@ -783,7 +783,7 @@ readlink satisfies typeof promises.readlink;
783
783
  * @param uid
784
784
  * @param gid
785
785
  */
786
- export async function chown(path: PathLike, uid: number, gid: number): Promise<void> {
786
+ export async function chown(path: fs.PathLike, uid: number, gid: number): Promise<void> {
787
787
  const handle = await open(path, 'r+');
788
788
  try {
789
789
  await handle.chown(uid, gid);
@@ -799,8 +799,8 @@ chown satisfies typeof promises.chown;
799
799
  * @param uid
800
800
  * @param gid
801
801
  */
802
- export async function lchown(path: PathLike, uid: number, gid: number): Promise<void> {
803
- const handle = await lopen(path, 'r+');
802
+ export async function lchown(path: fs.PathLike, uid: number, gid: number): Promise<void> {
803
+ const handle: FileHandle = await _open(path, 'r+', 0o644, false);
804
804
  try {
805
805
  await handle.chown(uid, gid);
806
806
  } finally {
@@ -814,7 +814,7 @@ lchown satisfies typeof promises.lchown;
814
814
  * @param path
815
815
  * @param mode
816
816
  */
817
- export async function chmod(path: PathLike, mode: Node.Mode): Promise<void> {
817
+ export async function chmod(path: fs.PathLike, mode: fs.Mode): Promise<void> {
818
818
  const handle = await open(path, 'r+');
819
819
  try {
820
820
  await handle.chmod(mode);
@@ -829,8 +829,8 @@ chmod satisfies typeof promises.chmod;
829
829
  * @param path
830
830
  * @param mode
831
831
  */
832
- export async function lchmod(path: PathLike, mode: Node.Mode): Promise<void> {
833
- const handle = await lopen(path, 'r+');
832
+ export async function lchmod(path: fs.PathLike, mode: fs.Mode): Promise<void> {
833
+ const handle: FileHandle = await _open(path, 'r+', 0o644, false);
834
834
  try {
835
835
  await handle.chmod(mode);
836
836
  } finally {
@@ -845,7 +845,7 @@ lchmod satisfies typeof promises.lchmod;
845
845
  * @param atime
846
846
  * @param mtime
847
847
  */
848
- export async function utimes(path: PathLike, atime: string | number | Date, mtime: string | number | Date): Promise<void> {
848
+ export async function utimes(path: fs.PathLike, atime: string | number | Date, mtime: string | number | Date): Promise<void> {
849
849
  const handle = await open(path, 'r+');
850
850
  try {
851
851
  await handle.utimes(atime, mtime);
@@ -861,10 +861,10 @@ utimes satisfies typeof promises.utimes;
861
861
  * @param atime
862
862
  * @param mtime
863
863
  */
864
- export async function lutimes(path: PathLike, atime: number | Date, mtime: number | Date): Promise<void> {
865
- const handle = await lopen(path, 'r+');
864
+ export async function lutimes(path: fs.PathLike, atime: fs.TimeLike, mtime: fs.TimeLike): Promise<void> {
865
+ const handle: FileHandle = await _open(path, 'r+', 0o644, false);
866
866
  try {
867
- await handle.utimes(atime, mtime);
867
+ await handle.utimes(new Date(atime), new Date(mtime));
868
868
  } finally {
869
869
  await handle.close();
870
870
  }
@@ -875,12 +875,10 @@ lutimes satisfies typeof promises.lutimes;
875
875
  * Asynchronous realpath(3) - return the canonicalized absolute pathname.
876
876
  * @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
877
877
  * @param options The encoding (or an object specifying the encoding), used as the encoding of the result. If not provided, `'utf8'` is used.
878
- *
879
- * Note: This *Can not* use doOp since doOp depends on it
880
878
  */
881
- export async function realpath(path: PathLike, options: Node.BufferEncodingOption): Promise<Buffer>;
882
- export async function realpath(path: PathLike, options?: Node.EncodingOption | BufferEncoding): Promise<string>;
883
- export async function realpath(path: PathLike, options?: Node.EncodingOption | BufferEncoding | Node.BufferEncodingOption): Promise<string | Buffer> {
879
+ export async function realpath(path: fs.PathLike, options: fs.BufferEncodingOption): Promise<Buffer>;
880
+ export async function realpath(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding): Promise<string>;
881
+ export async function realpath(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding | fs.BufferEncodingOption): Promise<string | Buffer> {
884
882
  path = normalizePath(path);
885
883
  const { base, dir } = parse(path);
886
884
  const lpath = join(dir == '/' ? '/' : await realpath(dir), base);
@@ -894,7 +892,7 @@ export async function realpath(path: PathLike, options?: Node.EncodingOption | B
894
892
 
895
893
  return realpath(mountPoint + (await readlink(lpath)));
896
894
  } catch (e) {
897
- throw fixError(e, { [resolvedPath]: lpath });
895
+ throw fixError(e as Error, { [resolvedPath]: lpath });
898
896
  }
899
897
  }
900
898
  realpath satisfies typeof promises.realpath;
@@ -902,10 +900,11 @@ realpath satisfies typeof promises.realpath;
902
900
  /**
903
901
  * @todo Implement
904
902
  */
905
- export function watch(filename: PathLike, options: (Node.WatchOptions & { encoding: 'buffer' }) | 'buffer'): AsyncIterable<FileChangeInfo<Buffer>>;
906
- export function watch(filename: PathLike, options?: Node.WatchOptions | BufferEncoding): AsyncIterable<FileChangeInfo<string>>;
907
- export function watch(filename: PathLike, options: Node.WatchOptions | string): AsyncIterable<FileChangeInfo<string>> | AsyncIterable<FileChangeInfo<Buffer>> {
908
- throw ApiError.With('ENOSYS', filename, 'watch');
903
+ export function watch(filename: fs.PathLike, options?: fs.WatchOptions | BufferEncoding): AsyncIterable<FileChangeInfo<string>>;
904
+ export function watch(filename: fs.PathLike, options: fs.WatchOptions | fs.BufferEncodingOption): AsyncIterable<FileChangeInfo<Buffer>>;
905
+ export function watch(filename: fs.PathLike, options?: fs.WatchOptions | string): AsyncIterable<FileChangeInfo<string>> | AsyncIterable<FileChangeInfo<Buffer>>;
906
+ export function watch(filename: fs.PathLike, options: fs.WatchOptions | string = {}): AsyncIterable<FileChangeInfo<string>> | AsyncIterable<FileChangeInfo<Buffer>> {
907
+ throw ErrnoError.With('ENOSYS', filename.toString(), 'watch');
909
908
  }
910
909
  watch satisfies typeof promises.watch;
911
910
 
@@ -914,10 +913,10 @@ watch satisfies typeof promises.watch;
914
913
  * @param path
915
914
  * @param mode
916
915
  */
917
- export async function access(path: PathLike, mode: number = constants.F_OK): Promise<void> {
916
+ export async function access(path: fs.PathLike, mode: number = constants.F_OK): Promise<void> {
918
917
  const stats = await stat(path);
919
918
  if (!stats.hasAccess(mode, cred)) {
920
- throw new ApiError(ErrorCode.EACCES);
919
+ throw new ErrnoError(Errno.EACCES);
921
920
  }
922
921
  }
923
922
  access satisfies typeof promises.access;
@@ -926,7 +925,7 @@ access satisfies typeof promises.access;
926
925
  * Asynchronous `rm`. Removes files or directories (recursively).
927
926
  * @param path The path to the file or directory to remove.
928
927
  */
929
- export async function rm(path: PathLike, options?: Node.RmOptions) {
928
+ export async function rm(path: fs.PathLike, options?: fs.RmOptions) {
930
929
  path = normalizePath(path);
931
930
 
932
931
  const stats = await stat(path);
@@ -950,7 +949,7 @@ export async function rm(path: PathLike, options?: Node.RmOptions) {
950
949
  case constants.S_IFIFO:
951
950
  case constants.S_IFSOCK:
952
951
  default:
953
- throw new ApiError(ErrorCode.EPERM, 'File type not supported', path, 'rm');
952
+ throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
954
953
  }
955
954
  }
956
955
  rm satisfies typeof promises.rm;
@@ -961,10 +960,10 @@ rm satisfies typeof promises.rm;
961
960
  * @param options The encoding (or an object including `encoding`).
962
961
  * @returns The path to the created temporary directory, encoded as a string or buffer.
963
962
  */
964
- export async function mkdtemp(prefix: string, options?: Node.EncodingOption): Promise<string>;
965
- export async function mkdtemp(prefix: string, options?: Node.BufferEncodingOption): Promise<Buffer>;
966
- export async function mkdtemp(prefix: string, options?: Node.EncodingOption | Node.BufferEncodingOption): Promise<string | Buffer> {
967
- const encoding = typeof options === 'object' ? options.encoding : options || 'utf8';
963
+ export async function mkdtemp(prefix: string, options?: fs.EncodingOption): Promise<string>;
964
+ export async function mkdtemp(prefix: string, options?: fs.BufferEncodingOption): Promise<Buffer>;
965
+ export async function mkdtemp(prefix: string, options?: fs.EncodingOption | fs.BufferEncodingOption): Promise<string | Buffer> {
966
+ const encoding = typeof options === 'object' ? options?.encoding : options || 'utf8';
968
967
  const fsName = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`;
969
968
  const resolvedPath = '/tmp/' + fsName;
970
969
 
@@ -981,12 +980,12 @@ mkdtemp satisfies typeof promises.mkdtemp;
981
980
  * @param mode Optional flags for the copy operation. Currently supports these flags:
982
981
  * * `fs.constants.COPYFILE_EXCL`: If the destination file already exists, the operation fails.
983
982
  */
984
- export async function copyFile(src: PathLike, dest: PathLike, mode?: number): Promise<void> {
983
+ export async function copyFile(src: fs.PathLike, dest: fs.PathLike, mode?: number): Promise<void> {
985
984
  src = normalizePath(src);
986
985
  dest = normalizePath(dest);
987
986
 
988
987
  if (mode && mode & constants.COPYFILE_EXCL && (await exists(dest))) {
989
- throw new ApiError(ErrorCode.EEXIST, 'Destination file already exists.', dest, 'copyFile');
988
+ throw new ErrnoError(Errno.EEXIST, 'Destination file already exists.', dest, 'copyFile');
990
989
  }
991
990
 
992
991
  await writeFile(dest, await readFile(src));
@@ -999,7 +998,7 @@ copyFile satisfies typeof promises.copyFile;
999
998
  * @param options Options for opening the directory.
1000
999
  * @returns A `Dir` object representing the opened directory.
1001
1000
  */
1002
- export async function opendir(path: PathLike, options?: Node.OpenDirOptions): Promise<Dir> {
1001
+ export async function opendir(path: fs.PathLike, options?: fs.OpenDirOptions): Promise<Dir> {
1003
1002
  path = normalizePath(path);
1004
1003
  return new Dir(path);
1005
1004
  }
@@ -1017,20 +1016,20 @@ opendir satisfies typeof promises.opendir;
1017
1016
  * * `preserveTimestamps`: Preserve file timestamps.
1018
1017
  * * `recursive`: If `true`, copies directories recursively.
1019
1018
  */
1020
- export async function cp(source: PathLike, destination: PathLike, opts?: Node.CopyOptions): Promise<void> {
1019
+ export async function cp(source: fs.PathLike, destination: fs.PathLike, opts?: fs.CopyOptions): Promise<void> {
1021
1020
  source = normalizePath(source);
1022
1021
  destination = normalizePath(destination);
1023
1022
 
1024
1023
  const srcStats = await lstat(source); // Use lstat to follow symlinks if not dereferencing
1025
1024
 
1026
1025
  if (opts?.errorOnExist && (await exists(destination))) {
1027
- 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');
1028
1027
  }
1029
1028
 
1030
1029
  switch (srcStats.mode & constants.S_IFMT) {
1031
1030
  case constants.S_IFDIR:
1032
1031
  if (!opts?.recursive) {
1033
- 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');
1034
1033
  }
1035
1034
  await mkdir(destination, { recursive: true }); // Ensure the destination directory exists
1036
1035
  for (const dirent of await readdir(source, { withFileTypes: true })) {
@@ -1049,7 +1048,7 @@ export async function cp(source: PathLike, destination: PathLike, opts?: Node.Co
1049
1048
  case constants.S_IFIFO:
1050
1049
  case constants.S_IFSOCK:
1051
1050
  default:
1052
- throw new ApiError(ErrorCode.EPERM, 'File type not supported', source, 'rm');
1051
+ throw new ErrnoError(Errno.EPERM, 'File type not supported', source, 'rm');
1053
1052
  }
1054
1053
 
1055
1054
  // Optionally preserve timestamps
@@ -1063,9 +1062,9 @@ cp satisfies typeof promises.cp;
1063
1062
  * @since v18.15.0
1064
1063
  * @return Fulfills with an {fs.StatFs} for the file system.
1065
1064
  */
1066
- export async function statfs(path: PathLike, opts?: Node.StatFsOptions & { bigint?: false }): Promise<StatsFs>;
1067
- export async function statfs(path: PathLike, opts: Node.StatFsOptions & { bigint: true }): Promise<BigIntStatsFs>;
1068
- export async function statfs(path: PathLike, opts?: Node.StatFsOptions): Promise<StatsFs | BigIntStatsFs>;
1069
- export async function statfs(path: PathLike, opts?: Node.StatFsOptions): Promise<StatsFs | BigIntStatsFs> {
1070
- throw ApiError.With('ENOSYS', path, 'statfs');
1065
+ export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions & { bigint?: false }): Promise<StatsFs>;
1066
+ export async function statfs(path: fs.PathLike, opts: fs.StatFsOptions & { bigint: true }): Promise<BigIntStatsFs>;
1067
+ export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions): Promise<StatsFs | BigIntStatsFs>;
1068
+ export async function statfs(path: fs.PathLike, opts?: fs.StatFsOptions): Promise<StatsFs | BigIntStatsFs> {
1069
+ throw ErrnoError.With('ENOSYS', path.toString(), 'statfs');
1071
1070
  }