@zenfs/core 1.5.0 → 1.6.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.
package/dist/config.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js';
2
+ import { type Device, type DeviceDriver } from './devices.js';
2
3
  /**
3
4
  * Configuration for a specific mount point
4
5
  */
@@ -85,6 +86,7 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
85
86
  * Configures ZenFS with single mount point /
86
87
  */
87
88
  export declare function configureSingle<T extends Backend>(configuration: MountConfiguration<T>): Promise<void>;
89
+ export declare function addDevice(driver: DeviceDriver, options?: object): Device;
88
90
  /**
89
91
  * Configures ZenFS with `configuration`
90
92
  * @see Configuration
package/dist/config.js CHANGED
@@ -4,6 +4,7 @@ import { DeviceFS } from './devices.js';
4
4
  import * as cache from './emulation/cache.js';
5
5
  import { config } from './emulation/config.js';
6
6
  import * as fs from './emulation/index.js';
7
+ import { mounts } from './emulation/shared.js';
7
8
  import { Errno, ErrnoError } from './error.js';
8
9
  import { FileSystem } from './filesystem.js';
9
10
  function isMountConfig(arg) {
@@ -80,6 +81,12 @@ async function mount(path, mount) {
80
81
  }
81
82
  fs.mount(path, mount);
82
83
  }
84
+ export function addDevice(driver, options) {
85
+ const devfs = mounts.get('/dev');
86
+ if (!(devfs instanceof DeviceFS))
87
+ throw new ErrnoError(Errno.ENOTSUP, '/dev does not exist or is not a device file system');
88
+ return devfs._createDevice(driver, options);
89
+ }
83
90
  /**
84
91
  * Configures ZenFS with `configuration`
85
92
  * @see Configuration
package/dist/devices.d.ts CHANGED
@@ -8,7 +8,7 @@ import { Stats } from './stats.js';
8
8
  * A device
9
9
  * @todo Maybe add major/minor number or some other device information, like a UUID?
10
10
  * @privateRemarks
11
- * UUIDs were considered, however they don't make sense without an easy mechanism for persistance
11
+ * UUIDs were considered, however they don't make sense without an easy mechanism for persistence
12
12
  */
13
13
  export interface Device<TData = any> {
14
14
  /**
@@ -22,17 +22,14 @@ export interface Device<TData = any> {
22
22
  /**
23
23
  * Data associated with a device.
24
24
  * This is meant to be used by device drivers.
25
- * @experimental
26
25
  */
27
26
  data: TData;
28
27
  /**
29
28
  * Major device number
30
- * @experimental
31
29
  */
32
30
  major: number;
33
31
  /**
34
32
  * Minor device number
35
- * @experimental
36
33
  */
37
34
  minor: number;
38
35
  }
@@ -44,6 +41,11 @@ export interface DeviceDriver<TData = any> {
44
41
  * The name of the device driver
45
42
  */
46
43
  name: string;
44
+ /**
45
+ * If true, only a single device can exist per device FS.
46
+ * Note that if this is unset or false, auto-named devices will have a number suffix
47
+ */
48
+ singleton?: boolean;
47
49
  /**
48
50
  * Whether the device is buffered (a "block" device) or unbuffered (a "character" device)
49
51
  * @default false
@@ -52,33 +54,33 @@ export interface DeviceDriver<TData = any> {
52
54
  /**
53
55
  * Initializes a new device.
54
56
  * @returns `Device.data`
55
- * @experimental
56
57
  */
57
- init?(ino: bigint): {
58
+ init?(ino: bigint, options: object): {
58
59
  data?: TData;
59
60
  minor?: number;
60
61
  major?: number;
62
+ name?: string;
61
63
  };
62
64
  /**
63
65
  * Synchronously read from the device
64
66
  * @group File operations
65
67
  */
66
- read(file: DeviceFile, buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number;
68
+ read(file: DeviceFile<TData>, buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number;
67
69
  /**
68
70
  * Synchronously write to the device
69
71
  * @group File operations
70
72
  */
71
- write(file: DeviceFile, buffer: Uint8Array, offset: number, length: number, position?: number): number;
73
+ write(file: DeviceFile<TData>, buffer: Uint8Array, offset: number, length: number, position?: number): number;
72
74
  /**
73
75
  * Sync the device
74
76
  * @group File operations
75
77
  */
76
- sync?(file: DeviceFile): void;
78
+ sync?(file: DeviceFile<TData>): void;
77
79
  /**
78
80
  * Close the device
79
81
  * @group File operations
80
82
  */
81
- close?(file: DeviceFile): void;
83
+ close?(file: DeviceFile<TData>): void;
82
84
  }
83
85
  /**
84
86
  * The base class for device files
@@ -86,12 +88,12 @@ export interface DeviceDriver<TData = any> {
86
88
  * It implements `truncate` using `write` and it has non-device methods throw.
87
89
  * It is up to device drivers to implement the rest of the functionality.
88
90
  */
89
- export declare class DeviceFile extends File {
91
+ export declare class DeviceFile<TData = any> extends File {
90
92
  fs: DeviceFS;
91
- readonly device: Device;
93
+ readonly device: Device<TData>;
92
94
  position: number;
93
- constructor(fs: DeviceFS, path: string, device: Device);
94
- get driver(): DeviceDriver;
95
+ constructor(fs: DeviceFS, path: string, device: Device<TData>);
96
+ get driver(): DeviceDriver<TData>;
95
97
  protected get stats(): Partial<StatsLike>;
96
98
  stat(): Promise<Stats>;
97
99
  statSync(): Stats;
@@ -121,8 +123,14 @@ export declare class DeviceFS extends StoreFS<InMemoryStore> {
121
123
  protected readonly devices: Map<string, Device<any>>;
122
124
  /**
123
125
  * Creates a new device at `path` relative to the `DeviceFS` root.
126
+ * @deprecated
127
+ */
128
+ createDevice<TData = any>(path: string, driver: DeviceDriver<TData>, options?: object): Device<TData | Record<string, never>>;
129
+ protected devicesWithDriver(driver: DeviceDriver<unknown> | string, forceIdentity?: boolean): Device[];
130
+ /**
131
+ * @internal
124
132
  */
125
- createDevice<TData = any>(path: string, driver: DeviceDriver<TData>): Device<TData | Record<string, never>>;
133
+ _createDevice<TData = any>(driver: DeviceDriver<TData>, options?: object): Device<TData | Record<string, never>>;
126
134
  /**
127
135
  * Adds default devices
128
136
  */
@@ -153,7 +161,7 @@ export declare class DeviceFS extends StoreFS<InMemoryStore> {
153
161
  * Simulates the `/dev/null` device.
154
162
  * - Reads return 0 bytes (EOF).
155
163
  * - Writes discard data, advancing the file position.
156
- * @experimental
164
+ * @internal
157
165
  */
158
166
  export declare const nullDevice: DeviceDriver;
159
167
  /**
@@ -164,31 +172,32 @@ export declare const nullDevice: DeviceDriver;
164
172
  * - Reads fill the buffer with zeroes.
165
173
  * - Writes discard data but update the file position.
166
174
  * - Provides basic file metadata, treating it as a character device.
167
- * @experimental
175
+ * @internal
168
176
  */
169
177
  export declare const zeroDevice: DeviceDriver;
170
178
  /**
171
179
  * Simulates the `/dev/full` device.
172
180
  * - Reads behave like `/dev/zero` (returns zeroes).
173
181
  * - Writes always fail with ENOSPC (no space left on device).
174
- * @experimental
182
+ * @internal
175
183
  */
176
184
  export declare const fullDevice: DeviceDriver;
177
185
  /**
178
186
  * Simulates the `/dev/random` device.
179
187
  * - Reads return random bytes.
180
188
  * - Writes discard data, advancing the file position.
181
- * @experimental
189
+ * @internal
182
190
  */
183
191
  export declare const randomDevice: DeviceDriver;
184
192
  /**
185
193
  * Shortcuts for importing.
186
- * @experimental
187
194
  */
188
- declare const _default: {
195
+ export declare const devices: {
189
196
  null: DeviceDriver<any>;
190
197
  zero: DeviceDriver<any>;
191
198
  full: DeviceDriver<any>;
192
199
  random: DeviceDriver<any>;
200
+ console: DeviceDriver<{
201
+ output: (text: string) => unknown;
202
+ }>;
193
203
  };
194
- export default _default;
package/dist/devices.js CHANGED
@@ -60,6 +60,7 @@ import { Errno, ErrnoError } from './error.js';
60
60
  import { File } from './file.js';
61
61
  import { Stats } from './stats.js';
62
62
  import { basename, dirname } from './emulation/path.js';
63
+ import { decodeUTF8 } from './utils.js';
63
64
  /**
64
65
  * The base class for device files
65
66
  * This class only does some simple things:
@@ -154,8 +155,9 @@ export class DeviceFile extends File {
154
155
  export class DeviceFS extends StoreFS {
155
156
  /**
156
157
  * Creates a new device at `path` relative to the `DeviceFS` root.
158
+ * @deprecated
157
159
  */
158
- createDevice(path, driver) {
160
+ createDevice(path, driver, options = {}) {
159
161
  if (this.existsSync(path)) {
160
162
  throw ErrnoError.With('EEXIST', path, 'mknod');
161
163
  }
@@ -168,19 +170,56 @@ export class DeviceFS extends StoreFS {
168
170
  data: {},
169
171
  minor: 0,
170
172
  major: 0,
171
- ...driver.init?.(ino),
173
+ ...driver.init?.(ino, options),
172
174
  };
173
175
  this.devices.set(path, dev);
174
176
  return dev;
175
177
  }
178
+ devicesWithDriver(driver, forceIdentity) {
179
+ if (forceIdentity && typeof driver == 'string') {
180
+ throw new ErrnoError(Errno.EINVAL, 'Can not fetch devices using only a driver name');
181
+ }
182
+ const devs = [];
183
+ for (const device of this.devices.values()) {
184
+ if (forceIdentity && device.driver != driver)
185
+ continue;
186
+ const name = typeof driver == 'string' ? driver : driver.name;
187
+ if (name == device.driver.name)
188
+ devs.push(device);
189
+ }
190
+ return devs;
191
+ }
192
+ /**
193
+ * @internal
194
+ */
195
+ _createDevice(driver, options = {}) {
196
+ let ino = 1n;
197
+ while (this.store.has(ino))
198
+ ino++;
199
+ const dev = {
200
+ driver,
201
+ ino,
202
+ data: {},
203
+ minor: 0,
204
+ major: 0,
205
+ ...driver.init?.(ino, options),
206
+ };
207
+ const path = '/' + (dev.name || driver.name) + (driver.singleton ? '' : this.devicesWithDriver(driver).length);
208
+ if (this.existsSync(path)) {
209
+ throw ErrnoError.With('EEXIST', path, 'mknod');
210
+ }
211
+ this.devices.set(path, dev);
212
+ return dev;
213
+ }
176
214
  /**
177
215
  * Adds default devices
178
216
  */
179
217
  addDefaults() {
180
- this.createDevice('/null', nullDevice);
181
- this.createDevice('/zero', zeroDevice);
182
- this.createDevice('/full', fullDevice);
183
- this.createDevice('/random', randomDevice);
218
+ this._createDevice(nullDevice);
219
+ this._createDevice(zeroDevice);
220
+ this._createDevice(fullDevice);
221
+ this._createDevice(randomDevice);
222
+ this._createDevice(consoleDevice);
184
223
  }
185
224
  constructor() {
186
225
  super(new InMemoryStore('devfs'));
@@ -351,10 +390,11 @@ function defaultWrite(file, buffer, offset, length) {
351
390
  * Simulates the `/dev/null` device.
352
391
  * - Reads return 0 bytes (EOF).
353
392
  * - Writes discard data, advancing the file position.
354
- * @experimental
393
+ * @internal
355
394
  */
356
395
  export const nullDevice = {
357
396
  name: 'null',
397
+ singleton: true,
358
398
  init() {
359
399
  return { major: 1, minor: 3 };
360
400
  },
@@ -371,10 +411,11 @@ export const nullDevice = {
371
411
  * - Reads fill the buffer with zeroes.
372
412
  * - Writes discard data but update the file position.
373
413
  * - Provides basic file metadata, treating it as a character device.
374
- * @experimental
414
+ * @internal
375
415
  */
376
416
  export const zeroDevice = {
377
417
  name: 'zero',
418
+ singleton: true,
378
419
  init() {
379
420
  return { major: 1, minor: 5 };
380
421
  },
@@ -392,10 +433,11 @@ export const zeroDevice = {
392
433
  * Simulates the `/dev/full` device.
393
434
  * - Reads behave like `/dev/zero` (returns zeroes).
394
435
  * - Writes always fail with ENOSPC (no space left on device).
395
- * @experimental
436
+ * @internal
396
437
  */
397
438
  export const fullDevice = {
398
439
  name: 'full',
440
+ singleton: true,
399
441
  init() {
400
442
  return { major: 1, minor: 7 };
401
443
  },
@@ -415,10 +457,11 @@ export const fullDevice = {
415
457
  * Simulates the `/dev/random` device.
416
458
  * - Reads return random bytes.
417
459
  * - Writes discard data, advancing the file position.
418
- * @experimental
460
+ * @internal
419
461
  */
420
462
  export const randomDevice = {
421
463
  name: 'random',
464
+ singleton: true,
422
465
  init() {
423
466
  return { major: 1, minor: 8 };
424
467
  },
@@ -432,13 +475,33 @@ export const randomDevice = {
432
475
  },
433
476
  write: defaultWrite,
434
477
  };
478
+ /**
479
+ * Simulates the `/dev/console` device.
480
+ * @experimental @internal
481
+ */
482
+ const consoleDevice = {
483
+ name: 'console',
484
+ singleton: true,
485
+ init(ino, { output = console.log } = {}) {
486
+ return { major: 5, minor: 1, data: { output } };
487
+ },
488
+ read() {
489
+ return 0;
490
+ },
491
+ write(file, buffer, offset, length) {
492
+ const text = decodeUTF8(buffer.slice(offset, offset + length));
493
+ file.device.data.output(text);
494
+ file.position += length;
495
+ return length;
496
+ },
497
+ };
435
498
  /**
436
499
  * Shortcuts for importing.
437
- * @experimental
438
500
  */
439
- export default {
501
+ export const devices = {
440
502
  null: nullDevice,
441
503
  zero: zeroDevice,
442
504
  full: fullDevice,
443
505
  random: randomDevice,
506
+ console: consoleDevice,
444
507
  };
@@ -1,5 +1,6 @@
1
1
  import { Buffer } from 'buffer';
2
2
  import type * as fs from 'node:fs';
3
+ import { ErrnoError } from '../error.js';
3
4
  import type { FileContents } from '../filesystem.js';
4
5
  import { BigIntStats, type Stats } from '../stats.js';
5
6
  import { type Callback } from '../utils.js';
@@ -309,4 +310,12 @@ export declare function statfs(this: V_Context, path: fs.PathLike, options: fs.S
309
310
  bigint: true;
310
311
  }, callback: Callback<[fs.BigIntStatsFs]>): void;
311
312
  export declare function openAsBlob(this: V_Context, path: fs.PathLike, options?: fs.OpenAsBlobOptions): Promise<Blob>;
313
+ type GlobCallback<Args extends unknown[]> = (e: ErrnoError | null, ...args: Args) => unknown;
314
+ /**
315
+ * Retrieves the files matching the specified pattern.
316
+ */
317
+ export declare function glob(this: V_Context, pattern: string | string[], callback: GlobCallback<[string[]]>): void;
318
+ export declare function glob(this: V_Context, pattern: string | string[], options: fs.GlobOptionsWithFileTypes, callback: GlobCallback<[Dirent[]]>): void;
319
+ export declare function glob(this: V_Context, pattern: string | string[], options: fs.GlobOptionsWithoutFileTypes, callback: GlobCallback<[string[]]>): void;
320
+ export declare function glob(this: V_Context, pattern: string | string[], options: fs.GlobOptions, callback: GlobCallback<[Dirent[] | string[]]>): void;
312
321
  export {};
@@ -8,6 +8,16 @@ import { fd2file, fdMap } from './shared.js';
8
8
  import { ReadStream, WriteStream } from './streams.js';
9
9
  import { FSWatcher, StatWatcher } from './watchers.js';
10
10
  const nop = () => { };
11
+ /**
12
+ * Helper to collect an async iterator into an array
13
+ */
14
+ async function collectAsyncIterator(it) {
15
+ const results = [];
16
+ for await (const result of it) {
17
+ results.push(result);
18
+ }
19
+ return results;
20
+ }
11
21
  /**
12
22
  * Asynchronous rename. No arguments other than a possible exception are given to the completion callback.
13
23
  */
@@ -556,3 +566,11 @@ export async function openAsBlob(path, options) {
556
566
  return new Blob([buffer], options);
557
567
  }
558
568
  openAsBlob;
569
+ export function glob(pattern, options, callback = nop) {
570
+ callback = typeof options == 'function' ? options : callback;
571
+ const it = promises.glob.call(this, pattern, typeof options === 'function' ? undefined : options);
572
+ collectAsyncIterator(it)
573
+ .then(results => callback(null, results ?? []))
574
+ .catch((e) => callback(e));
575
+ }
576
+ glob;
@@ -19,7 +19,7 @@ export declare class Dirent implements _Dirent {
19
19
  /**
20
20
  * A class representing a directory stream.
21
21
  */
22
- export declare class Dir implements _Dir {
22
+ export declare class Dir implements _Dir, AsyncIterator<Dirent> {
23
23
  readonly path: string;
24
24
  protected readonly context: V_Context;
25
25
  protected closed: boolean;
@@ -55,5 +55,6 @@ export declare class Dir implements _Dir {
55
55
  /**
56
56
  * Asynchronously iterates over the directory via `readdir(3)` until all entries have been read.
57
57
  */
58
- [Symbol.asyncIterator](): AsyncIterableIterator<Dirent>;
58
+ [Symbol.asyncIterator](): this;
59
+ [Symbol.asyncDispose](): Promise<void>;
59
60
  }
@@ -102,4 +102,7 @@ export class Dir {
102
102
  [Symbol.asyncIterator]() {
103
103
  return this;
104
104
  }
105
+ [Symbol.asyncDispose]() {
106
+ return Promise.resolve();
107
+ }
105
108
  }
@@ -4,5 +4,6 @@ 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, _synced, chroot } from './shared.js';
7
+ export { mount, umount, chroot, mountObject } from './shared.js';
8
+ export { /** @deprecated security */ mounts } from './shared.js';
8
9
  export { Stats, StatsFs, BigIntStatsFs } from '../stats.js';
@@ -4,5 +4,6 @@ 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, _synced, chroot } from './shared.js';
7
+ export { mount, umount, chroot, mountObject } from './shared.js';
8
+ export { /** @deprecated security */ mounts } from './shared.js';
8
9
  export { Stats, StatsFs, BigIntStatsFs } from '../stats.js';
@@ -10,8 +10,8 @@ import type { FileContents } from '../filesystem.js';
10
10
  import '../polyfills.js';
11
11
  import { BigIntStats, type Stats } from '../stats.js';
12
12
  import { Dir, Dirent } from './dir.js';
13
- import { type InternalOptions, type ReaddirOptions } from './shared.js';
14
13
  import { ReadStream, WriteStream } from './streams.js';
14
+ import type { InternalOptions, NullEnc, ReaddirOptions, ReaddirOptsI, ReaddirOptsU } from './types.js';
15
15
  export * as constants from './constants.js';
16
16
  export declare class FileHandle implements promises.FileHandle {
17
17
  protected context?: V_Context | undefined;
@@ -71,7 +71,9 @@ export declare class FileHandle implements promises.FileHandle {
71
71
  * @param length The number of bytes to read.
72
72
  * @param position The offset from the beginning of the file from which data should be read. If `null`, data will be read from the current position.
73
73
  */
74
- read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number | null): Promise<promises.FileReadResult<TBuffer>>;
74
+ read<T extends NodeJS.ArrayBufferView>(buffer: T, offset?: number, length?: number, position?: number | null): Promise<promises.FileReadResult<T>>;
75
+ read<T extends NodeJS.ArrayBufferView = Buffer>(buffer: T, options?: promises.FileReadOptions<T>): Promise<promises.FileReadResult<T>>;
76
+ read<T extends NodeJS.ArrayBufferView = Buffer>(options?: promises.FileReadOptions<T>): Promise<promises.FileReadResult<T>>;
75
77
  /**
76
78
  * Asynchronously reads the entire contents of a file. The underlying file will _not_ be closed automatically.
77
79
  * The `FileHandle` must have been opened for reading.
@@ -90,9 +92,6 @@ export declare class FileHandle implements promises.FileHandle {
90
92
  * While the `ReadableStream` will read the file to completion,
91
93
  * it will not close the `FileHandle` automatically.
92
94
  * User code must still call the `fileHandle.close()` method.
93
- *
94
- * @since v17.0.0
95
- * @experimental
96
95
  */
97
96
  readableWebStream(options?: promises.ReadableWebStreamOptions): TReadableStream<Uint8Array>;
98
97
  /**
@@ -113,17 +112,13 @@ export declare class FileHandle implements promises.FileHandle {
113
112
  * It is unsafe to call `write()` multiple times on the same file without waiting for the `Promise`
114
113
  * to be resolved (or rejected). For this scenario, `fs.createWriteStream` is strongly recommended.
115
114
  */
116
- write(data: FileContents, posOrOff?: number | null, lenOrEnc?: BufferEncoding | number, position?: number | null): Promise<{
117
- bytesWritten: number;
118
- buffer: FileContents;
119
- }>;
120
- write<TBuffer extends Uint8Array>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<{
115
+ write<T extends FileContents>(data: T, options?: number | null | {
116
+ offset?: number;
117
+ length?: number;
118
+ position?: number;
119
+ }, lenOrEnc?: BufferEncoding | number | null, position?: number | null): Promise<{
121
120
  bytesWritten: number;
122
- buffer: TBuffer;
123
- }>;
124
- write(data: string, position?: number, encoding?: BufferEncoding): Promise<{
125
- bytesWritten: number;
126
- buffer: string;
121
+ buffer: T;
127
122
  }>;
128
123
  /**
129
124
  * Asynchronously writes data to a file, replacing the file if it already exists. The underlying file will _not_ be closed automatically.
@@ -256,19 +251,19 @@ export declare function mkdir(this: V_Context, path: fs.PathLike, options?: fs.M
256
251
  * @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
257
252
  * @param options The encoding (or an object specifying the encoding), used as the encoding of the result. If not provided, `'utf8'`.
258
253
  */
259
- export declare function readdir(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
254
+ export declare function readdir(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{
260
255
  withFileTypes?: false;
261
- }) | BufferEncoding | null): Promise<string[]>;
256
+ }> | NullEnc): Promise<string[]>;
262
257
  export declare function readdir(this: V_Context, path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & {
263
258
  withFileTypes?: false;
264
259
  }): Promise<Buffer[]>;
265
- export declare function readdir(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
260
+ export declare function readdir(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{
266
261
  withFileTypes?: false;
267
- }) | BufferEncoding | null): Promise<string[] | Buffer[]>;
268
- export declare function readdir(this: V_Context, path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & {
262
+ }> | NullEnc): Promise<string[] | Buffer[]>;
263
+ export declare function readdir(this: V_Context, path: fs.PathLike, options: ReaddirOptsI<{
269
264
  withFileTypes: true;
270
- }): Promise<Dirent[]>;
271
- export declare function readdir(this: V_Context, path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null): Promise<string[] | Dirent[] | Buffer[]>;
265
+ }>): Promise<Dirent[]>;
266
+ export declare function readdir(this: V_Context, path: fs.PathLike, options?: ReaddirOptsU<fs.BufferEncodingOption> | NullEnc): Promise<string[] | Dirent[] | Buffer[]>;
272
267
  export declare function link(this: V_Context, targetPath: fs.PathLike, linkPath: fs.PathLike): Promise<void>;
273
268
  /**
274
269
  * `symlink`.
@@ -357,3 +352,11 @@ export declare function statfs(this: V_Context, path: fs.PathLike, opts: fs.Stat
357
352
  bigint: true;
358
353
  }): Promise<fs.BigIntStatsFs>;
359
354
  export declare function statfs(this: V_Context, path: fs.PathLike, opts?: fs.StatFsOptions): Promise<fs.StatsFs | fs.BigIntStatsFs>;
355
+ /**
356
+ * Retrieves the files matching the specified pattern.
357
+ * @todo Implement
358
+ */
359
+ export declare function glob(this: V_Context, pattern: string | string[]): NodeJS.AsyncIterator<string>;
360
+ export declare function glob(this: V_Context, pattern: string | string[], opt: fs.GlobOptionsWithFileTypes): NodeJS.AsyncIterator<Dirent>;
361
+ export declare function glob(this: V_Context, pattern: string | string[], opt: fs.GlobOptionsWithoutFileTypes): NodeJS.AsyncIterator<string>;
362
+ export declare function glob(this: V_Context, pattern: string | string[], opt: fs.GlobOptions): NodeJS.AsyncIterator<Dirent | string>;
@@ -147,19 +147,23 @@ export class FileHandle {
147
147
  await this.file.write(encodedData, 0, encodedData.length);
148
148
  emitChange('change', this.file.path);
149
149
  }
150
- /**
151
- * Asynchronously reads data from the file.
152
- * The `FileHandle` must have been opened for reading.
153
- * @param buffer The buffer that the data will be written to.
154
- * @param offset The offset in the buffer at which to start writing.
155
- * @param length The number of bytes to read.
156
- * @param position The offset from the beginning of the file from which data should be read. If `null`, data will be read from the current position.
157
- */
158
- read(buffer, offset, length, position) {
150
+ async read(buffer, offset, length, position) {
151
+ if (typeof offset == 'object' && offset != null) {
152
+ position = offset.position;
153
+ length = offset.length;
154
+ offset = offset.offset;
155
+ }
156
+ if (!ArrayBuffer.isView(buffer) && typeof buffer == 'object') {
157
+ position = buffer.position;
158
+ length = buffer.length;
159
+ offset = buffer.offset;
160
+ buffer = buffer.buffer;
161
+ }
159
162
  if (isNaN(+position)) {
160
163
  position = this.file.position;
161
164
  }
162
- return this.file.read(buffer, offset, length, position);
165
+ buffer || (buffer = new Uint8Array((await this.file.stat()).size));
166
+ return this.file.read(buffer, offset ?? undefined, length ?? undefined, position ?? undefined);
163
167
  }
164
168
  async readFile(_options) {
165
169
  const options = normalizeOptions(_options, null, 'r', 0o444);
@@ -180,9 +184,6 @@ export class FileHandle {
180
184
  * While the `ReadableStream` will read the file to completion,
181
185
  * it will not close the `FileHandle` automatically.
182
186
  * User code must still call the `fileHandle.close()` method.
183
- *
184
- * @since v17.0.0
185
- * @experimental
186
187
  */
187
188
  readableWebStream(options = {}) {
188
189
  // Note: using an arrow function to preserve `this`
@@ -230,27 +231,36 @@ export class FileHandle {
230
231
  }
231
232
  return opts?.bigint ? new BigIntStats(stats) : stats;
232
233
  }
233
- async write(data, posOrOff, lenOrEnc, position) {
234
+ /**
235
+ * Asynchronously writes `string` to the file.
236
+ * The `FileHandle` must have been opened for writing.
237
+ * It is unsafe to call `write()` multiple times on the same file without waiting for the `Promise`
238
+ * to be resolved (or rejected). For this scenario, `fs.createWriteStream` is strongly recommended.
239
+ */
240
+ async write(data, options, lenOrEnc, position) {
234
241
  let buffer, offset, length;
242
+ if (typeof options == 'object') {
243
+ lenOrEnc = options?.length;
244
+ position = options?.position;
245
+ options = options?.offset;
246
+ }
235
247
  if (typeof data === 'string') {
236
- // Signature 1: (fd, string, [position?, [encoding?]])
237
- position = typeof posOrOff === 'number' ? posOrOff : null;
248
+ position = typeof options === 'number' ? options : null;
238
249
  const encoding = typeof lenOrEnc === 'string' ? lenOrEnc : 'utf8';
239
250
  offset = 0;
240
251
  buffer = Buffer.from(data, encoding);
241
252
  length = buffer.length;
242
253
  }
243
254
  else {
244
- // Signature 2: (fd, buffer, offset, length, position?)
245
255
  buffer = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
246
- offset = posOrOff;
256
+ offset = options;
247
257
  length = lenOrEnc;
248
258
  position = typeof position === 'number' ? position : null;
249
259
  }
250
260
  position ?? (position = this.file.position);
251
261
  const bytesWritten = await this.file.write(buffer, offset, length, position);
252
262
  emitChange('change', this.file.path);
253
- return { buffer, bytesWritten };
263
+ return { buffer: data, bytesWritten };
254
264
  }
255
265
  /**
256
266
  * Asynchronously writes data to a file, replacing the file if it already exists. The underlying file will _not_ be closed automatically.
@@ -938,6 +948,9 @@ export function watch(filename, options = {}) {
938
948
  },
939
949
  return: cleanup,
940
950
  throw: cleanup,
951
+ [Symbol.asyncDispose]() {
952
+ return Promise.resolve();
953
+ },
941
954
  };
942
955
  },
943
956
  };
@@ -1088,3 +1101,35 @@ export async function statfs(path, opts) {
1088
1101
  const { fs } = resolveMount(path, this);
1089
1102
  return Promise.resolve(_statfs(fs, opts?.bigint));
1090
1103
  }
1104
+ export function glob(pattern, opt) {
1105
+ pattern = Array.isArray(pattern) ? pattern : [pattern];
1106
+ const { cwd = '/', withFileTypes = false, exclude = () => false } = opt || {};
1107
+ // Escape special characters in pattern
1108
+ const regexPatterns = pattern.map(p => {
1109
+ p = p
1110
+ .replace(/([.?+^$(){}|[\]/])/g, '$1')
1111
+ .replace(/\*\*/g, '.*')
1112
+ .replace(/\*/g, '[^/]*')
1113
+ .replace(/\?/g, '.');
1114
+ return new RegExp(`^${p}$`);
1115
+ });
1116
+ async function* recursiveList(dir) {
1117
+ const entries = await readdir(dir, { withFileTypes, encoding: 'utf8' });
1118
+ for (const entry of entries) {
1119
+ const fullPath = withFileTypes ? entry.path : dir + '/' + entry;
1120
+ if (exclude((withFileTypes ? entry : fullPath)))
1121
+ continue;
1122
+ /**
1123
+ * @todo it the pattern.source check correct?
1124
+ */
1125
+ if ((await stat(fullPath)).isDirectory() && regexPatterns.some(pattern => pattern.source.includes('.*'))) {
1126
+ yield* recursiveList(fullPath);
1127
+ }
1128
+ if (regexPatterns.some(pattern => pattern.test(fullPath.replace(/^\/+/g, '')))) {
1129
+ yield withFileTypes ? entry : fullPath.replace(/^\/+/g, '');
1130
+ }
1131
+ }
1132
+ }
1133
+ return recursiveList(cwd);
1134
+ }
1135
+ glob;
@@ -1,4 +1,4 @@
1
- import type { BigIntStatsFs, StatsFs } from 'node:fs';
1
+ import type * as fs from 'node:fs';
2
2
  import { type BoundContext, type V_Context } from '../context.js';
3
3
  import { ErrnoError } from '../error.js';
4
4
  import type { File } from '../file.js';
@@ -48,12 +48,6 @@ export interface ResolvedMount {
48
48
  * @internal @hidden
49
49
  */
50
50
  export declare function resolveMount(path: string, ctx: V_Context): ResolvedMount;
51
- /**
52
- * Wait for all file systems to be ready and synced.
53
- * May be removed at some point.
54
- * @experimental @internal
55
- */
56
- export declare function _synced(): Promise<void>;
57
51
  /**
58
52
  * Reverse maps the paths in text from the mounted FileSystem to the global path
59
53
  * @internal @hidden
@@ -71,23 +65,7 @@ export declare function mountObject(mounts: MountObject): void;
71
65
  /**
72
66
  * @internal @hidden
73
67
  */
74
- export declare function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T extends true ? BigIntStatsFs : StatsFs;
75
- /**
76
- * Options used for caching, among other things.
77
- * @internal @hidden *UNSTABLE*
78
- */
79
- export interface InternalOptions {
80
- /**
81
- * If true, then this readdir was called from another function.
82
- * In this case, don't clear the cache when done.
83
- * @internal *UNSTABLE*
84
- */
85
- _isIndirect?: boolean;
86
- }
87
- export interface ReaddirOptions extends InternalOptions {
88
- withFileTypes?: boolean;
89
- recursive?: boolean;
90
- }
68
+ export declare function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T extends true ? fs.BigIntStatsFs : fs.StatsFs;
91
69
  /**
92
70
  * Change the root path
93
71
  * @param inPlace if true, this changes the root for the current context instead of creating a new one (if associated with a context).
@@ -85,15 +85,6 @@ export function resolveMount(path, ctx) {
85
85
  }
86
86
  throw new ErrnoError(Errno.EIO, 'No file system');
87
87
  }
88
- /**
89
- * Wait for all file systems to be ready and synced.
90
- * May be removed at some point.
91
- * @experimental @internal
92
- */
93
- export async function _synced() {
94
- await Promise.all([...mounts.values()].map(m => m.ready()));
95
- return;
96
- }
97
88
  /**
98
89
  * Reverse maps the paths in text from the mounted FileSystem to the global path
99
90
  * @internal @hidden
@@ -2,13 +2,14 @@ import type * as Node from 'node:fs';
2
2
  import { Readable, Writable } from 'readable-stream';
3
3
  import type { Callback } from '../utils.js';
4
4
  export declare class ReadStream extends Readable implements Node.ReadStream {
5
- close(callback?: Callback): void;
5
+ close(callback?: Callback<[void], null>): void;
6
+ wrap(oldStream: NodeJS.ReadableStream): this;
6
7
  bytesRead: number;
7
8
  path: string | Buffer;
8
9
  pending: boolean;
9
10
  }
10
11
  export declare class WriteStream extends Writable implements Node.WriteStream {
11
- close(callback?: Callback): void;
12
+ close(callback?: Callback<[void], null>): void;
12
13
  bytesWritten: number;
13
14
  path: string | Buffer;
14
15
  pending: boolean;
@@ -5,19 +5,23 @@ export class ReadStream extends Readable {
5
5
  try {
6
6
  super.destroy();
7
7
  super.emit('close');
8
- callback();
8
+ callback(null);
9
9
  }
10
10
  catch (err) {
11
11
  callback(new ErrnoError(Errno.EIO, err.toString()));
12
12
  }
13
13
  }
14
+ wrap(oldStream) {
15
+ super.wrap(oldStream);
16
+ return this;
17
+ }
14
18
  }
15
19
  export class WriteStream extends Writable {
16
20
  close(callback = () => null) {
17
21
  try {
18
22
  super.destroy();
19
23
  super.emit('close');
20
- callback();
24
+ callback(null);
21
25
  }
22
26
  catch (err) {
23
27
  callback(new ErrnoError(Errno.EIO, err.toString()));
@@ -3,8 +3,8 @@ 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';
7
6
  import type { V_Context } from '../context.js';
7
+ import type { ReaddirOptsI, ReaddirOptsU, InternalOptions, ReaddirOptions, NullEnc } from './types.js';
8
8
  export declare function renameSync(this: V_Context, oldPath: fs.PathLike, newPath: fs.PathLike): void;
9
9
  /**
10
10
  * Test whether or not `path` exists by checking with the file system.
@@ -112,19 +112,19 @@ export declare function mkdirSync(this: V_Context, path: fs.PathLike, options?:
112
112
  recursive?: false;
113
113
  }) | null): void;
114
114
  export declare function mkdirSync(this: V_Context, path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): string | undefined;
115
- export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
115
+ export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{
116
116
  withFileTypes?: false;
117
- }) | BufferEncoding | null): string[];
117
+ }> | NullEnc): string[];
118
118
  export declare function readdirSync(this: V_Context, path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & {
119
119
  withFileTypes?: false;
120
120
  }): Buffer[];
121
- export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & {
121
+ export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{
122
122
  withFileTypes?: false;
123
- }) | BufferEncoding | null): string[] | Buffer[];
124
- export declare function readdirSync(this: V_Context, path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & {
123
+ }> | NullEnc): string[] | Buffer[];
124
+ export declare function readdirSync(this: V_Context, path: fs.PathLike, options: ReaddirOptsI<{
125
125
  withFileTypes: true;
126
- }): Dirent[];
127
- export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null): string[] | Dirent[] | Buffer[];
126
+ }>): Dirent[];
127
+ export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: ReaddirOptsU<fs.BufferEncodingOption> | NullEnc): string[] | Dirent[] | Buffer[];
128
128
  export declare function linkSync(this: V_Context, targetPath: fs.PathLike, linkPath: fs.PathLike): void;
129
129
  /**
130
130
  * Synchronous `symlink`.
@@ -219,3 +219,10 @@ export declare function statfsSync(this: V_Context, path: fs.PathLike, options:
219
219
  bigint: true;
220
220
  }): fs.BigIntStatsFs;
221
221
  export declare function statfsSync(this: V_Context, path: fs.PathLike, options?: fs.StatFsOptions): fs.StatsFs | fs.BigIntStatsFs;
222
+ /**
223
+ * Retrieves the files matching the specified pattern.
224
+ */
225
+ export declare function globSync(pattern: string | string[]): string[];
226
+ export declare function globSync(pattern: string | string[], options: fs.GlobOptionsWithFileTypes): Dirent[];
227
+ export declare function globSync(pattern: string | string[], options: fs.GlobOptionsWithoutFileTypes): string[];
228
+ export declare function globSync(pattern: string | string[], options: fs.GlobOptions): Dirent[] | string[];
@@ -796,3 +796,37 @@ export function statfsSync(path, options) {
796
796
  const { fs } = resolveMount(path, this);
797
797
  return _statfs(fs, options?.bigint);
798
798
  }
799
+ export function globSync(pattern, options = {}) {
800
+ pattern = Array.isArray(pattern) ? pattern : [pattern];
801
+ const { cwd = '/', withFileTypes = false, exclude = () => false } = options;
802
+ // Escape special characters in pattern
803
+ const regexPatterns = pattern.map(p => {
804
+ p = p
805
+ .replace(/([.?+^$(){}|[\]/])/g, '\\$1')
806
+ .replace(/\*\*/g, '.*')
807
+ .replace(/\*/g, '[^/]*')
808
+ .replace(/\?/g, '.');
809
+ return new RegExp(`^${p}$`);
810
+ });
811
+ const results = [];
812
+ function recursiveList(dir) {
813
+ const entries = readdirSync(dir, { withFileTypes, encoding: 'utf8' });
814
+ for (const entry of entries) {
815
+ const fullPath = withFileTypes ? entry.path : dir + '/' + entry;
816
+ if (exclude((withFileTypes ? entry : fullPath)))
817
+ continue;
818
+ /**
819
+ * @todo it the pattern.source check correct?
820
+ */
821
+ if (statSync(fullPath).isDirectory() && regexPatterns.some(pattern => pattern.source.includes('.*'))) {
822
+ recursiveList(fullPath);
823
+ }
824
+ if (regexPatterns.some(pattern => pattern.test(fullPath.replace(/^\/+/g, '')))) {
825
+ results.push(withFileTypes ? entry.path : fullPath.replace(/^\/+/g, ''));
826
+ }
827
+ }
828
+ }
829
+ recursiveList(cwd);
830
+ return results;
831
+ }
832
+ globSync;
package/dist/index.d.ts CHANGED
@@ -12,7 +12,6 @@ export * from './config.js';
12
12
  export * from './context.js';
13
13
  export * from './credentials.js';
14
14
  export * from './devices.js';
15
- export { default as devices } from './devices.js';
16
15
  export * from './file.js';
17
16
  export * from './filesystem.js';
18
17
  export * from './inode.js';
package/dist/index.js CHANGED
@@ -12,7 +12,6 @@ export * from './config.js';
12
12
  export * from './context.js';
13
13
  export * from './credentials.js';
14
14
  export * from './devices.js';
15
- export { default as devices } from './devices.js';
16
15
  export * from './file.js';
17
16
  export * from './filesystem.js';
18
17
  export * from './inode.js';
package/dist/utils.d.ts CHANGED
@@ -49,7 +49,7 @@ export declare function decodeDirListing(data: Uint8Array): Record<string, bigin
49
49
  * @hidden
50
50
  */
51
51
  export declare function encodeDirListing(data: Record<string, bigint>): Uint8Array;
52
- export type Callback<Args extends unknown[] = []> = (e?: ErrnoError, ...args: OptionalTuple<Args>) => unknown;
52
+ export type Callback<Args extends unknown[] = [], NoError = undefined | void> = (e: ErrnoError | NoError, ...args: OptionalTuple<Args>) => unknown;
53
53
  /**
54
54
  * Normalizes a mode
55
55
  * @internal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -58,7 +58,8 @@
58
58
  "build": "tsc -p tsconfig.json",
59
59
  "build:docs": "typedoc",
60
60
  "dev": "npm run build -- --watch",
61
- "prepublishOnly": "npm run build"
61
+ "prepublishOnly": "npm run build",
62
+ "postinstall": "patch-package"
62
63
  },
63
64
  "lint-staged": {
64
65
  "*": [
@@ -66,7 +67,7 @@
66
67
  ]
67
68
  },
68
69
  "dependencies": {
69
- "@types/node": "^20.16.10",
70
+ "@types/node": "^22.10.1",
70
71
  "@types/readable-stream": "^4.0.10",
71
72
  "buffer": "^6.0.3",
72
73
  "eventemitter3": "^5.0.1",
@@ -83,6 +84,7 @@
83
84
  "eslint": "^9.15.0",
84
85
  "globals": "^15.9.0",
85
86
  "lint-staged": "^15.2.7",
87
+ "patch-package": "^8.0.0",
86
88
  "prettier": "^3.2.5",
87
89
  "tsx": "^4.19.1",
88
90
  "typedoc": "^0.27.1",
package/readme.md CHANGED
@@ -174,31 +174,31 @@ You can create your own devices by implementing a `DeviceDriver`. For example, t
174
174
  ```ts
175
175
  const customNullDevice = {
176
176
  name: 'custom_null',
177
+ // only 1 can exist per DeviceFS
178
+ singleton: true,
179
+ // optional if false
177
180
  isBuffered: false,
178
181
  read() {
179
182
  return 0;
180
183
  },
181
- write() {},
184
+ write() {
185
+ return 0;
186
+ },
182
187
  };
183
188
  ```
184
189
 
185
190
  Note the actual implementation's write is slightly more complicated since it adds to the file position. You can find more information on the docs.
186
191
 
187
- Finally, if you'd like to use your custom device with the file system, you can use so through the aptly named `DeviceFS`.
192
+ Finally, if you'd like to use your custom device with the file system:
188
193
 
189
194
  ```ts
190
- const devfs = fs.mounts.get('/dev') as DeviceFS;
191
- devfs.createDevice('/custom', customNullDevice);
195
+ import { addDevice, fs } from '@zenfs/core';
196
+
197
+ addDevice(customNullDevice);
192
198
 
193
199
  fs.writeFileSync('/dev/custom', 'This gets discarded.');
194
200
  ```
195
201
 
196
- In the above example, `createDevice` works relative to the `DeviceFS` mount point.
197
-
198
- Additionally, a type assertion (` as ...`) is used since `fs.mounts` does not keep track of which file system type is mapped to which mount point. Doing so would create significant maintenance costs due to the complexity of implementing it.
199
-
200
- If you would like to see a more intuitive way adding custom devices (e.g. `fs.mknod`), please feel free to open an issue for a feature request.
201
-
202
202
  ## Using with bundlers
203
203
 
204
204
  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.
@@ -13,8 +13,6 @@ for (const file of testFiles) {
13
13
  fs.writeFileSync(`${testDirPath}/${file}`, 'Sample content');
14
14
  }
15
15
 
16
- await fs._synced();
17
-
18
16
  suite('Dirent', () => {
19
17
  test('name and parentPath getters', async () => {
20
18
  const stats = await fs.promises.lstat(testFile);
@@ -18,8 +18,6 @@ for (const dir of testDirectories) {
18
18
  }
19
19
  }
20
20
 
21
- await fs._synced();
22
-
23
21
  suite('Directories', () => {
24
22
  test('mkdir', async () => {
25
23
  await fs.promises.mkdir('/one', 0o755);
@@ -33,7 +33,7 @@ suite('ReadStream', () => {
33
33
  closed = true;
34
34
  });
35
35
  readStream.close(err => {
36
- assert.strictEqual(err, undefined);
36
+ assert.equal(err, null);
37
37
  assert(closed);
38
38
  done();
39
39
  });
@@ -58,10 +58,10 @@ suite('ReadStream', () => {
58
58
  test('ReadStream close method can be called multiple times', (_, done) => {
59
59
  const readStream = new fs.ReadStream();
60
60
  readStream.close(err => {
61
- assert.strictEqual(err, undefined);
61
+ assert.equal(err, null);
62
62
  // Call close again
63
63
  readStream.close(err2 => {
64
- assert.strictEqual(err2, undefined);
64
+ assert.equal(err2, null);
65
65
  done();
66
66
  });
67
67
  });
@@ -94,7 +94,7 @@ suite('WriteStream', () => {
94
94
  closed = true;
95
95
  });
96
96
  writeStream.close(err => {
97
- assert.strictEqual(err, undefined);
97
+ assert.equal(err, null);
98
98
  assert(closed);
99
99
  done();
100
100
  });
@@ -119,10 +119,10 @@ suite('WriteStream', () => {
119
119
  test('WriteStream close method can be called multiple times', (_, done) => {
120
120
  const writeStream = new fs.WriteStream();
121
121
  writeStream.close(err => {
122
- assert.strictEqual(err, undefined);
122
+ assert.equal(err, null);
123
123
  // Call close again
124
124
  writeStream.close(err2 => {
125
- assert.strictEqual(err2, undefined);
125
+ assert.equal(err2, null);
126
126
  done();
127
127
  });
128
128
  });