@zenfs/core 1.2.10 → 1.3.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 (53) hide show
  1. package/dist/backends/fetch.js +1 -1
  2. package/dist/backends/memory.d.ts +1 -2
  3. package/dist/backends/overlay.js +2 -3
  4. package/dist/backends/port/fs.d.ts +2 -15
  5. package/dist/backends/store/fs.d.ts +17 -28
  6. package/dist/backends/store/fs.js +169 -191
  7. package/dist/backends/store/simple.d.ts +20 -21
  8. package/dist/backends/store/simple.js +24 -24
  9. package/dist/backends/store/store.d.ts +22 -23
  10. package/dist/backends/store/store.js +6 -6
  11. package/dist/config.d.ts +8 -0
  12. package/dist/config.js +2 -1
  13. package/dist/devices.d.ts +2 -3
  14. package/dist/emulation/cache.d.ts +40 -31
  15. package/dist/emulation/cache.js +62 -53
  16. package/dist/emulation/index.d.ts +1 -1
  17. package/dist/emulation/index.js +1 -1
  18. package/dist/emulation/promises.js +18 -17
  19. package/dist/emulation/shared.d.ts +6 -0
  20. package/dist/emulation/shared.js +13 -1
  21. package/dist/emulation/sync.js +14 -13
  22. package/dist/file.d.ts +3 -15
  23. package/dist/file.js +6 -18
  24. package/dist/inode.d.ts +4 -13
  25. package/dist/inode.js +20 -27
  26. package/dist/mixins/async.d.ts +15 -10
  27. package/dist/mixins/async.js +3 -1
  28. package/dist/utils.d.ts +5 -7
  29. package/dist/utils.js +11 -20
  30. package/package.json +1 -1
  31. package/src/backends/fetch.ts +1 -1
  32. package/src/backends/memory.ts +1 -2
  33. package/src/backends/overlay.ts +2 -2
  34. package/src/backends/store/fs.ts +187 -220
  35. package/src/backends/store/simple.ts +36 -37
  36. package/src/backends/store/store.ts +25 -26
  37. package/src/config.ts +11 -1
  38. package/src/devices.ts +2 -3
  39. package/src/emulation/cache.ts +68 -60
  40. package/src/emulation/index.ts +1 -1
  41. package/src/emulation/promises.ts +20 -19
  42. package/src/emulation/shared.ts +13 -1
  43. package/src/emulation/sync.ts +14 -13
  44. package/src/file.ts +8 -20
  45. package/src/inode.ts +8 -29
  46. package/src/mixins/async.ts +27 -24
  47. package/src/utils.ts +11 -23
  48. package/tests/fs/dir.test.ts +21 -31
  49. package/tests/fs/directory.test.ts +6 -4
  50. package/tests/fs/links.test.ts +9 -2
  51. package/tests/fs/permissions.test.ts +2 -2
  52. package/tests/fs/times.test.ts +22 -11
  53. package/tests/setup/cow+fetch.ts +4 -2
@@ -1,14 +1,13 @@
1
- import type { Ino } from '../../inode.js';
2
1
  import { SyncTransaction, type Store } from './store.js';
3
2
 
4
3
  /**
5
4
  * An interface for simple synchronous stores that don't have special support for transactions and such.
6
5
  */
7
6
  export interface SimpleSyncStore extends Store {
8
- keys(): Iterable<Ino>;
9
- get(ino: Ino): Uint8Array | undefined;
10
- set(ino: Ino, data: Uint8Array): void;
11
- delete(ino: Ino): void;
7
+ keys(): Iterable<bigint>;
8
+ get(id: bigint): Uint8Array | undefined;
9
+ set(id: bigint, data: Uint8Array): void;
10
+ delete(id: bigint): void;
12
11
  }
13
12
 
14
13
  /**
@@ -18,33 +17,33 @@ export interface SimpleSyncStore extends Store {
18
17
  export abstract class SimpleAsyncStore implements SimpleSyncStore {
19
18
  public abstract name: string;
20
19
 
21
- protected cache: Map<Ino, Uint8Array> = new Map();
20
+ protected cache: Map<bigint, Uint8Array> = new Map();
22
21
 
23
22
  protected queue: Set<Promise<unknown>> = new Set();
24
23
 
25
- protected abstract entries(): Promise<Iterable<[Ino, Uint8Array]>>;
24
+ protected abstract entries(): Promise<Iterable<[bigint, Uint8Array]>>;
26
25
 
27
- public keys(): Iterable<Ino> {
26
+ public keys(): Iterable<bigint> {
28
27
  return this.cache.keys();
29
28
  }
30
29
 
31
- public get(ino: Ino): Uint8Array | undefined {
32
- return this.cache.get(ino);
30
+ public get(id: bigint): Uint8Array | undefined {
31
+ return this.cache.get(id);
33
32
  }
34
33
 
35
- public set(ino: Ino, data: Uint8Array): void {
36
- this.cache.set(ino, data);
37
- this.queue.add(this._set(ino, data));
34
+ public set(id: bigint, data: Uint8Array): void {
35
+ this.cache.set(id, data);
36
+ this.queue.add(this._set(id, data));
38
37
  }
39
38
 
40
- protected abstract _set(ino: Ino, data: Uint8Array): Promise<void>;
39
+ protected abstract _set(ino: bigint, data: Uint8Array): Promise<void>;
41
40
 
42
- public delete(ino: Ino): void {
43
- this.cache.delete(ino);
44
- this.queue.add(this._delete(ino));
41
+ public delete(id: bigint): void {
42
+ this.cache.delete(id);
43
+ this.queue.add(this._delete(id));
45
44
  }
46
45
 
47
- protected abstract _delete(ino: Ino): Promise<void>;
46
+ protected abstract _delete(ino: bigint): Promise<void>;
48
47
 
49
48
  public clearSync(): void {
50
49
  this.cache.clear();
@@ -79,32 +78,32 @@ export class SimpleTransaction extends SyncTransaction<SimpleSyncStore> {
79
78
  * Stores data in the keys we modify prior to modifying them.
80
79
  * Allows us to roll back commits.
81
80
  */
82
- protected originalData: Map<Ino, Uint8Array | void> = new Map();
81
+ protected originalData: Map<bigint, Uint8Array | void> = new Map();
83
82
  /**
84
83
  * List of keys modified in this transaction, if any.
85
84
  */
86
- protected modifiedKeys: Set<Ino> = new Set();
85
+ protected modifiedKeys: Set<bigint> = new Set();
87
86
 
88
87
  protected declare store: SimpleSyncStore;
89
88
 
90
- public keysSync(): Iterable<Ino> {
89
+ public keysSync(): Iterable<bigint> {
91
90
  return this.store.keys();
92
91
  }
93
92
 
94
- public getSync(ino: Ino): Uint8Array {
95
- const val = this.store.get(ino);
96
- this.stashOldValue(ino, val);
93
+ public getSync(id: bigint): Uint8Array {
94
+ const val = this.store.get(id);
95
+ this.stashOldValue(id, val);
97
96
  return val!;
98
97
  }
99
98
 
100
- public setSync(ino: Ino, data: Uint8Array): void {
101
- this.markModified(ino);
102
- return this.store.set(ino, data);
99
+ public setSync(id: bigint, data: Uint8Array): void {
100
+ this.markModified(id);
101
+ return this.store.set(id, data);
103
102
  }
104
103
 
105
- public removeSync(ino: Ino): void {
106
- this.markModified(ino);
107
- this.store.delete(ino);
104
+ public removeSync(id: bigint): void {
105
+ this.markModified(id);
106
+ this.store.delete(id);
108
107
  }
109
108
 
110
109
  public commitSync(): void {
@@ -135,10 +134,10 @@ export class SimpleTransaction extends SyncTransaction<SimpleSyncStore> {
135
134
  * prevent needless `get` requests if the program modifies the data later
136
135
  * on during the transaction.
137
136
  */
138
- protected stashOldValue(ino: Ino, value?: Uint8Array): void {
137
+ protected stashOldValue(id: bigint, value?: Uint8Array): void {
139
138
  // Keep only the earliest value in the transaction.
140
- if (!this.originalData.has(ino)) {
141
- this.originalData.set(ino, value);
139
+ if (!this.originalData.has(id)) {
140
+ this.originalData.set(id, value);
142
141
  }
143
142
  }
144
143
 
@@ -146,10 +145,10 @@ export class SimpleTransaction extends SyncTransaction<SimpleSyncStore> {
146
145
  * Marks `ino` as modified, and stashes its value if it has not been
147
146
  * stashed already.
148
147
  */
149
- protected markModified(ino: Ino): void {
150
- this.modifiedKeys.add(ino);
151
- if (!this.originalData.has(ino)) {
152
- this.originalData.set(ino, this.store.get(ino));
148
+ protected markModified(id: bigint): void {
149
+ this.modifiedKeys.add(id);
150
+ if (!this.originalData.has(id)) {
151
+ this.originalData.set(id, this.store.get(id));
153
152
  }
154
153
  }
155
154
  }
@@ -1,5 +1,4 @@
1
1
  import { ErrnoError } from '../../error.js';
2
- import type { Ino } from '../../inode.js';
3
2
  import '../../polyfills.js';
4
3
 
5
4
  /**
@@ -46,52 +45,52 @@ export abstract class Transaction<T extends Store = Store> {
46
45
  /**
47
46
  * Gets all of the keys
48
47
  */
49
- public abstract keys(): Promise<Iterable<Ino>>;
48
+ public abstract keys(): Promise<Iterable<bigint>>;
50
49
 
51
50
  /**
52
51
  * Gets all of the keys
53
52
  */
54
- public abstract keysSync(): Iterable<Ino>;
53
+ public abstract keysSync(): Iterable<bigint>;
55
54
 
56
55
  /**
57
- * Retrieves the data at `ino`.
58
- * @param ino The key to look under for data.
56
+ * Retrieves data.
57
+ * @param id The key to look under for data.
59
58
  */
60
- public abstract get(ino: Ino): Promise<Uint8Array>;
59
+ public abstract get(id: bigint): Promise<Uint8Array>;
61
60
 
62
61
  /**
63
- * Retrieves the data at `ino`.
62
+ * Retrieves data.
64
63
  * Throws an error if an error occurs or if the key does not exist.
65
- * @param ino The key to look under for data.
64
+ * @param id The key to look under for data.
66
65
  * @return The data stored under the key, or undefined if not present.
67
66
  */
68
- public abstract getSync(ino: Ino): Uint8Array;
67
+ public abstract getSync(id: bigint): Uint8Array;
69
68
 
70
69
  /**
71
- * Adds the data to the store under `ino`. Overwrites any existing data.
72
- * @param ino The key to add the data under.
70
+ * Adds the data to the store under an id. Overwrites any existing data.
71
+ * @param id The key to add the data under.
73
72
  * @param data The data to add to the store.
74
73
  */
75
- public abstract set(ino: Ino, data: Uint8Array): Promise<void>;
74
+ public abstract set(id: bigint, data: Uint8Array): Promise<void>;
76
75
 
77
76
  /**
78
- * Adds the data to the store under `ino`.
79
- * @param ino The key to add the data under.
77
+ * Adds the data to the store under and id.
78
+ * @param id The key to add the data under.
80
79
  * @param data The data to add to the store.
81
80
  */
82
- public abstract setSync(ino: Ino, data: Uint8Array): void;
81
+ public abstract setSync(id: bigint, data: Uint8Array): void;
83
82
 
84
83
  /**
85
84
  * Deletes the data at `ino`.
86
- * @param ino The key to delete from the store.
85
+ * @param id The key to delete from the store.
87
86
  */
88
- public abstract remove(ino: Ino): Promise<void>;
87
+ public abstract remove(id: bigint): Promise<void>;
89
88
 
90
89
  /**
91
90
  * Deletes the data at `ino`.
92
- * @param ino The key to delete from the store.
91
+ * @param id The key to delete from the store.
93
92
  */
94
- public abstract removeSync(ino: Ino): void;
93
+ public abstract removeSync(id: bigint): void;
95
94
 
96
95
  /**
97
96
  * Commits the transaction.
@@ -135,19 +134,19 @@ export abstract class Transaction<T extends Store = Store> {
135
134
  */
136
135
  export abstract class SyncTransaction<T extends Store = Store> extends Transaction<T> {
137
136
  /* eslint-disable @typescript-eslint/require-await */
138
- public async keys(): Promise<Iterable<Ino>> {
137
+ public async keys(): Promise<Iterable<bigint>> {
139
138
  return this.keysSync();
140
139
  }
141
- public async get(ino: Ino): Promise<Uint8Array> {
142
- return this.getSync(ino);
140
+ public async get(id: bigint): Promise<Uint8Array> {
141
+ return this.getSync(id);
143
142
  }
144
143
 
145
- public async set(ino: bigint, data: Uint8Array): Promise<void> {
146
- return this.setSync(ino, data);
144
+ public async set(id: bigint, data: Uint8Array): Promise<void> {
145
+ return this.setSync(id, data);
147
146
  }
148
147
 
149
- public async remove(ino: Ino): Promise<void> {
150
- return this.removeSync(ino);
148
+ public async remove(id: bigint): Promise<void> {
149
+ return this.removeSync(id);
151
150
  }
152
151
 
153
152
  public async commit(): Promise<void> {
package/src/config.ts CHANGED
@@ -108,6 +108,15 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
108
108
  */
109
109
  cacheStats: boolean;
110
110
 
111
+ /**
112
+ * If true, enables caching realpath output
113
+ *
114
+ * This can increase performance.
115
+ * @experimental
116
+ * @default false
117
+ */
118
+ cachePaths: boolean;
119
+
111
120
  /**
112
121
  * If true, disables *all* permissions checking.
113
122
  *
@@ -181,7 +190,8 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
181
190
 
182
191
  Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
183
192
 
184
- cache.setEnabled(configuration.cacheStats ?? false);
193
+ cache.stats.isEnabled = configuration.cacheStats ?? false;
194
+ cache.paths.isEnabled = configuration.cachePaths ?? false;
185
195
  config.checkAccess = !configuration.disableAccessChecks;
186
196
  config.updateOnRead = !configuration.disableUpdateOnRead;
187
197
  config.syncImmediately = !configuration.onlySyncOnClose;
package/src/devices.ts CHANGED
@@ -11,7 +11,6 @@ import { File } from './file.js';
11
11
  import type { StatsLike } from './stats.js';
12
12
  import { Stats } from './stats.js';
13
13
  import { basename, dirname } from './emulation/path.js';
14
- import type { Ino } from './inode.js';
15
14
 
16
15
  /**
17
16
  * A device
@@ -28,7 +27,7 @@ export interface Device<TData = any> {
28
27
  /**
29
28
  * Which inode the device is assigned
30
29
  */
31
- ino: Ino;
30
+ ino: bigint;
32
31
 
33
32
  /**
34
33
  * Data associated with a device.
@@ -70,7 +69,7 @@ export interface DeviceDriver<TData = any> {
70
69
  * @returns `Device.data`
71
70
  * @experimental
72
71
  */
73
- init?(ino: Ino): {
72
+ init?(ino: bigint): {
74
73
  data?: TData;
75
74
  minor?: number;
76
75
  major?: number;
@@ -3,71 +3,79 @@
3
3
  import type { Stats } from '../stats.js';
4
4
 
5
5
  /**
6
- * Whether the cache is enabled
6
+ * Used for caching data
7
+ * @internal
7
8
  */
8
- export let isEnabled = false;
9
-
10
- /**
11
- * Sets whether the cache is enabled or not
12
- */
13
- export function setEnabled(value: boolean): void {
14
- isEnabled = value;
9
+ export class Cache<T> {
10
+ public isEnabled: boolean = false;
11
+
12
+ protected sync = new Map<string, T>();
13
+
14
+ protected async = new Map<string, Promise<T>>();
15
+
16
+ /**
17
+ * Gets data from the cache, if is exists and the cache is enabled.
18
+ */
19
+ getSync(path: string): T | undefined {
20
+ if (!this.isEnabled) return;
21
+
22
+ return this.sync.get(path);
23
+ }
24
+
25
+ /**
26
+ * Adds data if the cache is enabled
27
+ */
28
+ setSync(path: string, value: T): void {
29
+ if (!this.isEnabled) return;
30
+
31
+ this.sync.set(path, value);
32
+ this.async.set(path, Promise.resolve(value));
33
+ }
34
+
35
+ /**
36
+ * Clears the cache if it is enabled
37
+ */
38
+ clearSync(): void {
39
+ if (!this.isEnabled) return;
40
+
41
+ this.sync.clear();
42
+ }
43
+
44
+ /**
45
+ * Gets data from the cache, if it exists and the cache is enabled.
46
+ */
47
+ get(path: string): Promise<T> | undefined {
48
+ if (!this.isEnabled) return;
49
+
50
+ return this.async.get(path);
51
+ }
52
+
53
+ /**
54
+ * Adds data if the cache is enabled
55
+ */
56
+ set(path: string, value: Promise<T>): void {
57
+ if (!this.isEnabled) return;
58
+
59
+ this.async.set(path, value);
60
+ void value.then(v => this.sync.set(path, v));
61
+ }
62
+
63
+ /**
64
+ * Clears the cache if it is enabled
65
+ */
66
+ clear(): void {
67
+ if (!this.isEnabled) return;
68
+
69
+ this.async.clear();
70
+ }
15
71
  }
16
72
 
17
- const statsSync = new Map<string, Stats>();
18
-
19
73
  /**
20
- * Gets stats from the cache, if they exist and the cache is enabled.
74
+ * Used to cache
21
75
  */
22
- export function getStatsSync(path: string): Stats | undefined {
23
- if (!isEnabled) return;
24
-
25
- return statsSync.get(path);
26
- }
76
+ export const stats = new Cache<Stats>();
27
77
 
28
78
  /**
29
- * Adds stats if the cache is enabled
79
+ * Used to cache realpath lookups
30
80
  */
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 {
52
- if (!isEnabled) return;
53
-
54
- return stats.get(path);
55
- }
56
-
57
- /**
58
- * Adds stats if the cache is enabled
59
- */
60
- export function setStats(path: string, value: Promise<Stats | undefined>): void {
61
- if (!isEnabled) return;
62
-
63
- stats.set(path, value);
64
- }
65
-
66
- /**
67
- * Clears the cache if it is enabled
68
- */
69
- export function clearStats(): void {
70
- if (!isEnabled) return;
71
-
72
- stats.clear();
73
- }
81
+ export const paths = new Cache<string>();
@@ -4,5 +4,5 @@ export * as promises from './promises.js';
4
4
  export * as constants from './constants.js';
5
5
  export * from './streams.js';
6
6
  export * from './dir.js';
7
- export { mountObject, mounts, mount, umount } from './shared.js';
7
+ export { mountObject, mounts, mount, umount, _synced } from './shared.js';
8
8
  export { Stats, StatsFs, BigIntStatsFs } from '../stats.js';
@@ -471,7 +471,7 @@ export async function unlink(path: fs.PathLike): Promise<void> {
471
471
  path = normalizePath(path);
472
472
  const { fs, path: resolved } = resolveMount(path);
473
473
  try {
474
- if (config.checkAccess && !(await (cache.getStats(path) || fs.stat(resolved)))!.hasAccess(constants.W_OK)) {
474
+ if (config.checkAccess && !(await (cache.stats.get(path) || fs.stat(resolved)))!.hasAccess(constants.W_OK)) {
475
475
  throw ErrnoError.With('EACCES', resolved, 'unlink');
476
476
  }
477
477
  await fs.unlink(resolved);
@@ -623,7 +623,7 @@ export async function rmdir(path: fs.PathLike): Promise<void> {
623
623
  path = await realpath(path);
624
624
  const { fs, path: resolved } = resolveMount(path);
625
625
  try {
626
- const stats = await (cache.getStats(path) || fs.stat(resolved));
626
+ const stats = await (cache.stats.get(path) || fs.stat(resolved));
627
627
  if (!stats) {
628
628
  throw ErrnoError.With('ENOENT', path, 'rmdir');
629
629
  }
@@ -718,8 +718,8 @@ export async function readdir(
718
718
 
719
719
  const { fs, path: resolved } = resolveMount(path);
720
720
 
721
- const _stats = cache.getStats(path) || fs.stat(resolved).catch(handleError);
722
- cache.setStats(path, _stats);
721
+ const _stats = cache.stats.get(path) || fs.stat(resolved).catch(handleError);
722
+ cache.stats.set(path, _stats);
723
723
  const stats = await _stats;
724
724
 
725
725
  if (!stats) {
@@ -740,8 +740,8 @@ export async function readdir(
740
740
  const addEntry = async (entry: string) => {
741
741
  let entryStats: Stats | undefined;
742
742
  if (options?.recursive || options?.withFileTypes) {
743
- const _entryStats = cache.getStats(join(path, entry)) || fs.stat(join(resolved, entry)).catch(handleError);
744
- cache.setStats(join(path, entry), _entryStats);
743
+ const _entryStats = cache.stats.get(join(path, entry)) || fs.stat(join(resolved, entry)).catch(handleError);
744
+ cache.stats.set(join(path, entry), _entryStats);
745
745
  entryStats = await _entryStats;
746
746
  }
747
747
  if (options?.withFileTypes) {
@@ -768,7 +768,7 @@ export async function readdir(
768
768
  };
769
769
  await Promise.all(entries.map(addEntry));
770
770
  if (!options?._isIndirect) {
771
- cache.clearStats();
771
+ cache.stats.clear();
772
772
  }
773
773
 
774
774
  return values as string[] | Dirent[];
@@ -892,16 +892,19 @@ export async function realpath(path: fs.PathLike, options?: fs.EncodingOption |
892
892
  export async function realpath(path: fs.PathLike, options?: fs.EncodingOption | BufferEncoding | fs.BufferEncodingOption): Promise<string | Buffer> {
893
893
  path = normalizePath(path);
894
894
  const { base, dir } = parse(path);
895
- const lpath = join(dir == '/' ? '/' : await realpath(dir), base);
895
+ const lpath = join(dir == '/' ? '/' : await (cache.paths.get(dir) || realpath(dir)), base);
896
896
  const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath);
897
897
 
898
898
  try {
899
- const stats = await fs.stat(resolvedPath);
900
- if (!stats.isSymbolicLink()) {
899
+ const _stats = cache.stats.get(lpath) || fs.stat(resolvedPath);
900
+ cache.stats.set(lpath, _stats);
901
+ if (!(await _stats).isSymbolicLink()) {
901
902
  return lpath;
902
903
  }
903
904
 
904
- return await realpath(mountPoint + (await readlink(lpath)));
905
+ const target = mountPoint + (await readlink(lpath));
906
+
907
+ return await (cache.paths.get(target) || realpath(target));
905
908
  } catch (e) {
906
909
  if ((e as ErrnoError).code == 'ENOENT') {
907
910
  return path;
@@ -965,20 +968,18 @@ access satisfies typeof promises.access;
965
968
  export async function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOptions) {
966
969
  path = normalizePath(path);
967
970
 
968
- const _stats =
969
- cache.getStats(path) ||
971
+ const stats = await (cache.stats.get(path) ||
970
972
  stat(path).catch((error: ErrnoError) => {
971
973
  if (error.code == 'ENOENT' && options?.force) return undefined;
972
974
  throw error;
973
- });
974
-
975
- cache.setStats(path, _stats);
976
- const stats = await _stats;
975
+ }));
977
976
 
978
977
  if (!stats) {
979
978
  return;
980
979
  }
981
980
 
981
+ cache.stats.setSync(path, stats);
982
+
982
983
  switch (stats.mode & constants.S_IFMT) {
983
984
  case constants.S_IFDIR:
984
985
  if (options?.recursive) {
@@ -998,12 +999,12 @@ export async function rm(path: fs.PathLike, options?: fs.RmOptions & InternalOpt
998
999
  case constants.S_IFIFO:
999
1000
  case constants.S_IFSOCK:
1000
1001
  default:
1001
- cache.clearStats();
1002
+ cache.stats.clear();
1002
1003
  throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
1003
1004
  }
1004
1005
 
1005
1006
  if (!options?._isIndirect) {
1006
- cache.clearStats();
1007
+ cache.stats.clear();
1007
1008
  }
1008
1009
  }
1009
1010
  rm satisfies typeof promises.rm;
@@ -83,6 +83,16 @@ export function resolveMount(path: string): { fs: FileSystem; path: string; moun
83
83
  throw new ErrnoError(Errno.EIO, 'ZenFS not initialized with a file system');
84
84
  }
85
85
 
86
+ /**
87
+ * Wait for all file systems to be ready and synced.
88
+ * May be removed at some point.
89
+ * @experimental @internal
90
+ */
91
+ export async function _synced(): Promise<void> {
92
+ await Promise.all([...mounts.values()].map(m => m.ready()));
93
+ return;
94
+ }
95
+
86
96
  /**
87
97
  * Reverse maps the paths in text from the mounted FileSystem to the global path
88
98
  * @hidden
@@ -102,7 +112,9 @@ export function fixError<E extends ErrnoError>(e: E, paths: Record<string, strin
102
112
  if (typeof e.stack == 'string') {
103
113
  e.stack = fixPaths(e.stack, paths);
104
114
  }
105
- e.message = fixPaths(e.message, paths);
115
+ try {
116
+ e.message = fixPaths(e.message, paths);
117
+ } catch {}
106
118
  return e;
107
119
  }
108
120
 
@@ -106,7 +106,7 @@ export function unlinkSync(path: fs.PathLike): void {
106
106
  path = normalizePath(path);
107
107
  const { fs, path: resolved } = resolveMount(path);
108
108
  try {
109
- if (config.checkAccess && !(cache.getStatsSync(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
109
+ if (config.checkAccess && !(cache.stats.getSync(path) || fs.statSync(resolved)).hasAccess(constants.W_OK)) {
110
110
  throw ErrnoError.With('EACCES', resolved, 'unlink');
111
111
  }
112
112
  fs.unlinkSync(resolved);
@@ -386,7 +386,7 @@ export function rmdirSync(path: fs.PathLike): void {
386
386
  path = normalizePath(path);
387
387
  const { fs, path: resolved } = resolveMount(realpathSync(path));
388
388
  try {
389
- const stats = cache.getStatsSync(path) || fs.statSync(resolved);
389
+ const stats = cache.stats.getSync(path) || fs.statSync(resolved);
390
390
  if (!stats.isDirectory()) {
391
391
  throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
392
392
  }
@@ -459,8 +459,8 @@ export function readdirSync(
459
459
  const { fs, path: resolved } = resolveMount(realpathSync(path));
460
460
  let entries: string[];
461
461
  try {
462
- const stats = cache.getStatsSync(path) || fs.statSync(resolved);
463
- cache.setStatsSync(path, stats);
462
+ const stats = cache.stats.getSync(path) || fs.statSync(resolved);
463
+ cache.stats.setSync(path, stats);
464
464
  if (config.checkAccess && !stats.hasAccess(constants.R_OK)) {
465
465
  throw ErrnoError.With('EACCES', resolved, 'readdir');
466
466
  }
@@ -475,8 +475,8 @@ export function readdirSync(
475
475
  // Iterate over entries and handle recursive case if needed
476
476
  const values: (string | Dirent | Buffer)[] = [];
477
477
  for (const entry of entries) {
478
- const entryStat = cache.getStatsSync(join(path, entry)) || fs.statSync(join(resolved, entry));
479
- cache.setStatsSync(join(path, entry), entryStat);
478
+ const entryStat = cache.stats.getSync(join(path, entry)) || fs.statSync(join(resolved, entry));
479
+ cache.stats.setSync(join(path, entry), entryStat);
480
480
 
481
481
  if (options?.withFileTypes) {
482
482
  values.push(new Dirent(entry, entryStat));
@@ -500,7 +500,7 @@ export function readdirSync(
500
500
  }
501
501
 
502
502
  if (!options?._isIndirect) {
503
- cache.clearStatsSync();
503
+ cache.stats.clearSync();
504
504
  }
505
505
  return values as string[] | Dirent[] | Buffer[];
506
506
  }
@@ -622,7 +622,7 @@ export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption): st
622
622
  export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs.BufferEncodingOption): string | Buffer {
623
623
  path = normalizePath(path);
624
624
  const { base, dir } = parse(path);
625
- const lpath = join(dir == '/' ? '/' : realpathSync(dir), base);
625
+ const lpath = join(dir == '/' ? '/' : cache.paths.getSync(dir) || realpathSync(dir), base);
626
626
  const { fs, path: resolvedPath, mountPoint } = resolveMount(lpath);
627
627
 
628
628
  try {
@@ -631,7 +631,8 @@ export function realpathSync(path: fs.PathLike, options?: fs.EncodingOption | fs
631
631
  return lpath;
632
632
  }
633
633
 
634
- return realpathSync(mountPoint + readlinkSync(lpath, options).toString());
634
+ const target = mountPoint + readlinkSync(lpath, options).toString();
635
+ return cache.paths.getSync(target) || realpathSync(target);
635
636
  } catch (e) {
636
637
  if ((e as ErrnoError).code == 'ENOENT') {
637
638
  return path;
@@ -658,7 +659,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
658
659
 
659
660
  let stats: Stats | undefined;
660
661
  try {
661
- stats = cache.getStatsSync(path) || statSync(path);
662
+ stats = cache.stats.getSync(path) || statSync(path);
662
663
  } catch (error) {
663
664
  if ((error as ErrnoError).code != 'ENOENT' || !options?.force) throw error;
664
665
  }
@@ -667,7 +668,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
667
668
  return;
668
669
  }
669
670
 
670
- cache.setStatsSync(path, stats);
671
+ cache.stats.setSync(path, stats);
671
672
 
672
673
  switch (stats.mode & constants.S_IFMT) {
673
674
  case constants.S_IFDIR:
@@ -688,12 +689,12 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions & InternalOptio
688
689
  case constants.S_IFIFO:
689
690
  case constants.S_IFSOCK:
690
691
  default:
691
- cache.clearStatsSync();
692
+ cache.stats.clearSync();
692
693
  throw new ErrnoError(Errno.EPERM, 'File type not supported', path, 'rm');
693
694
  }
694
695
 
695
696
  if (!options?._isIndirect) {
696
- cache.clearStatsSync();
697
+ cache.stats.clearSync();
697
698
  }
698
699
  }
699
700
  rmSync satisfies typeof fs.rmSync;