@zenfs/core 1.2.7 → 1.2.9

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -58,6 +58,7 @@
58
58
  "lint": "eslint src tests",
59
59
  "test:common": "tsx --test --experimental-test-coverage 'tests/**/!(fs)/*.test.ts' 'tests/*.test.ts'",
60
60
  "test": "tsx --test --experimental-test-coverage",
61
+ "pretest": "npm run build",
61
62
  "build": "tsc -p tsconfig.json",
62
63
  "build:docs": "typedoc",
63
64
  "dev": "npm run build -- --watch",
package/readme.md CHANGED
@@ -201,6 +201,10 @@ If you would like to see a more intuitive way adding custom devices (e.g. `fs.mk
201
201
 
202
202
  ZenFS exports a drop-in for Node's `fs` module (up to the version of `@types/node` in package.json), so you can use it for your bundler of preference using the default export.
203
203
 
204
+ ## Sponsors
205
+
206
+ A huge thank you to [![Deco.cx logo](https://avatars.githubusercontent.com/deco-cx?size=20) Deco.cx](https://github.com/deco-cx) for sponsoring ZenFS and helping to make this possible.
207
+
204
208
  ## Building
205
209
 
206
210
  - Make sure you have Node and NPM installed. You must have Node v18 or newer.
@@ -17,8 +17,9 @@ export type OptionsConfig<T> = {
17
17
 
18
18
  /**
19
19
  * Description of the option. Used in error messages and documentation.
20
+ * @deprecated
20
21
  */
21
- description: string;
22
+ description?: string;
22
23
 
23
24
  /**
24
25
  * Whether or not the option is required (optional can be set to null or undefined). Defaults to false.
@@ -12,11 +12,11 @@ import type { IndexData } from './file_index.js';
12
12
  * constants.
13
13
  * @hidden
14
14
  */
15
- async function fetchFile(path: string, type: 'buffer'): Promise<Uint8Array>;
16
- async function fetchFile<T extends object>(path: string, type: 'json'): Promise<T>;
17
- async function fetchFile<T extends object>(path: string, type: 'buffer' | 'json'): Promise<T | Uint8Array>;
18
- async function fetchFile<T extends object>(path: string, type: string): Promise<T | Uint8Array> {
19
- const response = await fetch(path).catch((e: Error) => {
15
+ async function fetchFile(path: string, type: 'buffer', init?: RequestInit): Promise<Uint8Array>;
16
+ async function fetchFile<T extends object>(path: string, type: 'json', init?: RequestInit): Promise<T>;
17
+ async function fetchFile<T extends object>(path: string, type: 'buffer' | 'json', init?: RequestInit): Promise<T | Uint8Array>;
18
+ async function fetchFile<T extends object>(path: string, type: string, init?: RequestInit): Promise<T | Uint8Array> {
19
+ const response = await fetch(path, init).catch((e: Error) => {
20
20
  throw new ErrnoError(Errno.EIO, e.message, path);
21
21
  });
22
22
  if (!response.ok) {
@@ -42,6 +42,11 @@ async function fetchFile<T extends object>(path: string, type: string): Promise<
42
42
  * Configuration options for FetchFS.
43
43
  */
44
44
  export interface FetchOptions {
45
+ /**
46
+ * Options to pass through to fetch calls
47
+ */
48
+ requestInit?: RequestInit;
49
+
45
50
  /**
46
51
  * URL to a file index as a JSON file or the file index object itself.
47
52
  * Defaults to `index.json`.
@@ -75,6 +80,7 @@ export interface FetchOptions {
75
80
  */
76
81
  export class FetchFS extends IndexFS {
77
82
  public readonly baseUrl: string;
83
+ public readonly requestInit?: RequestInit;
78
84
 
79
85
  public async ready(): Promise<void> {
80
86
  if (this._isInitialized) {
@@ -94,15 +100,16 @@ export class FetchFS extends IndexFS {
94
100
  }
95
101
  }
96
102
 
97
- public constructor({ index = 'index.json', baseUrl = '' }: FetchOptions) {
103
+ public constructor({ index = 'index.json', baseUrl = '', requestInit }: FetchOptions) {
98
104
  // prefix url must end in a directory separator.
99
105
  if (baseUrl.at(-1) != '/') {
100
106
  baseUrl += '/';
101
107
  }
102
108
 
103
- super(typeof index != 'string' ? index : fetchFile<IndexData>(baseUrl + index, 'json'));
109
+ super(typeof index != 'string' ? index : fetchFile<IndexData>(baseUrl + index, 'json', requestInit));
104
110
 
105
111
  this.baseUrl = baseUrl;
112
+ this.requestInit = requestInit;
106
113
  }
107
114
 
108
115
  public metadata(): FileSystemMetadata {
@@ -136,7 +143,7 @@ export class FetchFS extends IndexFS {
136
143
  return stats.fileData;
137
144
  }
138
145
 
139
- const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer');
146
+ const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer', this.requestInit);
140
147
  stats.fileData = data;
141
148
  return data;
142
149
  }
@@ -154,16 +161,9 @@ const _Fetch = {
154
161
  name: 'Fetch',
155
162
 
156
163
  options: {
157
- index: {
158
- type: ['string', 'object'],
159
- required: false,
160
- description: 'URL to a file index as a JSON file or the file index object itself, generated with the make-index script. Defaults to `index.json`.',
161
- },
162
- baseUrl: {
163
- type: 'string',
164
- required: false,
165
- description: 'Used as the URL prefix for fetched files. Default: Fetch files relative to the index.',
166
- },
164
+ index: { type: ['string', 'object'], required: false },
165
+ baseUrl: { type: 'string', required: false },
166
+ requestInit: { type: 'object', required: false },
167
167
  },
168
168
 
169
169
  isAvailable(): boolean {
@@ -32,11 +32,7 @@ const _InMemory = {
32
32
  return true;
33
33
  },
34
34
  options: {
35
- name: {
36
- type: 'string',
37
- required: false,
38
- description: 'The name of the store',
39
- },
35
+ name: { type: 'string', required: false },
40
36
  },
41
37
  create({ name }: { name?: string }) {
42
38
  const fs = new StoreFS(new InMemoryStore(name));
@@ -298,7 +298,6 @@ const _Port = {
298
298
  port: {
299
299
  type: 'object',
300
300
  required: true,
301
- description: 'The target port that you want to connect to',
302
301
  validator(port: RPC.Port) {
303
302
  // Check for a `postMessage` function.
304
303
  if (typeof port?.postMessage != 'function') {
@@ -309,7 +308,6 @@ const _Port = {
309
308
  timeout: {
310
309
  type: 'number',
311
310
  required: false,
312
- description: 'How long to wait before the request times out',
313
311
  },
314
312
  },
315
313
 
@@ -534,22 +534,22 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
534
534
  * @param data The data to store at the file's data node.
535
535
  */
536
536
  private async commitNew(path: string, type: FileType, mode: number, data: Uint8Array): Promise<Inode> {
537
- await using tx = this.store.transaction();
538
- const parentPath = dirname(path),
539
- parent = await this.findINode(tx, parentPath);
540
-
541
- const fname = basename(path),
542
- listing = await this.getDirListing(tx, parent, parentPath);
543
-
544
537
  /*
545
538
  The root always exists.
546
539
  If we don't check this prior to taking steps below,
547
- we will create a file with name '' in root should path == '/'.
540
+ we will create a file with name '' in root if path is '/'.
548
541
  */
549
- if (path === '/') {
542
+ if (path == '/') {
550
543
  throw ErrnoError.With('EEXIST', path, 'commitNew');
551
544
  }
552
545
 
546
+ await using tx = this.store.transaction();
547
+ const parentPath = dirname(path),
548
+ parent = await this.findINode(tx, parentPath);
549
+
550
+ const fname = basename(path),
551
+ listing = await this.getDirListing(tx, parent, parentPath);
552
+
553
553
  // Check if file already exists.
554
554
  if (listing[fname]) {
555
555
  await tx.abort();
@@ -581,22 +581,22 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
581
581
  * @return The Inode for the new file.
582
582
  */
583
583
  protected commitNewSync(path: string, type: FileType, mode: number, data: Uint8Array = new Uint8Array()): Inode {
584
- using tx = this.store.transaction();
585
- const parentPath = dirname(path),
586
- parent = this.findINodeSync(tx, parentPath);
587
-
588
- const fname = basename(path),
589
- listing = this.getDirListingSync(tx, parent, parentPath);
590
-
591
584
  /*
592
585
  The root always exists.
593
586
  If we don't check this prior to taking steps below,
594
- we will create a file with name '' in root should p == '/'.
587
+ we will create a file with name '' in root if path is '/'.
595
588
  */
596
- if (path === '/') {
589
+ if (path == '/') {
597
590
  throw ErrnoError.With('EEXIST', path, 'commitNew');
598
591
  }
599
592
 
593
+ using tx = this.store.transaction();
594
+ const parentPath = dirname(path),
595
+ parent = this.findINodeSync(tx, parentPath);
596
+
597
+ const fname = basename(path),
598
+ listing = this.getDirListingSync(tx, parent, parentPath);
599
+
600
600
  // Check if file already exists.
601
601
  if (listing[fname]) {
602
602
  throw ErrnoError.With('EEXIST', path, 'commitNew');
package/src/config.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js';
2
2
  import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
3
3
  import { credentials } from './credentials.js';
4
- import { DeviceFS, fullDevice, nullDevice, randomDevice, zeroDevice } from './devices.js';
4
+ import { DeviceFS } from './devices.js';
5
5
  import * as cache from './emulation/cache.js';
6
+ import { config } from './emulation/config.js';
6
7
  import * as fs from './emulation/index.js';
7
8
  import type { AbsolutePath } from './emulation/path.js';
8
- import { type MountObject } from './emulation/shared.js';
9
- import { config } from './emulation/config.js';
10
9
  import { Errno, ErrnoError } from './error.js';
11
10
  import { FileSystem } from './filesystem.js';
12
11
 
@@ -151,6 +150,27 @@ export async function configureSingle<T extends Backend>(configuration: MountCon
151
150
  fs.mount('/', resolved);
152
151
  }
153
152
 
153
+ /**
154
+ * Like `fs.mount`, but it also creates missing directories.
155
+ * @privateRemarks
156
+ * This is implemented as a separate function to avoid a circular dependency between emulation/shared.ts and other emulation layer files.
157
+ * @internal
158
+ */
159
+ async function mount(path: string, mount: FileSystem): Promise<void> {
160
+ if (path == '/') {
161
+ fs.mount(path, mount);
162
+ return;
163
+ }
164
+
165
+ const stats = await fs.promises.stat(path).catch(() => null);
166
+ if (!stats) {
167
+ await fs.promises.mkdir(path, { recursive: true });
168
+ } else if (!stats.isDirectory()) {
169
+ throw ErrnoError.With('ENOTDIR', path, 'configure');
170
+ }
171
+ fs.mount(path, mount);
172
+ }
173
+
154
174
  /**
155
175
  * Configures ZenFS with `configuration`
156
176
  * @see Configuration
@@ -168,18 +188,18 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
168
188
 
169
189
  if (configuration.addDevices) {
170
190
  const devfs = new DeviceFS();
171
- devfs.createDevice('/null', nullDevice);
172
- devfs.createDevice('/zero', zeroDevice);
173
- devfs.createDevice('/full', fullDevice);
174
- devfs.createDevice('/random', randomDevice);
191
+ devfs.addDefaults();
175
192
  await devfs.ready();
176
- fs.mount('/dev', devfs);
193
+ await mount('/dev', devfs);
177
194
  }
178
195
 
179
196
  if (!configuration.mounts) {
180
197
  return;
181
198
  }
182
199
 
200
+ const toMount: [string, FileSystem][] = [];
201
+ let unmountRoot = false;
202
+
183
203
  for (const [point, mountConfig] of Object.entries(configuration.mounts)) {
184
204
  if (!point.startsWith('/')) {
185
205
  throw new ErrnoError(Errno.EINVAL, 'Mount points must have absolute paths');
@@ -189,8 +209,11 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
189
209
  mountConfig.disableAsyncCache ??= configuration.disableAsyncCache || false;
190
210
  }
191
211
 
192
- configuration.mounts[point as keyof T & `/${string}`] = await resolveMountConfig(mountConfig);
212
+ if (point == '/') unmountRoot = true;
213
+ toMount.push([point, await resolveMountConfig(mountConfig)]);
193
214
  }
194
215
 
195
- fs.mountObject(configuration.mounts as MountObject);
216
+ if (unmountRoot) fs.umount('/');
217
+
218
+ await Promise.all(toMount.map(([point, fs]) => mount(point, fs)));
196
219
  }
@@ -21,6 +21,9 @@ export const credentials: Credentials = {
21
21
  egid: 0,
22
22
  };
23
23
 
24
+ /**
25
+ * @deprecated
26
+ */
24
27
  export const rootCredentials: Credentials = {
25
28
  uid: 0,
26
29
  gid: 0,
package/src/devices.ts CHANGED
@@ -1,3 +1,7 @@
1
+ /*
2
+ This is a great resource: https://www.kernel.org/doc/html/latest/admin-guide/devices.html
3
+ */
4
+
1
5
  import type { FileReadResult } from 'node:fs/promises';
2
6
  import { InMemoryStore } from './backends/memory.js';
3
7
  import { StoreFS } from './backends/store/fs.js';
@@ -12,9 +16,10 @@ import type { Ino } from './inode.js';
12
16
  /**
13
17
  * A device
14
18
  * @todo Maybe add major/minor number or some other device information, like a UUID?
15
- * @experimental
19
+ * @privateRemarks
20
+ * UUIDs were considered, however they don't make sense without an easy mechanism for persistance
16
21
  */
17
- export interface Device {
22
+ export interface Device<TData = any> {
18
23
  /**
19
24
  * The device's driver
20
25
  */
@@ -24,13 +29,31 @@ export interface Device {
24
29
  * Which inode the device is assigned
25
30
  */
26
31
  ino: Ino;
32
+
33
+ /**
34
+ * Data associated with a device.
35
+ * This is meant to be used by device drivers.
36
+ * @experimental
37
+ */
38
+ data: TData;
39
+
40
+ /**
41
+ * Major device number
42
+ * @experimental
43
+ */
44
+ major: number;
45
+
46
+ /**
47
+ * Minor device number
48
+ * @experimental
49
+ */
50
+ minor: number;
27
51
  }
28
52
 
29
53
  /**
30
54
  * A device driver
31
- * @experimental
32
55
  */
33
- export interface DeviceDriver {
56
+ export interface DeviceDriver<TData = any> {
34
57
  /**
35
58
  * The name of the device driver
36
59
  */
@@ -38,26 +61,42 @@ export interface DeviceDriver {
38
61
 
39
62
  /**
40
63
  * Whether the device is buffered (a "block" device) or unbuffered (a "character" device)
64
+ * @default false
41
65
  */
42
- isBuffered: boolean;
66
+ isBuffered?: boolean;
67
+
68
+ /**
69
+ * Initializes a new device.
70
+ * @returns `Device.data`
71
+ * @experimental
72
+ */
73
+ init?(ino: Ino): {
74
+ data?: TData;
75
+ minor?: number;
76
+ major?: number;
77
+ };
43
78
 
44
79
  /**
45
80
  * Synchronously read from the device
81
+ * @group File operations
46
82
  */
47
83
  read(file: DeviceFile, buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number;
48
84
 
49
85
  /**
50
86
  * Synchronously write to the device
87
+ * @group File operations
51
88
  */
52
89
  write(file: DeviceFile, buffer: Uint8Array, offset: number, length: number, position?: number): number;
53
90
 
54
91
  /**
55
92
  * Sync the device
93
+ * @group File operations
56
94
  */
57
95
  sync?(file: DeviceFile): void;
58
96
 
59
97
  /**
60
98
  * Close the device
99
+ * @group File operations
61
100
  */
62
101
  close?(file: DeviceFile): void;
63
102
  }
@@ -67,7 +106,6 @@ export interface DeviceDriver {
67
106
  * This class only does some simple things:
68
107
  * It implements `truncate` using `write` and it has non-device methods throw.
69
108
  * It is up to device drivers to implement the rest of the functionality.
70
- * @experimental
71
109
  */
72
110
  export class DeviceFile extends File {
73
111
  public position = 0;
@@ -182,12 +220,15 @@ export class DeviceFile extends File {
182
220
  }
183
221
 
184
222
  /**
185
- * @experimental
223
+ * A temporary file system that manages and interfaces with devices
186
224
  */
187
225
  export class DeviceFS extends StoreFS<InMemoryStore> {
188
226
  protected readonly devices = new Map<string, Device>();
189
227
 
190
- public createDevice(path: string, driver: DeviceDriver): Device {
228
+ /**
229
+ * Creates a new device at `path` relative to the `DeviceFS` root.
230
+ */
231
+ public createDevice<TData = any>(path: string, driver: DeviceDriver<TData>): Device<TData | Record<string, never>> {
191
232
  if (this.existsSync(path)) {
192
233
  throw ErrnoError.With('EEXIST', path, 'mknod');
193
234
  }
@@ -196,11 +237,25 @@ export class DeviceFS extends StoreFS<InMemoryStore> {
196
237
  const dev = {
197
238
  driver,
198
239
  ino,
240
+ data: {},
241
+ minor: 0,
242
+ major: 0,
243
+ ...driver.init?.(ino),
199
244
  };
200
245
  this.devices.set(path, dev);
201
246
  return dev;
202
247
  }
203
248
 
249
+ /**
250
+ * Adds default devices
251
+ */
252
+ public addDefaults(): void {
253
+ this.createDevice('/null', nullDevice);
254
+ this.createDevice('/zero', zeroDevice);
255
+ this.createDevice('/full', fullDevice);
256
+ this.createDevice('/random', randomDevice);
257
+ }
258
+
204
259
  public constructor() {
205
260
  super(new InMemoryStore('devfs'));
206
261
  }
@@ -373,7 +428,9 @@ function defaultWrite(file: DeviceFile, buffer: Uint8Array, offset: number, leng
373
428
  */
374
429
  export const nullDevice: DeviceDriver = {
375
430
  name: 'null',
376
- isBuffered: false,
431
+ init() {
432
+ return { major: 1, minor: 3 };
433
+ },
377
434
  read(): number {
378
435
  return 0;
379
436
  },
@@ -392,7 +449,9 @@ export const nullDevice: DeviceDriver = {
392
449
  */
393
450
  export const zeroDevice: DeviceDriver = {
394
451
  name: 'zero',
395
- isBuffered: false,
452
+ init() {
453
+ return { major: 1, minor: 5 };
454
+ },
396
455
  read(file: DeviceFile, buffer: ArrayBufferView, offset = 0, length = buffer.byteLength): number {
397
456
  const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
398
457
  for (let i = offset; i < offset + length; i++) {
@@ -412,7 +471,9 @@ export const zeroDevice: DeviceDriver = {
412
471
  */
413
472
  export const fullDevice: DeviceDriver = {
414
473
  name: 'full',
415
- isBuffered: false,
474
+ init() {
475
+ return { major: 1, minor: 7 };
476
+ },
416
477
  read(file: DeviceFile, buffer: ArrayBufferView, offset = 0, length = buffer.byteLength): number {
417
478
  const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
418
479
  for (let i = offset; i < offset + length; i++) {
@@ -435,7 +496,9 @@ export const fullDevice: DeviceDriver = {
435
496
  */
436
497
  export const randomDevice: DeviceDriver = {
437
498
  name: 'random',
438
- isBuffered: false,
499
+ init() {
500
+ return { major: 1, minor: 8 };
501
+ },
439
502
  read(file: DeviceFile, buffer: ArrayBufferView, offset = 0, length = buffer.byteLength): number {
440
503
  const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
441
504
  for (let i = offset; i < offset + length; i++) {
@@ -13,11 +13,11 @@ import '../polyfills.js';
13
13
  import { BigIntStats, type Stats } from '../stats.js';
14
14
  import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
15
15
  import * as cache from './cache.js';
16
+ import { config } from './config.js';
16
17
  import * as constants from './constants.js';
17
18
  import { Dir, Dirent } from './dir.js';
18
19
  import { dirname, join, parse } from './path.js';
19
- import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
20
- import { config } from './config.js';
20
+ import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
21
21
  import { ReadStream, WriteStream } from './streams.js';
22
22
  import { FSWatcher, emitChange } from './watchers.js';
23
23
  export * as constants from './constants.js';
@@ -625,7 +625,7 @@ export async function rmdir(path: fs.PathLike): Promise<void> {
625
625
  try {
626
626
  const stats = await (cache.getStats(path) || fs.stat(resolved));
627
627
  if (!stats) {
628
- throw ErrnoError.With('ENOENT', path, 'readdir');
628
+ throw ErrnoError.With('ENOENT', path, 'rmdir');
629
629
  }
630
630
  if (!stats.isDirectory()) {
631
631
  throw ErrnoError.With('ENOTDIR', resolved, 'rmdir');
@@ -736,17 +736,6 @@ export async function readdir(
736
736
 
737
737
  const entries = await fs.readdir(resolved).catch(handleError);
738
738
 
739
- for (const point of mounts.keys()) {
740
- if (point.startsWith(path)) {
741
- const entry = point.slice(path.length);
742
- if (entry.includes('/') || entry.length == 0) {
743
- // ignore FSs mounted in subdirectories and any FS mounted to `path`.
744
- continue;
745
- }
746
- entries.push(entry);
747
- }
748
- }
749
-
750
739
  const values: (string | Dirent | Buffer)[] = [];
751
740
  const addEntry = async (entry: string) => {
752
741
  let entryStats: Stats | undefined;
@@ -54,7 +54,7 @@ export function mount(mountPoint: string, fs: FileSystem): void {
54
54
  */
55
55
  export function umount(mountPoint: string): void {
56
56
  if (mountPoint[0] !== '/') {
57
- mountPoint = `/${mountPoint}`;
57
+ mountPoint = '/' + mountPoint;
58
58
  }
59
59
  mountPoint = resolve(mountPoint);
60
60
  if (!mounts.has(mountPoint)) {
@@ -64,11 +64,10 @@ export function umount(mountPoint: string): void {
64
64
  }
65
65
 
66
66
  /**
67
- * Gets the internal FileSystem for the path, then returns it along with the path relative to the FS' root
67
+ * Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root
68
68
  */
69
69
  export function resolveMount(path: string): { fs: FileSystem; path: string; mountPoint: string } {
70
70
  path = normalizePath(path);
71
- // Maybe do something for devices here
72
71
  const sortedMounts = [...mounts].sort((a, b) => (a[0].length > b[0].length ? -1 : 1)); // descending order of the string length
73
72
  for (const [mountPoint, fs] of sortedMounts) {
74
73
  // We know path is normalized, so it would be a substring of the mount point.
@@ -107,6 +106,9 @@ export function fixError<E extends ErrnoError>(e: E, paths: Record<string, strin
107
106
  return e;
108
107
  }
109
108
 
109
+ /**
110
+ * @deprecated
111
+ */
110
112
  export function mountObject(mounts: MountObject): void {
111
113
  if ('/' in mounts) {
112
114
  umount('/');
@@ -6,13 +6,13 @@ import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWrit
6
6
  import type { FileContents } from '../filesystem.js';
7
7
  import { BigIntStats, type Stats } from '../stats.js';
8
8
  import { decodeUTF8, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
9
+ import * as cache from './cache.js';
10
+ import { config } from './config.js';
9
11
  import * as constants from './constants.js';
10
12
  import { Dir, Dirent } from './dir.js';
11
13
  import { dirname, join, parse } from './path.js';
12
- import { _statfs, fd2file, fdMap, file2fd, fixError, mounts, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
13
- import { config } from './config.js';
14
+ import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount, type InternalOptions, type ReaddirOptions } from './shared.js';
14
15
  import { emitChange } from './watchers.js';
15
- import * as cache from './cache.js';
16
16
 
17
17
  export function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void {
18
18
  oldPath = normalizePath(oldPath);
@@ -472,18 +472,6 @@ export function readdirSync(
472
472
  throw fixError(e as ErrnoError, { [resolved]: path });
473
473
  }
474
474
 
475
- for (const mount of mounts.keys()) {
476
- if (!mount.startsWith(path)) {
477
- continue;
478
- }
479
- const entry = mount.slice(path.length);
480
- if (entry.includes('/') || entry.length == 0) {
481
- // ignore FSs mounted in subdirectories and any FS mounted to `path`.
482
- continue;
483
- }
484
- entries.push(entry);
485
- }
486
-
487
475
  // Iterate over entries and handle recursive case if needed
488
476
  const values: (string | Dirent | Buffer)[] = [];
489
477
  for (const entry of entries) {
@@ -80,8 +80,8 @@ export function Async<T extends typeof FileSystem>(
80
80
  const async = (this as StoreFS<Store>)['store'].transaction();
81
81
 
82
82
  const promises = [];
83
- for (const key of sync.keysSync()) {
84
- promises.push(async.set(key, sync.getSync(key)));
83
+ for (const key of await async.keys()) {
84
+ promises.push(async.get(key).then(data => sync.setSync(key, data)));
85
85
  }
86
86
 
87
87
  await Promise.all(promises);