@zenfs/core 1.1.6 → 1.2.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 (58) hide show
  1. package/dist/backends/file_index.js +0 -3
  2. package/dist/backends/overlay.js +0 -8
  3. package/dist/backends/store/fs.js +4 -17
  4. package/dist/config.d.ts +14 -0
  5. package/dist/config.js +4 -0
  6. package/dist/devices.js +0 -12
  7. package/dist/emulation/cache.d.ts +21 -0
  8. package/dist/emulation/cache.js +36 -0
  9. package/dist/emulation/promises.d.ts +9 -14
  10. package/dist/emulation/promises.js +71 -48
  11. package/dist/emulation/shared.d.ts +22 -0
  12. package/dist/emulation/shared.js +6 -0
  13. package/dist/emulation/sync.d.ts +11 -20
  14. package/dist/emulation/sync.js +44 -23
  15. package/package.json +4 -2
  16. package/scripts/test.js +14 -1
  17. package/src/backends/backend.ts +160 -0
  18. package/src/backends/fetch.ts +180 -0
  19. package/src/backends/file_index.ts +206 -0
  20. package/src/backends/memory.ts +50 -0
  21. package/src/backends/overlay.ts +560 -0
  22. package/src/backends/port/fs.ts +335 -0
  23. package/src/backends/port/readme.md +54 -0
  24. package/src/backends/port/rpc.ts +167 -0
  25. package/src/backends/readme.md +3 -0
  26. package/src/backends/store/fs.ts +700 -0
  27. package/src/backends/store/readme.md +9 -0
  28. package/src/backends/store/simple.ts +146 -0
  29. package/src/backends/store/store.ts +173 -0
  30. package/src/config.ts +173 -0
  31. package/src/credentials.ts +31 -0
  32. package/src/devices.ts +459 -0
  33. package/src/emulation/async.ts +834 -0
  34. package/src/emulation/cache.ts +44 -0
  35. package/src/emulation/constants.ts +182 -0
  36. package/src/emulation/dir.ts +138 -0
  37. package/src/emulation/index.ts +8 -0
  38. package/src/emulation/path.ts +440 -0
  39. package/src/emulation/promises.ts +1133 -0
  40. package/src/emulation/shared.ts +160 -0
  41. package/src/emulation/streams.ts +34 -0
  42. package/src/emulation/sync.ts +867 -0
  43. package/src/emulation/watchers.ts +193 -0
  44. package/src/error.ts +307 -0
  45. package/src/file.ts +661 -0
  46. package/src/filesystem.ts +174 -0
  47. package/src/index.ts +25 -0
  48. package/src/inode.ts +132 -0
  49. package/src/mixins/async.ts +208 -0
  50. package/src/mixins/index.ts +5 -0
  51. package/src/mixins/mutexed.ts +257 -0
  52. package/src/mixins/readonly.ts +96 -0
  53. package/src/mixins/shared.ts +25 -0
  54. package/src/mixins/sync.ts +58 -0
  55. package/src/polyfills.ts +21 -0
  56. package/src/stats.ts +363 -0
  57. package/src/utils.ts +288 -0
  58. package/tests/fs/readdir.test.ts +3 -3
@@ -151,9 +151,6 @@ export class IndexFS extends Readonly(FileSystem) {
151
151
  if (!stats) {
152
152
  throw ErrnoError.With('ENOENT', path, 'readdir');
153
153
  }
154
- if (!stats.isDirectory()) {
155
- throw ErrnoError.With('ENOTDIR', path, 'readdir');
156
- }
157
154
  const content = JSON.parse(decodeUTF8(stats.fileData));
158
155
  if (!Array.isArray(content)) {
159
156
  throw ErrnoError.With('ENODATA', path, 'readdir');
@@ -314,10 +314,6 @@ export class UnmutexedOverlayFS extends FileSystem {
314
314
  }
315
315
  async readdir(path) {
316
316
  this.checkInitialized();
317
- const dirStats = await this.stat(path);
318
- if (!dirStats.isDirectory()) {
319
- throw ErrnoError.With('ENOTDIR', path, 'readdir');
320
- }
321
317
  // Readdir in both, check delete log on RO file system's listing, merge, return.
322
318
  const contents = [];
323
319
  try {
@@ -341,10 +337,6 @@ export class UnmutexedOverlayFS extends FileSystem {
341
337
  }
342
338
  readdirSync(path) {
343
339
  this.checkInitialized();
344
- const dirStats = this.statSync(path);
345
- if (!dirStats.isDirectory()) {
346
- throw ErrnoError.With('ENOTDIR', path, 'readdir');
347
- }
348
340
  // Readdir in both, check delete log on RO file system's listing, merge, return.
349
341
  let contents = [];
350
342
  try {
@@ -580,16 +580,12 @@ export class StoreFS extends FileSystem {
580
580
  * listing.
581
581
  */
582
582
  async getDirListing(tx, inode, path) {
583
- if (!inode.toStats().isDirectory()) {
584
- throw ErrnoError.With('ENOTDIR', path, 'getDirListing');
585
- }
586
583
  const data = await tx.get(inode.ino);
584
+ /*
585
+ Occurs when data is undefined,or corresponds to something other than a directory listing.
586
+ The latter should never occur unless the file system is corrupted.
587
+ */
587
588
  if (!data) {
588
- /*
589
- Occurs when data is undefined, or corresponds to something other
590
- than a directory listing. The latter should never occur unless
591
- the file system is corrupted.
592
- */
593
589
  throw ErrnoError.With('ENOENT', path, 'getDirListing');
594
590
  }
595
591
  return decodeDirListing(data);
@@ -598,9 +594,6 @@ export class StoreFS extends FileSystem {
598
594
  * Given the Inode of a directory, retrieves the corresponding directory listing.
599
595
  */
600
596
  getDirListingSync(tx, inode, p) {
601
- if (!inode.toStats().isDirectory()) {
602
- throw ErrnoError.With('ENOTDIR', p, 'getDirListing');
603
- }
604
597
  const data = tx.getSync(inode.ino);
605
598
  if (!data) {
606
599
  throw ErrnoError.With('ENOENT', p, 'getDirListing');
@@ -758,9 +751,6 @@ export class StoreFS extends FileSystem {
758
751
  if (!isDir && fileNode.toStats().isDirectory()) {
759
752
  throw ErrnoError.With('EISDIR', path, 'remove');
760
753
  }
761
- if (isDir && !fileNode.toStats().isDirectory()) {
762
- throw ErrnoError.With('ENOTDIR', path, 'remove');
763
- }
764
754
  await tx.set(parentNode.ino, encodeDirListing(listing));
765
755
  if (--fileNode.nlink < 1) {
766
756
  // remove file
@@ -801,9 +791,6 @@ export class StoreFS extends FileSystem {
801
791
  if (!isDir && fileNode.toStats().isDirectory()) {
802
792
  throw ErrnoError.With('EISDIR', path, 'remove');
803
793
  }
804
- if (isDir && !fileNode.toStats().isDirectory()) {
805
- throw ErrnoError.With('ENOTDIR', path, 'remove');
806
- }
807
794
  // Update directory listing.
808
795
  tx.setSync(parentNode.ino, encodeDirListing(listing));
809
796
  if (--fileNode.nlink < 1) {
package/dist/config.d.ts CHANGED
@@ -38,6 +38,20 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
38
38
  * @experimental
39
39
  */
40
40
  addDevices: boolean;
41
+ /**
42
+ * If true, enables caching stats for certain operations.
43
+ * This should reduce the number of stat calls performed.
44
+ * @default false
45
+ * @experimental
46
+ */
47
+ cacheStats: boolean;
48
+ /**
49
+ * If true, disables *all* permissions checking.
50
+ * This can increase performance
51
+ * @default false
52
+ * @experimental
53
+ */
54
+ disableAccessChecks: boolean;
41
55
  }
42
56
  /**
43
57
  * Configures ZenFS with single mount point /
package/dist/config.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
2
2
  import { credentials } from './credentials.js';
3
3
  import { DeviceFS, fullDevice, nullDevice, randomDevice, zeroDevice } from './devices.js';
4
+ import * as cache from './emulation/cache.js';
4
5
  import * as fs from './emulation/index.js';
6
+ import { config } from './emulation/shared.js';
5
7
  import { Errno, ErrnoError } from './error.js';
6
8
  import { FileSystem } from './filesystem.js';
7
9
  function isMountConfig(arg) {
@@ -66,6 +68,8 @@ export async function configure(configuration) {
66
68
  const uid = 'uid' in configuration ? configuration.uid || 0 : 0;
67
69
  const gid = 'gid' in configuration ? configuration.gid || 0 : 0;
68
70
  Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
71
+ cache.setEnabled(configuration.cacheStats ?? false);
72
+ config.checkAccess = !configuration.disableAccessChecks;
69
73
  if (configuration.addDevices) {
70
74
  const devfs = new DeviceFS();
71
75
  devfs.createDevice('/null', nullDevice);
package/dist/devices.js CHANGED
@@ -254,15 +254,9 @@ export class DeviceFS extends StoreFS {
254
254
  return super.unlinkSync(path);
255
255
  }
256
256
  async rmdir(path) {
257
- if (this.devices.has(path)) {
258
- throw ErrnoError.With('ENOTDIR', path, 'rmdir');
259
- }
260
257
  return super.rmdir(path);
261
258
  }
262
259
  rmdirSync(path) {
263
- if (this.devices.has(path)) {
264
- throw ErrnoError.With('ENOTDIR', path, 'rmdir');
265
- }
266
260
  return super.rmdirSync(path);
267
261
  }
268
262
  async mkdir(path, mode) {
@@ -278,9 +272,6 @@ export class DeviceFS extends StoreFS {
278
272
  return super.mkdirSync(path, mode);
279
273
  }
280
274
  async readdir(path) {
281
- if (this.devices.has(path)) {
282
- throw ErrnoError.With('ENOTDIR', path, 'readdir');
283
- }
284
275
  const entries = await super.readdir(path);
285
276
  for (const dev of this.devices.keys()) {
286
277
  if (dirname(dev) == path) {
@@ -290,9 +281,6 @@ export class DeviceFS extends StoreFS {
290
281
  return entries;
291
282
  }
292
283
  readdirSync(path) {
293
- if (this.devices.has(path)) {
294
- throw ErrnoError.With('ENOTDIR', path, 'readdirSync');
295
- }
296
284
  const entries = super.readdirSync(path);
297
285
  for (const dev of this.devices.keys()) {
298
286
  if (dirname(dev) == path) {
@@ -0,0 +1,21 @@
1
+ import type { Stats } from '../stats.js';
2
+ /**
3
+ * Whether the cache is enabled
4
+ */
5
+ export declare let isEnabled: boolean;
6
+ /**
7
+ * Sets whether the cache is enabled or not
8
+ */
9
+ export declare function setEnabled(value: boolean): void;
10
+ /**
11
+ * Gets stats from the cache, if they exist and the cache is enabled.
12
+ */
13
+ export declare function getStats(path: string): Stats | undefined;
14
+ /**
15
+ * Adds stats if the cache is enabled
16
+ */
17
+ export declare function setStats(path: string, value: Stats): void;
18
+ /**
19
+ * Clears the cache if it is enabled
20
+ */
21
+ export declare function clearStats(): void;
@@ -0,0 +1,36 @@
1
+ /* Experimental caching */
2
+ /**
3
+ * Whether the cache is enabled
4
+ */
5
+ export let isEnabled = false;
6
+ /**
7
+ * Sets whether the cache is enabled or not
8
+ */
9
+ export function setEnabled(value) {
10
+ isEnabled = value;
11
+ }
12
+ const stats = new Map();
13
+ /**
14
+ * Gets stats from the cache, if they exist and the cache is enabled.
15
+ */
16
+ export function getStats(path) {
17
+ if (!isEnabled)
18
+ return;
19
+ return stats.get(path);
20
+ }
21
+ /**
22
+ * Adds stats if the cache is enabled
23
+ */
24
+ export function setStats(path, value) {
25
+ if (!isEnabled)
26
+ return;
27
+ stats.set(path, value);
28
+ }
29
+ /**
30
+ * Clears the cache if it is enabled
31
+ */
32
+ export function clearStats() {
33
+ if (!isEnabled)
34
+ return;
35
+ stats.clear();
36
+ }
@@ -9,6 +9,7 @@ import type { FileContents } from '../filesystem.js';
9
9
  import '../polyfills.js';
10
10
  import { BigIntStats, type Stats } from '../stats.js';
11
11
  import { Dir, Dirent } from './dir.js';
12
+ import { type InternalOptions, type ReaddirOptions } from './shared.js';
12
13
  import { ReadStream, WriteStream } from './streams.js';
13
14
  export * as constants from './constants.js';
14
15
  export declare class FileHandle implements promises.FileHandle {
@@ -248,30 +249,24 @@ export declare function mkdir(path: fs.PathLike, options?: fs.Mode | (fs.MakeDir
248
249
  export declare function mkdir(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): Promise<string | undefined>;
249
250
  /**
250
251
  * Asynchronous readdir(3) - read a directory.
252
+ *
253
+ * Note: The order of entries is not guaranteed
251
254
  * @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
252
255
  * @param options The encoding (or an object specifying the encoding), used as the encoding of the result. If not provided, `'utf8'`.
253
256
  */
254
- export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & {
257
+ export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
255
258
  withFileTypes?: false;
256
- recursive?: boolean;
257
259
  }) | BufferEncoding | null): Promise<string[]>;
258
- export declare function readdir(path: fs.PathLike, options: fs.BufferEncodingOption & {
260
+ export declare function readdir(path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & {
259
261
  withFileTypes?: false;
260
- recursive?: boolean;
261
262
  }): Promise<Buffer[]>;
262
- export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & {
263
+ export declare function readdir(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
263
264
  withFileTypes?: false;
264
- recursive?: boolean;
265
265
  }) | BufferEncoding | null): Promise<string[] | Buffer[]>;
266
- export declare function readdir(path: fs.PathLike, options: fs.ObjectEncodingOptions & {
266
+ export declare function readdir(path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & {
267
267
  withFileTypes: true;
268
- recursive?: boolean;
269
268
  }): Promise<Dirent[]>;
270
- export declare function readdir(path: fs.PathLike, options?: {
271
- withFileTypes?: boolean;
272
- recursive?: boolean;
273
- encoding?: BufferEncoding | 'buffer' | null;
274
- } | BufferEncoding | 'buffer' | null): Promise<string[] | Dirent[] | Buffer[]>;
269
+ export declare function readdir(path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null): Promise<string[] | Dirent[] | Buffer[]>;
275
270
  export declare function link(targetPath: fs.PathLike, linkPath: fs.PathLike): Promise<void>;
276
271
  /**
277
272
  * `symlink`.
@@ -311,7 +306,7 @@ export declare function access(path: fs.PathLike, mode?: number): Promise<void>;
311
306
  * Asynchronous `rm`. Removes files or directories (recursively).
312
307
  * @param path The path to the file or directory to remove.
313
308
  */
314
- export declare function rm(path: fs.PathLike, options?: fs.RmOptions): Promise<void>;
309
+ export declare function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOptions): Promise<void>;
315
310
  /**
316
311
  * Asynchronous `mkdtemp`. Creates a unique temporary directory.
317
312
  * @param prefix The directory prefix.
@@ -52,10 +52,11 @@ import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWrit
52
52
  import '../polyfills.js';
53
53
  import { BigIntStats } from '../stats.js';
54
54
  import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
55
+ import * as cache from './cache.js';
55
56
  import * as constants from './constants.js';
56
57
  import { Dir, Dirent } from './dir.js';
57
58
  import { dirname, join, parse } from './path.js';
58
- import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
59
+ import { _statfs, config, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
59
60
  import { ReadStream, WriteStream } from './streams.js';
60
61
  import { FSWatcher, emitChange } from './watchers.js';
61
62
  export * as constants from './constants.js';
@@ -217,7 +218,7 @@ export class FileHandle {
217
218
  }
218
219
  async stat(opts) {
219
220
  const stats = await this.file.stat();
220
- if (!stats.hasAccess(constants.R_OK)) {
221
+ if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
221
222
  throw ErrnoError.With('EACCES', this.file.path, 'stat');
222
223
  }
223
224
  return opts?.bigint ? new BigIntStats(stats) : stats;
@@ -351,7 +352,7 @@ export async function rename(oldPath, newPath) {
351
352
  newPath = normalizePath(newPath);
352
353
  const src = resolveMount(oldPath);
353
354
  const dst = resolveMount(newPath);
354
- if (!(await stat(dirname(oldPath))).hasAccess(constants.W_OK)) {
355
+ if (config.checkAccess && !(await stat(dirname(oldPath))).hasAccess(constants.W_OK)) {
355
356
  throw ErrnoError.With('EACCES', oldPath, 'rename');
356
357
  }
357
358
  try {
@@ -389,7 +390,7 @@ export async function stat(path, options) {
389
390
  const { fs, path: resolved } = resolveMount(await realpath(path));
390
391
  try {
391
392
  const stats = await fs.stat(resolved);
392
- if (!stats.hasAccess(constants.R_OK)) {
393
+ if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
393
394
  throw ErrnoError.With('EACCES', resolved, 'stat');
394
395
  }
395
396
  return options?.bigint ? new BigIntStats(stats) : stats;
@@ -433,7 +434,7 @@ export async function unlink(path) {
433
434
  path = normalizePath(path);
434
435
  const { fs, path: resolved } = resolveMount(path);
435
436
  try {
436
- if (!(await fs.stat(resolved)).hasAccess(constants.W_OK)) {
437
+ if (config.checkAccess && !(cache.getStats(path) || (await fs.stat(resolved))).hasAccess(constants.W_OK)) {
437
438
  throw ErrnoError.With('EACCES', resolved, 'unlink');
438
439
  }
439
440
  await fs.unlink(resolved);
@@ -460,7 +461,7 @@ async function _open(path, _flag, _mode = 0o644, resolveSymlinks) {
460
461
  }
461
462
  // Create the file
462
463
  const parentStats = await fs.stat(dirname(resolved));
463
- if (!parentStats.hasAccess(constants.W_OK)) {
464
+ if (config.checkAccess && !parentStats.hasAccess(constants.W_OK)) {
464
465
  throw ErrnoError.With('EACCES', dirname(path), '_open');
465
466
  }
466
467
  if (!parentStats.isDirectory()) {
@@ -468,7 +469,7 @@ async function _open(path, _flag, _mode = 0o644, resolveSymlinks) {
468
469
  }
469
470
  return new FileHandle(await fs.createFile(resolved, flag, mode));
470
471
  }
471
- if (!stats.hasAccess(flagToMode(flag))) {
472
+ if (config.checkAccess && !stats.hasAccess(flagToMode(flag))) {
472
473
  throw ErrnoError.With('EACCES', path, '_open');
473
474
  }
474
475
  if (isExclusive(flag)) {
@@ -579,11 +580,14 @@ export async function appendFile(path, data, _options) {
579
580
  appendFile;
580
581
  // DIRECTORY-ONLY METHODS
581
582
  export async function rmdir(path) {
582
- path = normalizePath(path);
583
583
  path = await realpath(path);
584
584
  const { fs, path: resolved } = resolveMount(path);
585
585
  try {
586
- if (!(await fs.stat(resolved)).hasAccess(constants.W_OK)) {
586
+ const stats = cache.getStats(path) || (await fs.stat(resolved));
587
+ if (!stats.isDirectory()) {
588
+ throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
589
+ }
590
+ if (config.checkAccess && !stats.hasAccess(constants.W_OK)) {
587
591
  throw ErrnoError.With('EACCES', resolved, 'rmdir');
588
592
  }
589
593
  await fs.rmdir(resolved);
@@ -597,12 +601,12 @@ rmdir;
597
601
  export async function mkdir(path, options) {
598
602
  options = typeof options === 'object' ? options : { mode: options };
599
603
  const mode = normalizeMode(options?.mode, 0o777);
600
- path = await realpath(normalizePath(path));
604
+ path = await realpath(path);
601
605
  const { fs, path: resolved } = resolveMount(path);
602
606
  const errorPaths = { [resolved]: path };
603
607
  try {
604
608
  if (!options?.recursive) {
605
- if (!(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK)) {
609
+ if (config.checkAccess && !(await fs.stat(dirname(resolved))).hasAccess(constants.W_OK)) {
606
610
  throw ErrnoError.With('EACCES', dirname(resolved), 'mkdir');
607
611
  }
608
612
  await fs.mkdir(resolved, mode);
@@ -615,7 +619,7 @@ export async function mkdir(path, options) {
615
619
  errorPaths[dir] = origDir;
616
620
  }
617
621
  for (const dir of dirs) {
618
- if (!(await fs.stat(dirname(dir))).hasAccess(constants.W_OK)) {
622
+ if (config.checkAccess && !(await fs.stat(dirname(dir))).hasAccess(constants.W_OK)) {
619
623
  throw ErrnoError.With('EACCES', dirname(dir), 'mkdir');
620
624
  }
621
625
  await fs.mkdir(dir, mode);
@@ -630,14 +634,20 @@ export async function mkdir(path, options) {
630
634
  mkdir;
631
635
  export async function readdir(path, options) {
632
636
  options = typeof options === 'object' ? options : { encoding: options };
633
- path = await realpath(normalizePath(path));
637
+ path = await realpath(path);
638
+ const handleError = (e) => {
639
+ throw fixError(e, { [resolved]: path });
640
+ };
634
641
  const { fs, path: resolved } = resolveMount(path);
635
- if (!(await fs.stat(resolved)).hasAccess(constants.R_OK)) {
642
+ const stats = cache.getStats(path) || (await fs.stat(resolved).catch(handleError));
643
+ cache.setStats(path, stats);
644
+ if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
636
645
  throw ErrnoError.With('EACCES', path, 'readdir');
637
646
  }
638
- const entries = await fs.readdir(resolved).catch((e) => {
639
- throw fixError(e, { [resolved]: path });
640
- });
647
+ if (!stats.isDirectory()) {
648
+ throw ErrnoError.With('ENOTDIR', path, 'readdir');
649
+ }
650
+ const entries = await fs.readdir(resolved).catch(handleError);
641
651
  for (const point of mounts.keys()) {
642
652
  if (point.startsWith(path)) {
643
653
  const entry = point.slice(path.length);
@@ -649,26 +659,24 @@ export async function readdir(path, options) {
649
659
  }
650
660
  }
651
661
  const values = [];
652
- for (const entry of entries) {
653
- let stats;
662
+ const addEntry = async (entry) => {
663
+ let entryStats;
654
664
  if (options?.recursive || options?.withFileTypes) {
655
- stats = await fs.stat(join(resolved, entry)).catch((error) => {
656
- throw fixError(error, { [resolved]: path });
657
- });
665
+ entryStats = cache.getStats(join(path, entry)) || (await fs.stat(join(resolved, entry)).catch(handleError));
666
+ cache.setStats(join(path, entry), entryStats);
658
667
  }
659
668
  if (options?.withFileTypes) {
660
- values.push(new Dirent(entry, stats));
669
+ values.push(new Dirent(entry, entryStats));
661
670
  }
662
- else if (options?.encoding === 'buffer') {
671
+ else if (options?.encoding == 'buffer') {
663
672
  values.push(Buffer.from(entry));
664
673
  }
665
674
  else {
666
675
  values.push(entry);
667
676
  }
668
- if (!options?.recursive || !stats?.isDirectory()) {
669
- continue;
670
- }
671
- for (const subEntry of await readdir(join(path, entry), options)) {
677
+ if (!options?.recursive || !entryStats?.isDirectory())
678
+ return;
679
+ for (const subEntry of await readdir(join(path, entry), { ...options, _isIndirect: true })) {
672
680
  if (subEntry instanceof Dirent) {
673
681
  subEntry.path = join(entry, subEntry.path);
674
682
  values.push(subEntry);
@@ -681,26 +689,30 @@ export async function readdir(path, options) {
681
689
  values.push(join(entry, subEntry));
682
690
  }
683
691
  }
692
+ };
693
+ await Promise.all(entries.map(addEntry));
694
+ if (!options?._isIndirect) {
695
+ cache.clearStats();
684
696
  }
685
697
  return values;
686
698
  }
687
699
  readdir;
688
700
  export async function link(targetPath, linkPath) {
689
701
  targetPath = normalizePath(targetPath);
690
- if (!(await stat(dirname(targetPath))).hasAccess(constants.R_OK)) {
691
- throw ErrnoError.With('EACCES', dirname(targetPath), 'link');
692
- }
693
702
  linkPath = normalizePath(linkPath);
694
- if (!(await stat(dirname(linkPath))).hasAccess(constants.W_OK)) {
695
- throw ErrnoError.With('EACCES', dirname(linkPath), 'link');
696
- }
697
703
  const { fs, path } = resolveMount(targetPath);
698
704
  const link = resolveMount(linkPath);
699
705
  if (fs != link.fs) {
700
706
  throw ErrnoError.With('EXDEV', linkPath, 'link');
701
707
  }
702
708
  try {
703
- if (!(await fs.stat(path)).hasAccess(constants.W_OK)) {
709
+ if (config.checkAccess && !(await fs.stat(dirname(targetPath))).hasAccess(constants.R_OK)) {
710
+ throw ErrnoError.With('EACCES', dirname(path), 'link');
711
+ }
712
+ if (config.checkAccess && !(await stat(dirname(linkPath))).hasAccess(constants.W_OK)) {
713
+ throw ErrnoError.With('EACCES', dirname(linkPath), 'link');
714
+ }
715
+ if (config.checkAccess && !(await fs.stat(path)).hasAccess(constants.W_OK | constants.R_OK)) {
704
716
  throw ErrnoError.With('EACCES', path, 'link');
705
717
  }
706
718
  return await fs.link(path, link.path);
@@ -907,6 +919,8 @@ export function watch(filename, options = {}) {
907
919
  }
908
920
  watch;
909
921
  export async function access(path, mode = constants.F_OK) {
922
+ if (!config.checkAccess)
923
+ return;
910
924
  const stats = await stat(path);
911
925
  if (!stats.hasAccess(mode)) {
912
926
  throw new ErrnoError(Errno.EACCES);
@@ -919,33 +933,39 @@ access;
919
933
  */
920
934
  export async function rm(path, options) {
921
935
  path = normalizePath(path);
922
- const stats = await stat(path).catch((error) => {
923
- if (error.code != 'ENOENT' || !options?.force)
924
- throw error;
925
- });
936
+ const stats = cache.getStats(path) ||
937
+ (await stat(path).catch((error) => {
938
+ if (error.code != 'ENOENT' || !options?.force)
939
+ throw error;
940
+ }));
926
941
  if (!stats) {
927
942
  return;
928
943
  }
944
+ cache.setStats(path, stats);
929
945
  switch (stats.mode & constants.S_IFMT) {
930
946
  case constants.S_IFDIR:
931
947
  if (options?.recursive) {
932
- for (const entry of await readdir(path)) {
933
- await rm(join(path, entry), options);
948
+ for (const entry of await readdir(path, { _isIndirect: true })) {
949
+ await rm(join(path, entry), { ...options, _isIndirect: true });
934
950
  }
935
951
  }
936
952
  await rmdir(path);
937
- return;
953
+ break;
938
954
  case constants.S_IFREG:
939
955
  case constants.S_IFLNK:
940
956
  await unlink(path);
941
- return;
957
+ break;
942
958
  case constants.S_IFBLK:
943
959
  case constants.S_IFCHR:
944
960
  case constants.S_IFIFO:
945
961
  case constants.S_IFSOCK:
946
962
  default:
963
+ cache.clearStats();
947
964
  throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
948
965
  }
966
+ if (!options?._isIndirect) {
967
+ cache.clearStats();
968
+ }
949
969
  }
950
970
  rm;
951
971
  export async function mkdtemp(prefix, options) {
@@ -1005,18 +1025,21 @@ export async function cp(source, destination, opts) {
1005
1025
  throw new ErrnoError(Errno.EEXIST, 'Destination file or directory already exists.', destination, 'cp');
1006
1026
  }
1007
1027
  switch (srcStats.mode & constants.S_IFMT) {
1008
- case constants.S_IFDIR:
1028
+ case constants.S_IFDIR: {
1009
1029
  if (!opts?.recursive) {
1010
1030
  throw new ErrnoError(Errno.EISDIR, source + ' is a directory (not copied)', source, 'cp');
1011
1031
  }
1012
- await mkdir(destination, { recursive: true }); // Ensure the destination directory exists
1013
- for (const dirent of await readdir(source, { withFileTypes: true })) {
1032
+ const [entries] = await Promise.all([readdir(source, { withFileTypes: true }), mkdir(destination, { recursive: true })] // Ensure the destination directory exists
1033
+ );
1034
+ const _cp = async (dirent) => {
1014
1035
  if (opts.filter && !opts.filter(join(source, dirent.name), join(destination, dirent.name))) {
1015
- continue; // Skip if the filter returns false
1036
+ return; // Skip if the filter returns false
1016
1037
  }
1017
1038
  await cp(join(source, dirent.name), join(destination, dirent.name), opts);
1018
- }
1039
+ };
1040
+ await Promise.all(entries.map(_cp));
1019
1041
  break;
1042
+ }
1020
1043
  case constants.S_IFREG:
1021
1044
  case constants.S_IFLNK:
1022
1045
  await copyFile(source, destination);
@@ -43,3 +43,25 @@ export declare function mountObject(mounts: MountObject): void;
43
43
  * @hidden
44
44
  */
45
45
  export declare function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T extends true ? BigIntStatsFs : StatsFs;
46
+ export declare const config: {
47
+ /**
48
+ * Whether to perform access checks
49
+ */
50
+ checkAccess: boolean;
51
+ };
52
+ /**
53
+ * Options used for caching, among other things.
54
+ * @internal *UNSTABLE*
55
+ */
56
+ export interface InternalOptions {
57
+ /**
58
+ * If true, then this readdir was called from another function.
59
+ * In this case, don't clear the cache when done.
60
+ * @internal *UNSTABLE*
61
+ */
62
+ _isIndirect?: boolean;
63
+ }
64
+ export interface ReaddirOptions extends InternalOptions {
65
+ withFileTypes?: boolean;
66
+ recursive?: boolean;
67
+ }
@@ -115,3 +115,9 @@ export function _statfs(fs, bigint) {
115
115
  blocks: (bigint ? BigInt : Number)(md.totalSpace / bs),
116
116
  };
117
117
  }
118
+ export const config = {
119
+ /**
120
+ * Whether to perform access checks
121
+ */
122
+ checkAccess: true,
123
+ };
@@ -3,6 +3,7 @@ import type * as fs from 'node:fs';
3
3
  import type { FileContents } from '../filesystem.js';
4
4
  import { BigIntStats, type Stats } from '../stats.js';
5
5
  import { Dir, Dirent } from './dir.js';
6
+ import { type InternalOptions, type ReaddirOptions } from './shared.js';
6
7
  export declare function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void;
7
8
  /**
8
9
  * Test whether or not `path` exists by checking with the file system.
@@ -110,29 +111,19 @@ export declare function mkdirSync(path: fs.PathLike, options?: fs.Mode | (fs.Mak
110
111
  recursive?: false;
111
112
  }) | null): void;
112
113
  export declare function mkdirSync(path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): string | undefined;
113
- export declare function readdirSync(path: fs.PathLike, options?: {
114
- recursive?: boolean;
115
- encoding?: BufferEncoding | null;
114
+ export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
116
115
  withFileTypes?: false;
117
- } | BufferEncoding | null): string[];
118
- export declare function readdirSync(path: fs.PathLike, options: {
119
- recursive?: boolean;
120
- encoding: 'buffer';
116
+ }) | BufferEncoding | null): string[];
117
+ export declare function readdirSync(path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & {
121
118
  withFileTypes?: false;
122
- } | 'buffer'): Buffer[];
123
- export declare function readdirSync(path: fs.PathLike, options: {
124
- recursive?: boolean;
125
- withFileTypes: true;
126
- }): Dirent[];
127
- export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & {
119
+ }): Buffer[];
120
+ export declare function readdirSync(path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
128
121
  withFileTypes?: false;
129
- recursive?: boolean;
130
122
  }) | BufferEncoding | null): string[] | Buffer[];
131
- export declare function readdirSync(path: fs.PathLike, options?: {
132
- withFileTypes?: boolean;
133
- recursive?: boolean;
134
- encoding?: BufferEncoding | 'buffer' | null;
135
- } | BufferEncoding | 'buffer' | null): string[] | Dirent[] | Buffer[];
123
+ export declare function readdirSync(path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & {
124
+ withFileTypes: true;
125
+ }): Dirent[];
126
+ export declare function readdirSync(path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null): string[] | Dirent[] | Buffer[];
136
127
  export declare function linkSync(targetPath: fs.PathLike, linkPath: fs.PathLike): void;
137
128
  /**
138
129
  * Synchronous `symlink`.
@@ -163,7 +154,7 @@ export declare function accessSync(path: fs.PathLike, mode?: number): void;
163
154
  * Synchronous `rm`. Removes files or directories (recursively).
164
155
  * @param path The path to the file or directory to remove.
165
156
  */
166
- export declare function rmSync(path: fs.PathLike, options?: fs.RmOptions): void;
157
+ export declare function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptions): void;
167
158
  /**
168
159
  * Synchronous `mkdtemp`. Creates a unique temporary directory.
169
160
  * @param prefix The directory prefix.