@zenfs/core 1.2.0 → 1.2.2

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 (52) hide show
  1. package/dist/config.d.ts +13 -4
  2. package/dist/config.js +2 -1
  3. package/dist/emulation/cache.d.ts +14 -2
  4. package/dist/emulation/cache.js +25 -0
  5. package/dist/emulation/config.d.ts +10 -0
  6. package/dist/emulation/config.js +10 -0
  7. package/dist/emulation/promises.js +24 -13
  8. package/dist/emulation/shared.d.ts +0 -6
  9. package/dist/emulation/shared.js +0 -6
  10. package/dist/emulation/sync.js +13 -12
  11. package/dist/file.d.ts +1 -1
  12. package/dist/file.js +6 -3
  13. package/package.json +1 -1
  14. package/readme.md +1 -1
  15. package/scripts/test.js +5 -0
  16. package/src/config.ts +17 -5
  17. package/src/emulation/cache.ts +32 -3
  18. package/src/emulation/config.ts +11 -0
  19. package/src/emulation/promises.ts +25 -13
  20. package/src/emulation/shared.ts +0 -7
  21. package/src/emulation/sync.ts +13 -12
  22. package/src/file.ts +4 -3
  23. package/tests/common.ts +1 -11
  24. package/tests/fs/appendFile.test.ts +3 -3
  25. package/tests/fs/dir.test.ts +7 -7
  26. package/tests/fs/directory.test.ts +8 -8
  27. package/tests/fs/errors.test.ts +1 -1
  28. package/tests/fs/links.test.ts +4 -4
  29. package/tests/fs/open.test.ts +3 -3
  30. package/tests/fs/permissions.test.ts +5 -5
  31. package/tests/fs/read.test.ts +5 -5
  32. package/tests/fs/readFile.test.ts +2 -2
  33. package/tests/fs/rename.test.ts +5 -5
  34. package/tests/fs/stat.test.ts +1 -1
  35. package/tests/fs/streams.test.ts +19 -19
  36. package/tests/fs/times.test.ts +6 -6
  37. package/tests/fs/truncate.test.ts +1 -1
  38. package/tests/fs/watch.test.ts +10 -10
  39. package/tests/fs/write.test.ts +5 -5
  40. package/tests/fs/writeFile.test.ts +5 -5
  41. package/tests/handle.test.ts +2 -2
  42. package/tests/mutex.test.ts +1 -1
  43. package/tests/port/channel.test.ts +3 -3
  44. package/tests/port/config.test.ts +4 -5
  45. package/tests/port/config.worker.js +5 -0
  46. package/tests/port/remote.test.ts +4 -5
  47. package/tests/port/remote.worker.js +5 -0
  48. package/tests/port/timeout.test.ts +4 -4
  49. package/tests/setup/common.ts +1 -1
  50. package/tests/setup/cow+fetch.ts +1 -1
  51. package/tests/port/config.worker.ts +0 -5
  52. package/tests/port/remote.worker.ts +0 -5
package/dist/config.d.ts CHANGED
@@ -34,24 +34,33 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
34
34
  gid: number;
35
35
  /**
36
36
  * Whether to automatically add normal Linux devices
37
- * @default false
38
37
  * @experimental
38
+ * @default false
39
39
  */
40
40
  addDevices: boolean;
41
41
  /**
42
42
  * If true, enables caching stats for certain operations.
43
43
  * This should reduce the number of stat calls performed.
44
- * @default false
45
44
  * @experimental
45
+ * @default false
46
46
  */
47
47
  cacheStats: boolean;
48
48
  /**
49
49
  * If true, disables *all* permissions checking.
50
- * This can increase performance
51
- * @default false
50
+ *
51
+ * This can increase performance.
52
52
  * @experimental
53
+ * @default false
53
54
  */
54
55
  disableAccessChecks: boolean;
56
+ /**
57
+ * If true, disables `read` and `readSync` from immediately syncing the updated atime to the file system.
58
+ *
59
+ * This can increase performance.
60
+ * @experimental
61
+ * @default false
62
+ */
63
+ disableSyncOnRead: boolean;
55
64
  }
56
65
  /**
57
66
  * Configures ZenFS with single mount point /
package/dist/config.js CHANGED
@@ -3,7 +3,7 @@ import { credentials } from './credentials.js';
3
3
  import { DeviceFS, fullDevice, nullDevice, randomDevice, zeroDevice } from './devices.js';
4
4
  import * as cache from './emulation/cache.js';
5
5
  import * as fs from './emulation/index.js';
6
- import { config } from './emulation/shared.js';
6
+ import { config } from './emulation/config.js';
7
7
  import { Errno, ErrnoError } from './error.js';
8
8
  import { FileSystem } from './filesystem.js';
9
9
  function isMountConfig(arg) {
@@ -70,6 +70,7 @@ export async function configure(configuration) {
70
70
  Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
71
71
  cache.setEnabled(configuration.cacheStats ?? false);
72
72
  config.checkAccess = !configuration.disableAccessChecks;
73
+ config.syncOnRead = !configuration.disableSyncOnRead;
73
74
  if (configuration.addDevices) {
74
75
  const devfs = new DeviceFS();
75
76
  devfs.createDevice('/null', nullDevice);
@@ -10,11 +10,23 @@ export declare function setEnabled(value: boolean): void;
10
10
  /**
11
11
  * Gets stats from the cache, if they exist and the cache is enabled.
12
12
  */
13
- export declare function getStats(path: string): Stats | undefined;
13
+ export declare function getStatsSync(path: string): Stats | undefined;
14
14
  /**
15
15
  * Adds stats if the cache is enabled
16
16
  */
17
- export declare function setStats(path: string, value: Stats): void;
17
+ export declare function setStatsSync(path: string, value: Stats): void;
18
+ /**
19
+ * Clears the cache if it is enabled
20
+ */
21
+ export declare function clearStatsSync(): void;
22
+ /**
23
+ * Gets stats from the cache, if they exist and the cache is enabled.
24
+ */
25
+ export declare function getStats(path: string): Promise<Stats | undefined> | undefined;
26
+ /**
27
+ * Adds stats if the cache is enabled
28
+ */
29
+ export declare function setStats(path: string, value: Promise<Stats | undefined>): void;
18
30
  /**
19
31
  * Clears the cache if it is enabled
20
32
  */
@@ -9,6 +9,31 @@ export let isEnabled = false;
9
9
  export function setEnabled(value) {
10
10
  isEnabled = value;
11
11
  }
12
+ const statsSync = new Map();
13
+ /**
14
+ * Gets stats from the cache, if they exist and the cache is enabled.
15
+ */
16
+ export function getStatsSync(path) {
17
+ if (!isEnabled)
18
+ return;
19
+ return statsSync.get(path);
20
+ }
21
+ /**
22
+ * Adds stats if the cache is enabled
23
+ */
24
+ export function setStatsSync(path, value) {
25
+ if (!isEnabled)
26
+ return;
27
+ statsSync.set(path, value);
28
+ }
29
+ /**
30
+ * Clears the cache if it is enabled
31
+ */
32
+ export function clearStatsSync() {
33
+ if (!isEnabled)
34
+ return;
35
+ statsSync.clear();
36
+ }
12
37
  const stats = new Map();
13
38
  /**
14
39
  * Gets stats from the cache, if they exist and the cache is enabled.
@@ -0,0 +1,10 @@
1
+ export declare const config: {
2
+ /**
3
+ * Whether to perform access checks
4
+ */
5
+ checkAccess: boolean;
6
+ /**
7
+ * Whether to sync atime updates immediately when reading from a file
8
+ */
9
+ syncOnRead: boolean;
10
+ };
@@ -0,0 +1,10 @@
1
+ export const config = {
2
+ /**
3
+ * Whether to perform access checks
4
+ */
5
+ checkAccess: true,
6
+ /**
7
+ * Whether to sync atime updates immediately when reading from a file
8
+ */
9
+ syncOnRead: true,
10
+ };
@@ -56,7 +56,8 @@ import * as cache from './cache.js';
56
56
  import * as constants from './constants.js';
57
57
  import { Dir, Dirent } from './dir.js';
58
58
  import { dirname, join, parse } from './path.js';
59
- import { _statfs, config, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
59
+ import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
60
+ import { config } from './config.js';
60
61
  import { ReadStream, WriteStream } from './streams.js';
61
62
  import { FSWatcher, emitChange } from './watchers.js';
62
63
  export * as constants from './constants.js';
@@ -434,7 +435,7 @@ export async function unlink(path) {
434
435
  path = normalizePath(path);
435
436
  const { fs, path: resolved } = resolveMount(path);
436
437
  try {
437
- if (config.checkAccess && !(cache.getStats(path) || (await fs.stat(resolved))).hasAccess(constants.W_OK)) {
438
+ if (config.checkAccess && !(await (cache.getStats(path) || fs.stat(resolved))).hasAccess(constants.W_OK)) {
438
439
  throw ErrnoError.With('EACCES', resolved, 'unlink');
439
440
  }
440
441
  await fs.unlink(resolved);
@@ -583,7 +584,10 @@ export async function rmdir(path) {
583
584
  path = await realpath(path);
584
585
  const { fs, path: resolved } = resolveMount(path);
585
586
  try {
586
- const stats = cache.getStats(path) || (await fs.stat(resolved));
587
+ const stats = await (cache.getStats(path) || fs.stat(resolved));
588
+ if (!stats) {
589
+ throw ErrnoError.With('ENOENT', path, 'readdir');
590
+ }
587
591
  if (!stats.isDirectory()) {
588
592
  throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
589
593
  }
@@ -639,8 +643,12 @@ export async function readdir(path, options) {
639
643
  throw fixError(e, { [resolved]: path });
640
644
  };
641
645
  const { fs, path: resolved } = resolveMount(path);
642
- const stats = cache.getStats(path) || (await fs.stat(resolved).catch(handleError));
643
- cache.setStats(path, stats);
646
+ const _stats = cache.getStats(path) || fs.stat(resolved).catch(handleError);
647
+ cache.setStats(path, _stats);
648
+ const stats = await _stats;
649
+ if (!stats) {
650
+ throw ErrnoError.With('ENOENT', path, 'readdir');
651
+ }
644
652
  if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
645
653
  throw ErrnoError.With('EACCES', path, 'readdir');
646
654
  }
@@ -662,8 +670,9 @@ export async function readdir(path, options) {
662
670
  const addEntry = async (entry) => {
663
671
  let entryStats;
664
672
  if (options?.recursive || options?.withFileTypes) {
665
- entryStats = cache.getStats(join(path, entry)) || (await fs.stat(join(resolved, entry)).catch(handleError));
666
- cache.setStats(join(path, entry), entryStats);
673
+ const _entryStats = cache.getStats(join(path, entry)) || fs.stat(join(resolved, entry)).catch(handleError);
674
+ cache.setStats(join(path, entry), _entryStats);
675
+ entryStats = await _entryStats;
667
676
  }
668
677
  if (options?.withFileTypes) {
669
678
  values.push(new Dirent(entry, entryStats));
@@ -933,15 +942,17 @@ access;
933
942
  */
934
943
  export async function rm(path, options) {
935
944
  path = normalizePath(path);
936
- const stats = cache.getStats(path) ||
937
- (await stat(path).catch((error) => {
938
- if (error.code != 'ENOENT' || !options?.force)
939
- throw error;
940
- }));
945
+ const _stats = cache.getStats(path) ||
946
+ stat(path).catch((error) => {
947
+ if (error.code == 'ENOENT' && options?.force)
948
+ return undefined;
949
+ throw error;
950
+ });
951
+ cache.setStats(path, _stats);
952
+ const stats = await _stats;
941
953
  if (!stats) {
942
954
  return;
943
955
  }
944
- cache.setStats(path, stats);
945
956
  switch (stats.mode & constants.S_IFMT) {
946
957
  case constants.S_IFDIR:
947
958
  if (options?.recursive) {
@@ -43,12 +43,6 @@ 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
46
  /**
53
47
  * Options used for caching, among other things.
54
48
  * @internal *UNSTABLE*
@@ -115,9 +115,3 @@ 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
- };
@@ -53,7 +53,8 @@ import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTi
53
53
  import * as constants from './constants.js';
54
54
  import { Dir, Dirent } from './dir.js';
55
55
  import { dirname, join, parse } from './path.js';
56
- import { _statfs, config, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
56
+ import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
57
+ import { config } from './config.js';
57
58
  import { emitChange } from './watchers.js';
58
59
  import * as cache from './cache.js';
59
60
  export function renameSync(oldPath, newPath) {
@@ -146,7 +147,7 @@ export function unlinkSync(path) {
146
147
  path = normalizePath(path);
147
148
  const { fs, path: resolved } = resolveMount(path);
148
149
  try {
149
- if (config.checkAccess && !(cache.getStats(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
150
+ if (config.checkAccess && !(cache.getStatsSync(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
150
151
  throw ErrnoError.With('EACCES', resolved, 'unlink');
151
152
  }
152
153
  fs.unlinkSync(resolved);
@@ -393,7 +394,7 @@ export function rmdirSync(path) {
393
394
  path = normalizePath(path);
394
395
  const { fs, path: resolved } = resolveMount(realpathSync(path));
395
396
  try {
396
- const stats = cache.getStats(path) || fs.statSync(resolved);
397
+ const stats = cache.getStatsSync(path) || fs.statSync(resolved);
397
398
  if (!stats.isDirectory()) {
398
399
  throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
399
400
  }
@@ -446,8 +447,8 @@ export function readdirSync(path, options) {
446
447
  const { fs, path: resolved } = resolveMount(realpathSync(path));
447
448
  let entries;
448
449
  try {
449
- const stats = cache.getStats(path) || fs.statSync(resolved);
450
- cache.setStats(path, stats);
450
+ const stats = cache.getStatsSync(path) || fs.statSync(resolved);
451
+ cache.setStatsSync(path, stats);
451
452
  if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
452
453
  throw ErrnoError.With('EACCES', resolved, 'readdir');
453
454
  }
@@ -473,8 +474,8 @@ export function readdirSync(path, options) {
473
474
  // Iterate over entries and handle recursive case if needed
474
475
  const values = [];
475
476
  for (const entry of entries) {
476
- const entryStat = cache.getStats(join(path, entry)) || fs.statSync(join(resolved, entry));
477
- cache.setStats(join(path, entry), entryStat);
477
+ const entryStat = cache.getStatsSync(join(path, entry)) || fs.statSync(join(resolved, entry));
478
+ cache.setStatsSync(join(path, entry), entryStat);
478
479
  if (options?.withFileTypes) {
479
480
  values.push(new Dirent(entry, entryStat));
480
481
  }
@@ -500,7 +501,7 @@ export function readdirSync(path, options) {
500
501
  }
501
502
  }
502
503
  if (!options?._isIndirect) {
503
- cache.clearStats();
504
+ cache.clearStatsSync();
504
505
  }
505
506
  return values;
506
507
  }
@@ -637,7 +638,7 @@ export function rmSync(path, options) {
637
638
  path = normalizePath(path);
638
639
  let stats;
639
640
  try {
640
- stats = cache.getStats(path) || statSync(path);
641
+ stats = cache.getStatsSync(path) || statSync(path);
641
642
  }
642
643
  catch (error) {
643
644
  if (error.code != 'ENOENT' || !options?.force)
@@ -646,7 +647,7 @@ export function rmSync(path, options) {
646
647
  if (!stats) {
647
648
  return;
648
649
  }
649
- cache.setStats(path, stats);
650
+ cache.setStatsSync(path, stats);
650
651
  switch (stats.mode & constants.S_IFMT) {
651
652
  case constants.S_IFDIR:
652
653
  if (options?.recursive) {
@@ -665,11 +666,11 @@ export function rmSync(path, options) {
665
666
  case constants.S_IFIFO:
666
667
  case constants.S_IFSOCK:
667
668
  default:
668
- cache.clearStats();
669
+ cache.clearStatsSync();
669
670
  throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
670
671
  }
671
672
  if (!options?._isIndirect) {
672
- cache.clearStats();
673
+ cache.clearStatsSync();
673
674
  }
674
675
  }
675
676
  rmSync;
package/dist/file.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { FileReadResult } from 'node:fs/promises';
2
2
  import type { FileSystem } from './filesystem.js';
3
- import { Stats, type FileType } from './stats.js';
4
3
  import './polyfills.js';
4
+ import { Stats, type FileType } from './stats.js';
5
5
  /**
6
6
  Typescript does not include a type declaration for resizable array buffers.
7
7
  It has been standardized into ECMAScript though
package/dist/file.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT, size_max } from './emulation/constants.js';
2
+ import { config } from './emulation/config.js';
2
3
  import { Errno, ErrnoError } from './error.js';
3
- import { Stats } from './stats.js';
4
4
  import './polyfills.js';
5
+ import { Stats } from './stats.js';
5
6
  const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+'];
6
7
  export function parseFlag(flag) {
7
8
  if (typeof flag === 'number') {
@@ -383,7 +384,8 @@ export class PreloadFile extends File {
383
384
  */
384
385
  async read(buffer, offset, length, position) {
385
386
  const bytesRead = this._read(buffer, offset, length, position);
386
- await this.sync();
387
+ if (config.syncOnRead)
388
+ await this.sync();
387
389
  return { bytesRead, buffer };
388
390
  }
389
391
  /**
@@ -397,7 +399,8 @@ export class PreloadFile extends File {
397
399
  */
398
400
  readSync(buffer, offset, length, position) {
399
401
  const bytesRead = this._read(buffer, offset, length, position);
400
- this.statSync();
402
+ if (config.syncOnRead)
403
+ this.syncSync();
401
404
  return bytesRead;
402
405
  }
403
406
  async chmod(mode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
package/readme.md CHANGED
@@ -32,7 +32,7 @@ npm install @zenfs/core
32
32
  ```js
33
33
  import { fs } from '@zenfs/core'; // You can also use the default export
34
34
 
35
- fs.writeFileSync('/test.txt', 'You can do this in anywhere (including browsers)!');
35
+ fs.writeFileSync('/test.txt', 'You can do this anywhere, including browsers!');
36
36
 
37
37
  const contents = fs.readFileSync('/test.txt', 'utf-8');
38
38
  console.log(contents);
package/scripts/test.js CHANGED
@@ -31,6 +31,11 @@ options:
31
31
 
32
32
  if (options.verbose) console.debug('Forcing tests to exit (--test-force-exit)');
33
33
 
34
+ if (!existsSync(join(import.meta.dirname, '../dist'))) {
35
+ console.log('ERROR: Missing build. If you are using an installed package, please submit a bug report.');
36
+ process.exit(1);
37
+ }
38
+
34
39
  const testsGlob = join(import.meta.dirname, `../tests/fs/${options.test || '*'}.test.ts`);
35
40
 
36
41
  for (const setupFile of positionals) {
package/src/config.ts CHANGED
@@ -5,7 +5,8 @@ import { DeviceFS, fullDevice, nullDevice, randomDevice, zeroDevice } from './de
5
5
  import * as cache from './emulation/cache.js';
6
6
  import * as fs from './emulation/index.js';
7
7
  import type { AbsolutePath } from './emulation/path.js';
8
- import { config, type MountObject } from './emulation/shared.js';
8
+ import { type MountObject } from './emulation/shared.js';
9
+ import { config } from './emulation/config.js';
9
10
  import { Errno, ErrnoError } from './error.js';
10
11
  import { FileSystem } from './filesystem.js';
11
12
 
@@ -95,26 +96,36 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
95
96
 
96
97
  /**
97
98
  * Whether to automatically add normal Linux devices
98
- * @default false
99
99
  * @experimental
100
+ * @default false
100
101
  */
101
102
  addDevices: boolean;
102
103
 
103
104
  /**
104
105
  * If true, enables caching stats for certain operations.
105
106
  * This should reduce the number of stat calls performed.
106
- * @default false
107
107
  * @experimental
108
+ * @default false
108
109
  */
109
110
  cacheStats: boolean;
110
111
 
111
112
  /**
112
113
  * If true, disables *all* permissions checking.
113
- * This can increase performance
114
- * @default false
114
+ *
115
+ * This can increase performance.
115
116
  * @experimental
117
+ * @default false
116
118
  */
117
119
  disableAccessChecks: boolean;
120
+
121
+ /**
122
+ * If true, disables `read` and `readSync` from immediately syncing the updated atime to the file system.
123
+ *
124
+ * This can increase performance.
125
+ * @experimental
126
+ * @default false
127
+ */
128
+ disableSyncOnRead: boolean;
118
129
  }
119
130
 
120
131
  /**
@@ -142,6 +153,7 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
142
153
 
143
154
  cache.setEnabled(configuration.cacheStats ?? false);
144
155
  config.checkAccess = !configuration.disableAccessChecks;
156
+ config.syncOnRead = !configuration.disableSyncOnRead;
145
157
 
146
158
  if (configuration.addDevices) {
147
159
  const devfs = new DeviceFS();
@@ -14,12 +14,41 @@ export function setEnabled(value: boolean): void {
14
14
  isEnabled = value;
15
15
  }
16
16
 
17
- const stats = new Map<string, Stats>();
17
+ const statsSync = new Map<string, Stats>();
18
18
 
19
19
  /**
20
20
  * Gets stats from the cache, if they exist and the cache is enabled.
21
21
  */
22
- export function getStats(path: string): Stats | undefined {
22
+ export function getStatsSync(path: string): Stats | undefined {
23
+ if (!isEnabled) return;
24
+
25
+ return statsSync.get(path);
26
+ }
27
+
28
+ /**
29
+ * Adds stats if the cache is enabled
30
+ */
31
+ export function setStatsSync(path: string, value: Stats): void {
32
+ if (!isEnabled) return;
33
+
34
+ statsSync.set(path, value);
35
+ }
36
+
37
+ /**
38
+ * Clears the cache if it is enabled
39
+ */
40
+ export function clearStatsSync(): void {
41
+ if (!isEnabled) return;
42
+
43
+ statsSync.clear();
44
+ }
45
+
46
+ const stats = new Map<string, Promise<Stats | undefined>>();
47
+
48
+ /**
49
+ * Gets stats from the cache, if they exist and the cache is enabled.
50
+ */
51
+ export function getStats(path: string): Promise<Stats | undefined> | undefined {
23
52
  if (!isEnabled) return;
24
53
 
25
54
  return stats.get(path);
@@ -28,7 +57,7 @@ export function getStats(path: string): Stats | undefined {
28
57
  /**
29
58
  * Adds stats if the cache is enabled
30
59
  */
31
- export function setStats(path: string, value: Stats): void {
60
+ export function setStats(path: string, value: Promise<Stats | undefined>): void {
32
61
  if (!isEnabled) return;
33
62
 
34
63
  stats.set(path, value);
@@ -0,0 +1,11 @@
1
+ export const config = {
2
+ /**
3
+ * Whether to perform access checks
4
+ */
5
+ checkAccess: true,
6
+
7
+ /**
8
+ * Whether to sync atime updates immediately when reading from a file
9
+ */
10
+ syncOnRead: true,
11
+ };
@@ -16,7 +16,8 @@ import * as cache from './cache.js';
16
16
  import * as constants from './constants.js';
17
17
  import { Dir, Dirent } from './dir.js';
18
18
  import { dirname, join, parse } from './path.js';
19
- import { _statfs, config, fd2file, fdMap, file2fd, fixError, mounts, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
19
+ import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
20
+ import { config } from './config.js';
20
21
  import { ReadStream, WriteStream } from './streams.js';
21
22
  import { FSWatcher, emitChange } from './watchers.js';
22
23
  export * as constants from './constants.js';
@@ -470,7 +471,7 @@ export async function unlink(path: fs.PathLike): Promise<void> {
470
471
  path = normalizePath(path);
471
472
  const { fs, path: resolved } = resolveMount(path);
472
473
  try {
473
- if (config.checkAccess && !(cache.getStats(path) || (await fs.stat(resolved))).hasAccess(constants.W_OK)) {
474
+ if (config.checkAccess && !(await (cache.getStats(path) || fs.stat(resolved)))!.hasAccess(constants.W_OK)) {
474
475
  throw ErrnoError.With('EACCES', resolved, 'unlink');
475
476
  }
476
477
  await fs.unlink(resolved);
@@ -623,7 +624,10 @@ export async function rmdir(path: fs.PathLike): Promise<void> {
623
624
  path = await realpath(path);
624
625
  const { fs, path: resolved } = resolveMount(path);
625
626
  try {
626
- const stats = cache.getStats(path) || (await fs.stat(resolved));
627
+ const stats = await (cache.getStats(path) || fs.stat(resolved));
628
+ if (!stats) {
629
+ throw ErrnoError.With('ENOENT', path, 'readdir');
630
+ }
627
631
  if (!stats.isDirectory()) {
628
632
  throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
629
633
  }
@@ -715,8 +719,13 @@ export async function readdir(
715
719
 
716
720
  const { fs, path: resolved } = resolveMount(path);
717
721
 
718
- const stats = cache.getStats(path) || (await fs.stat(resolved).catch(handleError));
719
- cache.setStats(path, stats);
722
+ const _stats = cache.getStats(path) || fs.stat(resolved).catch(handleError);
723
+ cache.setStats(path, _stats);
724
+ const stats = await _stats;
725
+
726
+ if (!stats) {
727
+ throw ErrnoError.With('ENOENT', path, 'readdir');
728
+ }
720
729
 
721
730
  if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
722
731
  throw ErrnoError.With('EACCES', path, 'readdir');
@@ -743,8 +752,9 @@ export async function readdir(
743
752
  const addEntry = async (entry: string) => {
744
753
  let entryStats: Stats | undefined;
745
754
  if (options?.recursive || options?.withFileTypes) {
746
- entryStats = cache.getStats(join(path, entry)) || (await fs.stat(join(resolved, entry)).catch(handleError));
747
- cache.setStats(join(path, entry), entryStats);
755
+ const _entryStats = cache.getStats(join(path, entry)) || fs.stat(join(resolved, entry)).catch(handleError);
756
+ cache.setStats(join(path, entry), _entryStats);
757
+ entryStats = await _entryStats;
748
758
  }
749
759
  if (options?.withFileTypes) {
750
760
  values.push(new Dirent(entry, entryStats!));
@@ -967,18 +977,20 @@ access satisfies typeof promises.access;
967
977
  export async function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOptions) {
968
978
  path = normalizePath(path);
969
979
 
970
- const stats =
980
+ const _stats =
971
981
  cache.getStats(path) ||
972
- (await stat(path).catch((error: ErrnoError) => {
973
- if (error.code != 'ENOENT' || !options?.force) throw error;
974
- }));
982
+ stat(path).catch((error: ErrnoError) => {
983
+ if (error.code == 'ENOENT' && options?.force) return undefined;
984
+ throw error;
985
+ });
986
+
987
+ cache.setStats(path, _stats);
988
+ const stats = await _stats;
975
989
 
976
990
  if (!stats) {
977
991
  return;
978
992
  }
979
993
 
980
- cache.setStats(path, stats);
981
-
982
994
  switch (stats.mode & constants.S_IFMT) {
983
995
  case constants.S_IFDIR:
984
996
  if (options?.recursive) {
@@ -134,13 +134,6 @@ export function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T
134
134
  } as T extends true ? BigIntStatsFs : StatsFs;
135
135
  }
136
136
 
137
- export const config = {
138
- /**
139
- * Whether to perform access checks
140
- */
141
- checkAccess: true,
142
- };
143
-
144
137
  /**
145
138
  * Options used for caching, among other things.
146
139
  * @internal *UNSTABLE*