@zenfs/core 0.16.4 → 0.17.1

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 (80) hide show
  1. package/dist/backends/backend.d.ts +3 -4
  2. package/dist/backends/fetch.d.ts +8 -3
  3. package/dist/backends/fetch.js +3 -2
  4. package/dist/backends/{index/fs.d.ts → file_index.d.ts} +49 -10
  5. package/dist/backends/{index/fs.js → file_index.js} +84 -5
  6. package/dist/backends/memory.d.ts +6 -1
  7. package/dist/backends/memory.js +2 -1
  8. package/dist/backends/overlay.d.ts +16 -16
  9. package/dist/backends/overlay.js +59 -82
  10. package/dist/backends/port/fs.d.ts +6 -2
  11. package/dist/backends/port/fs.js +4 -2
  12. package/dist/backends/store/fs.js +484 -304
  13. package/dist/backends/store/simple.js +5 -1
  14. package/dist/backends/store/store.d.ts +4 -1
  15. package/dist/backends/store/store.js +9 -5
  16. package/dist/browser.min.js +4 -4
  17. package/dist/browser.min.js.map +4 -4
  18. package/dist/config.d.ts +3 -3
  19. package/dist/emulation/async.d.ts +1 -4
  20. package/dist/emulation/async.js +9 -4
  21. package/dist/emulation/dir.d.ts +4 -0
  22. package/dist/emulation/dir.js +8 -6
  23. package/dist/emulation/promises.d.ts +1 -3
  24. package/dist/emulation/promises.js +25 -2
  25. package/dist/emulation/sync.js +0 -1
  26. package/dist/emulation/watchers.d.ts +9 -4
  27. package/dist/emulation/watchers.js +7 -0
  28. package/dist/file.d.ts +17 -1
  29. package/dist/file.js +86 -1
  30. package/dist/filesystem.d.ts +4 -67
  31. package/dist/filesystem.js +2 -313
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +2 -2
  34. package/dist/mixins/async.d.ts +39 -0
  35. package/dist/mixins/async.js +216 -0
  36. package/dist/mixins/index.d.ts +4 -0
  37. package/dist/mixins/index.js +4 -0
  38. package/dist/mixins/mutexed.d.ts +33 -0
  39. package/dist/mixins/mutexed.js +465 -0
  40. package/dist/mixins/readonly.d.ts +25 -0
  41. package/dist/mixins/readonly.js +57 -0
  42. package/dist/mixins/shared.d.ts +12 -0
  43. package/dist/mixins/shared.js +4 -0
  44. package/dist/mixins/sync.d.ts +6 -0
  45. package/dist/mixins/sync.js +43 -0
  46. package/dist/utils.d.ts +0 -5
  47. package/dist/utils.js +0 -7
  48. package/package.json +3 -2
  49. package/src/backends/backend.ts +3 -4
  50. package/src/backends/fetch.ts +7 -3
  51. package/src/backends/{index/fs.ts → file_index.ts} +106 -8
  52. package/src/backends/memory.ts +5 -1
  53. package/src/backends/overlay.ts +64 -90
  54. package/src/backends/port/fs.ts +7 -2
  55. package/src/backends/{index/readme.md → readme.md} +1 -1
  56. package/src/backends/store/fs.ts +97 -155
  57. package/src/backends/store/simple.ts +5 -1
  58. package/src/backends/store/store.ts +10 -5
  59. package/src/config.ts +3 -1
  60. package/src/emulation/async.ts +20 -9
  61. package/src/emulation/dir.ts +19 -16
  62. package/src/emulation/promises.ts +28 -6
  63. package/src/emulation/sync.ts +1 -2
  64. package/src/emulation/watchers.ts +10 -4
  65. package/src/file.ts +94 -1
  66. package/src/filesystem.ts +5 -368
  67. package/src/index.ts +2 -2
  68. package/src/mixins/async.ts +211 -0
  69. package/src/mixins/index.ts +4 -0
  70. package/src/mixins/mutexed.ts +245 -0
  71. package/src/mixins/readonly.ts +97 -0
  72. package/src/mixins/shared.ts +20 -0
  73. package/src/mixins/sync.ts +59 -0
  74. package/src/utils.ts +0 -8
  75. package/dist/backends/index/index.d.ts +0 -43
  76. package/dist/backends/index/index.js +0 -83
  77. package/dist/backends/locked.d.ts +0 -92
  78. package/dist/backends/locked.js +0 -487
  79. package/src/backends/index/index.ts +0 -104
  80. package/src/backends/locked.ts +0 -264
@@ -57,15 +57,22 @@ export class Dir implements _Dir {
57
57
 
58
58
  protected _entries: Dirent[] = [];
59
59
 
60
- constructor(public readonly path: string) {}
60
+ /**
61
+ * @internal
62
+ */
63
+ public async _loadEntries() {
64
+ this._entries ??= await readdir(this.path, { withFileTypes: true });
65
+ }
66
+
67
+ public constructor(public readonly path: string) {}
61
68
 
62
69
  /**
63
70
  * Asynchronously close the directory's underlying resource handle.
64
71
  * Subsequent reads will result in errors.
65
72
  */
66
- close(): Promise<void>;
67
- close(cb: Callback): void;
68
- close(cb?: Callback): void | Promise<void> {
73
+ public close(): Promise<void>;
74
+ public close(cb: Callback): void;
75
+ public close(cb?: Callback): void | Promise<void> {
69
76
  this.closed = true;
70
77
  if (!cb) {
71
78
  return Promise.resolve();
@@ -77,14 +84,12 @@ export class Dir implements _Dir {
77
84
  * Synchronously close the directory's underlying resource handle.
78
85
  * Subsequent reads will result in errors.
79
86
  */
80
- closeSync(): void {
87
+ public closeSync(): void {
81
88
  this.closed = true;
82
89
  }
83
90
 
84
91
  protected async _read(): Promise<Dirent | null> {
85
- if (!this._entries) {
86
- this._entries = await readdir(this.path, { withFileTypes: true });
87
- }
92
+ await this._loadEntries();
88
93
  if (!this._entries.length) {
89
94
  return null;
90
95
  }
@@ -96,9 +101,9 @@ export class Dir implements _Dir {
96
101
  * After the read is completed, a value is returned that will be resolved with an `Dirent`, or `null` if there are no more directory entries to read.
97
102
  * Directory entries returned by this function are in no particular order as provided by the operating system's underlying directory mechanisms.
98
103
  */
99
- read(): Promise<Dirent | null>;
100
- read(cb: Callback<[Dirent | null]>): void;
101
- read(cb?: Callback<[Dirent | null]>): void | Promise<Dirent | null> {
104
+ public read(): Promise<Dirent | null>;
105
+ public read(cb: Callback<[Dirent | null]>): void;
106
+ public read(cb?: Callback<[Dirent | null]>): void | Promise<Dirent | null> {
102
107
  if (!cb) {
103
108
  return this._read();
104
109
  }
@@ -111,10 +116,8 @@ export class Dir implements _Dir {
111
116
  * If there are no more directory entries to read, null will be returned.
112
117
  * Directory entries returned by this function are in no particular order as provided by the operating system's underlying directory mechanisms.
113
118
  */
114
- readSync(): Dirent | null {
115
- if (!this._entries) {
116
- this._entries = readdirSync(this.path, { withFileTypes: true });
117
- }
119
+ public readSync(): Dirent | null {
120
+ this._entries ??= readdirSync(this.path, { withFileTypes: true });
118
121
  if (!this._entries.length) {
119
122
  return null;
120
123
  }
@@ -124,7 +127,7 @@ export class Dir implements _Dir {
124
127
  /**
125
128
  * Asynchronously iterates over the directory via `readdir(3)` until all entries have been read.
126
129
  */
127
- [Symbol.asyncIterator](): AsyncIterableIterator<Dirent> {
130
+ public [Symbol.asyncIterator](): AsyncIterableIterator<Dirent> {
128
131
  const _this = this;
129
132
 
130
133
  return {
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-redundant-type-constituents */
1
2
  import { Buffer } from 'buffer';
2
3
  import type * as fs from 'node:fs';
3
4
  import type * as promises from 'node:fs/promises';
@@ -18,6 +19,7 @@ import { Dir, Dirent } from './dir.js';
18
19
  import { dirname, join, parse } from './path.js';
19
20
  import { _statfs, cred, fd2file, fdMap, file2fd, fixError, mounts, resolveMount } from './shared.js';
20
21
  import { ReadStream, WriteStream } from './streams.js';
22
+ import { FSWatcher } from './watchers.js';
21
23
  export * as constants from './constants.js';
22
24
 
23
25
  export class FileHandle implements promises.FileHandle {
@@ -870,14 +872,31 @@ export async function realpath(path: fs.PathLike, options?: fs.EncodingOption |
870
872
  }
871
873
  realpath satisfies typeof promises.realpath;
872
874
 
873
- /**
874
- * @todo Implement
875
- */
876
875
  export function watch(filename: fs.PathLike, options?: fs.WatchOptions | BufferEncoding): AsyncIterable<FileChangeInfo<string>>;
877
876
  export function watch(filename: fs.PathLike, options: fs.WatchOptions | fs.BufferEncodingOption): AsyncIterable<FileChangeInfo<Buffer>>;
878
877
  export function watch(filename: fs.PathLike, options?: fs.WatchOptions | string): AsyncIterable<FileChangeInfo<string>> | AsyncIterable<FileChangeInfo<Buffer>>;
879
- export function watch(filename: fs.PathLike, options: fs.WatchOptions | string = {}): AsyncIterable<FileChangeInfo<string>> | AsyncIterable<FileChangeInfo<Buffer>> {
880
- throw ErrnoError.With('ENOSYS', filename.toString(), 'watch');
878
+ export function watch<T extends string | Buffer>(filename: fs.PathLike, options: fs.WatchOptions | string = {}): AsyncIterable<FileChangeInfo<T>> {
879
+ return {
880
+ [Symbol.asyncIterator](): AsyncIterator<FileChangeInfo<T>> {
881
+ const watcher = new FSWatcher<T>(typeof options != 'string' ? options : { encoding: options as BufferEncoding | 'buffer' });
882
+
883
+ function withDone(done: boolean) {
884
+ return function () {
885
+ const event = Promise.withResolvers<IteratorResult<FileChangeInfo<T>>>();
886
+ watcher.on('change', (eventType, filename) => {
887
+ event.resolve({ value: { eventType, filename }, done });
888
+ });
889
+ return event.promise;
890
+ };
891
+ }
892
+
893
+ return {
894
+ next: withDone(false),
895
+ return: withDone(true),
896
+ throw: withDone(true),
897
+ };
898
+ },
899
+ };
881
900
  }
882
901
  watch satisfies typeof promises.watch;
883
902
 
@@ -970,10 +989,13 @@ copyFile satisfies typeof promises.copyFile;
970
989
  * @param path The path to the directory.
971
990
  * @param options Options for opening the directory.
972
991
  * @returns A `Dir` object representing the opened directory.
992
+ * @todo Use options
973
993
  */
974
994
  export async function opendir(path: fs.PathLike, options?: fs.OpenDirOptions): Promise<Dir> {
975
995
  path = normalizePath(path);
976
- return new Dir(path);
996
+ const dir = new Dir(path);
997
+ await dir._loadEntries();
998
+ return dir;
977
999
  }
978
1000
  opendir satisfies typeof promises.opendir;
979
1001
 
@@ -4,7 +4,7 @@ import { Errno, ErrnoError } from '../error.js';
4
4
  import type { File } from '../file.js';
5
5
  import { isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../file.js';
6
6
  import type { FileContents } from '../filesystem.js';
7
- import { BigIntStats, FileType, type Stats } from '../stats.js';
7
+ import { BigIntStats, type Stats } from '../stats.js';
8
8
  import { normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
9
9
  import { COPYFILE_EXCL, F_OK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK } from './constants.js';
10
10
  import { Dir, Dirent } from './dir.js';
@@ -199,7 +199,6 @@ function _readFileSync(fname: string, flag: string, resolveSymlinks: boolean): U
199
199
  // Allocate buffer.
200
200
  const data = new Uint8Array(stat.size);
201
201
  file.readSync(data, 0, stat.size, 0);
202
- file.closeSync();
203
202
  return data;
204
203
  }
205
204
 
@@ -5,11 +5,11 @@ import { ErrnoError } from '../error.js';
5
5
 
6
6
  class Watcher<TEvents extends Record<string, unknown[]> = Record<string, unknown[]>> extends EventEmitter<TEvents> implements NodeEventEmitter {
7
7
  /* eslint-disable @typescript-eslint/no-explicit-any */
8
- public off<T extends EventEmitter.EventNames<TEvents>>(event: T, fn?: ((...args: any[]) => void) | undefined, context?: any, once?: boolean | undefined): this {
8
+ public off<T extends EventEmitter.EventNames<TEvents>>(event: T, fn?: (...args: any[]) => void, context?: any, once?: boolean): this {
9
9
  return super.off<T>(event, fn as EventEmitter.EventListener<TEvents, T>, context, once);
10
10
  }
11
11
 
12
- public removeListener<T extends EventEmitter.EventNames<TEvents>>(event: T, fn?: ((...args: any[]) => void) | undefined, context?: any, once?: boolean | undefined): this {
12
+ public removeListener<T extends EventEmitter.EventNames<TEvents>>(event: T, fn?: (...args: any[]) => void, context?: any, once?: boolean): this {
13
13
  return super.removeListener<T>(event, fn as EventEmitter.EventListener<TEvents, T>, context, once);
14
14
  }
15
15
  /* eslint-enable @typescript-eslint/no-explicit-any */
@@ -43,14 +43,20 @@ class Watcher<TEvents extends Record<string, unknown[]> = Record<string, unknown
43
43
  }
44
44
  }
45
45
 
46
- export class FSWatcher
46
+ /**
47
+ * @todo Actually emit events
48
+ */
49
+ export class FSWatcher<T extends string | Buffer = string | Buffer>
47
50
  extends Watcher<{
48
- change: [eventType: string, filename: string | Buffer];
51
+ change: [eventType: fs.WatchEventType, filename: T];
49
52
  close: [];
50
53
  error: [error: Error];
51
54
  }>
52
55
  implements fs.FSWatcher
53
56
  {
57
+ public constructor(public readonly options: fs.WatchOptions) {
58
+ super();
59
+ }
54
60
  public close(): void {}
55
61
  }
56
62
 
package/src/file.ts CHANGED
@@ -323,11 +323,23 @@ export abstract class File {
323
323
  * An implementation of the File interface that operates on a file that is
324
324
  * completely in-memory. PreloadFiles are backed by a Uint8Array.
325
325
  *
326
- * @todo 'close' lever that disables functionality once closed.
327
326
  */
328
327
  export class PreloadFile<FS extends FileSystem> extends File {
328
+ /**
329
+ * Current position
330
+ */
329
331
  protected _position: number = 0;
332
+
333
+ /**
334
+ * Whether the file has changes which have not been written to the FS
335
+ */
330
336
  protected dirty: boolean = false;
337
+
338
+ /**
339
+ * Whether the file is open or closed
340
+ */
341
+ protected closed: boolean = false;
342
+
331
343
  /**
332
344
  * Creates a file with the given path and, optionally, the given contents. Note
333
345
  * that, if contents is specified, it will be mutated by the file!
@@ -403,6 +415,9 @@ export class PreloadFile<FS extends FileSystem> extends File {
403
415
  }
404
416
 
405
417
  public async sync(): Promise<void> {
418
+ if (this.closed) {
419
+ throw ErrnoError.With('EBADF', this.path, 'File.sync');
420
+ }
406
421
  if (!this.dirty) {
407
422
  return;
408
423
  }
@@ -411,6 +426,9 @@ export class PreloadFile<FS extends FileSystem> extends File {
411
426
  }
412
427
 
413
428
  public syncSync(): void {
429
+ if (this.closed) {
430
+ throw ErrnoError.With('EBADF', this.path, 'File.sync');
431
+ }
414
432
  if (!this.dirty) {
415
433
  return;
416
434
  }
@@ -419,17 +437,48 @@ export class PreloadFile<FS extends FileSystem> extends File {
419
437
  }
420
438
 
421
439
  public async close(): Promise<void> {
440
+ if (this.closed) {
441
+ throw ErrnoError.With('EBADF', this.path, 'File.close');
442
+ }
422
443
  await this.sync();
444
+ this.dispose();
423
445
  }
424
446
 
425
447
  public closeSync(): void {
448
+ if (this.closed) {
449
+ throw ErrnoError.With('EBADF', this.path, 'File.close');
450
+ }
426
451
  this.syncSync();
452
+ this.dispose();
453
+ }
454
+
455
+ /**
456
+ * Cleans up
457
+ * This will *not* sync the file data to the FS
458
+ */
459
+ protected dispose(force?: boolean): void {
460
+ if (this.closed) {
461
+ throw ErrnoError.With('EBADF', this.path, 'File.dispose');
462
+ }
463
+ if (this.dirty && !force) {
464
+ throw ErrnoError.With('EBUSY', this.path, 'File.dispose');
465
+ }
466
+
467
+ // @ts-expect-error 2790
468
+ delete this._buffer;
469
+ // @ts-expect-error 2790
470
+ delete this.stats;
471
+
472
+ this.closed = true;
427
473
  }
428
474
 
429
475
  /**
430
476
  * Asynchronous `stat`.
431
477
  */
432
478
  public stat(): Promise<Stats> {
479
+ if (this.closed) {
480
+ throw ErrnoError.With('EBADF', this.path, 'File.stat');
481
+ }
433
482
  return Promise.resolve(new Stats(this.stats));
434
483
  }
435
484
 
@@ -437,10 +486,16 @@ export class PreloadFile<FS extends FileSystem> extends File {
437
486
  * Synchronous `stat`.
438
487
  */
439
488
  public statSync(): Stats {
489
+ if (this.closed) {
490
+ throw ErrnoError.With('EBADF', this.path, 'File.stat');
491
+ }
440
492
  return new Stats(this.stats);
441
493
  }
442
494
 
443
495
  protected _truncate(length: number): void {
496
+ if (this.closed) {
497
+ throw ErrnoError.With('EBADF', this.path, 'File.truncate');
498
+ }
444
499
  this.dirty = true;
445
500
  if (!isWriteable(this.flag)) {
446
501
  throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
@@ -476,6 +531,9 @@ export class PreloadFile<FS extends FileSystem> extends File {
476
531
  }
477
532
 
478
533
  protected _write(buffer: Uint8Array, offset: number = 0, length: number = this.stats.size, position: number = this.position): number {
534
+ if (this.closed) {
535
+ throw ErrnoError.With('EBADF', this.path, 'File.write');
536
+ }
479
537
  this.dirty = true;
480
538
  if (!isWriteable(this.flag)) {
481
539
  throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
@@ -540,6 +598,9 @@ export class PreloadFile<FS extends FileSystem> extends File {
540
598
  }
541
599
 
542
600
  protected _read(buffer: ArrayBufferView, offset: number = 0, length: number = this.stats.size, position?: number): number {
601
+ if (this.closed) {
602
+ throw ErrnoError.With('EBADF', this.path, 'File.read');
603
+ }
543
604
  if (!isReadable(this.flag)) {
544
605
  throw new ErrnoError(Errno.EPERM, 'File not opened with a readable mode.');
545
606
  }
@@ -599,6 +660,9 @@ export class PreloadFile<FS extends FileSystem> extends File {
599
660
  * @param mode the mode
600
661
  */
601
662
  public async chmod(mode: number): Promise<void> {
663
+ if (this.closed) {
664
+ throw ErrnoError.With('EBADF', this.path, 'File.chmod');
665
+ }
602
666
  this.dirty = true;
603
667
  this.stats.chmod(mode);
604
668
  await this.sync();
@@ -609,6 +673,9 @@ export class PreloadFile<FS extends FileSystem> extends File {
609
673
  * @param mode
610
674
  */
611
675
  public chmodSync(mode: number): void {
676
+ if (this.closed) {
677
+ throw ErrnoError.With('EBADF', this.path, 'File.chmod');
678
+ }
612
679
  this.dirty = true;
613
680
  this.stats.chmod(mode);
614
681
  this.syncSync();
@@ -620,6 +687,9 @@ export class PreloadFile<FS extends FileSystem> extends File {
620
687
  * @param gid
621
688
  */
622
689
  public async chown(uid: number, gid: number): Promise<void> {
690
+ if (this.closed) {
691
+ throw ErrnoError.With('EBADF', this.path, 'File.chown');
692
+ }
623
693
  this.dirty = true;
624
694
  this.stats.chown(uid, gid);
625
695
  await this.sync();
@@ -631,12 +701,18 @@ export class PreloadFile<FS extends FileSystem> extends File {
631
701
  * @param gid
632
702
  */
633
703
  public chownSync(uid: number, gid: number): void {
704
+ if (this.closed) {
705
+ throw ErrnoError.With('EBADF', this.path, 'File.chown');
706
+ }
634
707
  this.dirty = true;
635
708
  this.stats.chown(uid, gid);
636
709
  this.syncSync();
637
710
  }
638
711
 
639
712
  public async utimes(atime: Date, mtime: Date): Promise<void> {
713
+ if (this.closed) {
714
+ throw ErrnoError.With('EBADF', this.path, 'File.utimes');
715
+ }
640
716
  this.dirty = true;
641
717
  this.stats.atime = atime;
642
718
  this.stats.mtime = mtime;
@@ -644,6 +720,9 @@ export class PreloadFile<FS extends FileSystem> extends File {
644
720
  }
645
721
 
646
722
  public utimesSync(atime: Date, mtime: Date): void {
723
+ if (this.closed) {
724
+ throw ErrnoError.With('EBADF', this.path, 'File.utimes');
725
+ }
647
726
  this.dirty = true;
648
727
  this.stats.atime = atime;
649
728
  this.stats.mtime = mtime;
@@ -651,16 +730,30 @@ export class PreloadFile<FS extends FileSystem> extends File {
651
730
  }
652
731
 
653
732
  public async _setType(type: FileType): Promise<void> {
733
+ if (this.closed) {
734
+ throw ErrnoError.With('EBADF', this.path, 'File._setType');
735
+ }
654
736
  this.dirty = true;
655
737
  this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
656
738
  await this.sync();
657
739
  }
658
740
 
659
741
  public _setTypeSync(type: FileType): void {
742
+ if (this.closed) {
743
+ throw ErrnoError.With('EBADF', this.path, 'File._setType');
744
+ }
660
745
  this.dirty = true;
661
746
  this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
662
747
  this.syncSync();
663
748
  }
749
+
750
+ public async [Symbol.asyncDispose]() {
751
+ await this.close();
752
+ }
753
+
754
+ public [Symbol.dispose]() {
755
+ this.closeSync();
756
+ }
664
757
  }
665
758
 
666
759
  /**