@zenfs/core 0.16.3 → 0.17.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 (74) 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 +0 -3
  20. package/dist/emulation/async.js +6 -2
  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 +26 -3
  25. package/dist/emulation/sync.js +1 -2
  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 +0 -63
  31. package/dist/filesystem.js +0 -311
  32. package/dist/index.d.ts +1 -2
  33. package/dist/index.js +1 -2
  34. package/dist/mixins/async.d.ts +39 -0
  35. package/dist/mixins/async.js +216 -0
  36. package/dist/mixins/mutexed.d.ts +33 -0
  37. package/dist/mixins/mutexed.js +465 -0
  38. package/dist/mixins/readonly.d.ts +25 -0
  39. package/dist/mixins/readonly.js +57 -0
  40. package/dist/mixins/shared.d.ts +12 -0
  41. package/dist/mixins/shared.js +4 -0
  42. package/dist/mixins/sync.d.ts +6 -0
  43. package/dist/mixins/sync.js +43 -0
  44. package/package.json +1 -1
  45. package/src/backends/backend.ts +3 -4
  46. package/src/backends/fetch.ts +7 -3
  47. package/src/backends/{index/fs.ts → file_index.ts} +106 -8
  48. package/src/backends/memory.ts +5 -1
  49. package/src/backends/overlay.ts +64 -90
  50. package/src/backends/port/fs.ts +7 -2
  51. package/src/backends/{index/readme.md → readme.md} +1 -1
  52. package/src/backends/store/fs.ts +97 -155
  53. package/src/backends/store/simple.ts +5 -1
  54. package/src/backends/store/store.ts +10 -5
  55. package/src/config.ts +3 -1
  56. package/src/emulation/async.ts +15 -6
  57. package/src/emulation/dir.ts +19 -16
  58. package/src/emulation/promises.ts +30 -8
  59. package/src/emulation/sync.ts +2 -3
  60. package/src/emulation/watchers.ts +10 -4
  61. package/src/file.ts +94 -1
  62. package/src/filesystem.ts +3 -366
  63. package/src/index.ts +1 -2
  64. package/src/mixins/async.ts +211 -0
  65. package/src/mixins/mutexed.ts +245 -0
  66. package/src/mixins/readonly.ts +97 -0
  67. package/src/mixins/shared.ts +20 -0
  68. package/src/mixins/sync.ts +59 -0
  69. package/dist/backends/index/index.d.ts +0 -43
  70. package/dist/backends/index/index.js +0 -83
  71. package/dist/backends/locked.d.ts +0 -92
  72. package/dist/backends/locked.js +0 -487
  73. package/src/backends/index/index.ts +0 -104
  74. package/src/backends/locked.ts +0 -264
@@ -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 {
@@ -514,7 +516,7 @@ async function _open(path: fs.PathLike, _flag: fs.OpenMode, _mode: fs.Mode = 0o6
514
516
  return new FileHandle(await fs.openFile(resolved, flag, cred));
515
517
  }
516
518
 
517
- /*
519
+ /*
518
520
  In a previous implementation, we deleted the file and
519
521
  re-created it. However, this created a race condition if another
520
522
  asynchronous request was trying to read the file, as the file
@@ -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
 
@@ -907,7 +926,7 @@ export async function rm(path: fs.PathLike, options?: fs.RmOptions) {
907
926
  case constants.S_IFDIR:
908
927
  if (options?.recursive) {
909
928
  for (const entry of await readdir(path)) {
910
- await rm(join(path, entry));
929
+ await rm(join(path, entry), options);
911
930
  }
912
931
  }
913
932
 
@@ -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
 
@@ -731,7 +730,7 @@ export function rmSync(path: fs.PathLike, options?: fs.RmOptions): void {
731
730
  case S_IFDIR:
732
731
  if (options?.recursive) {
733
732
  for (const entry of readdirSync(path)) {
734
- rmSync(join(path, entry));
733
+ rmSync(join(path, entry), options);
735
734
  }
736
735
  }
737
736
 
@@ -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
  /**