@zenfs/core 2.5.7 → 2.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.
Files changed (67) hide show
  1. package/dist/backends/cow.d.ts +13 -0
  2. package/dist/backends/cow.js +48 -11
  3. package/dist/backends/single_buffer.d.ts +2 -2
  4. package/dist/backends/store/fs.d.ts +6 -6
  5. package/dist/backends/store/fs.js +14 -10
  6. package/dist/internal/devices.d.ts +6 -6
  7. package/dist/internal/filesystem.d.ts +4 -4
  8. package/dist/internal/index_fs.d.ts +4 -4
  9. package/dist/internal/index_fs.js +6 -8
  10. package/dist/internal/inode.js +3 -0
  11. package/dist/node/async.js +1 -1
  12. package/dist/node/dir.d.ts +3 -1
  13. package/dist/node/dir.js +23 -5
  14. package/dist/node/promises.d.ts +4 -2
  15. package/dist/node/promises.js +130 -89
  16. package/dist/node/streams.d.ts +4 -0
  17. package/dist/node/streams.js +9 -10
  18. package/dist/node/sync.js +71 -40
  19. package/dist/utils.d.ts +8 -1
  20. package/dist/utils.js +26 -7
  21. package/dist/vfs/async.js +224 -85
  22. package/dist/vfs/file.d.ts +13 -7
  23. package/dist/vfs/file.js +661 -357
  24. package/dist/vfs/shared.d.ts +1 -0
  25. package/dist/vfs/shared.js +19 -3
  26. package/dist/vfs/sync.js +214 -85
  27. package/dist/vfs/vcache.d.ts +66 -0
  28. package/dist/vfs/vcache.js +249 -0
  29. package/dist/vfs/vnode.d.ts +66 -0
  30. package/dist/vfs/vnode.js +200 -0
  31. package/dist/vfs/watchers.js +4 -2
  32. package/dist/vfs/xattr.js +2 -2
  33. package/eslint.shared.js +1 -1
  34. package/package.json +6 -3
  35. package/scripts/make-index.js +7 -1
  36. package/scripts/test.js +28 -21
  37. package/tests/backend/port.test.ts +3 -3
  38. package/tests/backend/store.test.ts +49 -0
  39. package/tests/common/inode.test.ts +8 -0
  40. package/tests/common.ts +62 -0
  41. package/tests/fetch/fetch.ts +6 -0
  42. package/tests/fetch/run.sh +2 -2
  43. package/tests/fetch/server.js +3 -3
  44. package/tests/fs/append.test.ts +5 -5
  45. package/tests/fs/concurrency.test.ts +52 -0
  46. package/tests/fs/dir.test.ts +13 -15
  47. package/tests/fs/directory.test.ts +32 -31
  48. package/tests/fs/errors.test.ts +151 -31
  49. package/tests/fs/exists.test.ts +4 -4
  50. package/tests/fs/links.test.ts +4 -11
  51. package/tests/fs/open.test.ts +4 -4
  52. package/tests/fs/permissions.test.ts +15 -15
  53. package/tests/fs/read.test.ts +9 -9
  54. package/tests/fs/readFile.test.ts +9 -9
  55. package/tests/fs/rename.test.ts +8 -8
  56. package/tests/fs/scaling.test.ts +4 -4
  57. package/tests/fs/stat.test.ts +20 -20
  58. package/tests/fs/streams.test.ts +9 -6
  59. package/tests/fs/tempdir.test.ts +33 -10
  60. package/tests/fs/times.test.ts +11 -11
  61. package/tests/fs/truncate.test.ts +6 -6
  62. package/tests/fs/watch.test.ts +107 -53
  63. package/tests/fs/write.test.ts +10 -10
  64. package/tests/fs/xattr.test.ts +3 -3
  65. package/tests/native/run.sh +3 -0
  66. package/tests/native/setup.ts +230 -0
  67. package/types/readable-stream.d.ts +2 -2
@@ -55,14 +55,15 @@ import { Exception, rethrow, UV } from 'kerium';
55
55
  import { encodeUTF8 } from 'utilium';
56
56
  import * as constants from '../constants.js';
57
57
  import { hasAccess, InodeFlags, isDirectory } from '../internal/inode.js';
58
- import { join, matchesGlob } from '../path.js';
58
+ import { dirname, join, matchesGlob } from '../path.js';
59
59
  import '../polyfills.js';
60
- import { _tempDirName, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
60
+ import { _tempDirName, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime, validateFD } from '../utils.js';
61
61
  import * as _async from '../vfs/async.js';
62
62
  import { checkAccess } from '../vfs/config.js';
63
63
  import { deleteFD, fromFD, toFD } from '../vfs/file.js';
64
64
  import * as flags from '../vfs/flags.js';
65
65
  import { _statfs, resolveMount } from '../vfs/shared.js';
66
+ import { cacheOf, lockPath } from '../vfs/vcache.js';
66
67
  import { emitChange, FSWatcher } from '../vfs/watchers.js';
67
68
  import { Dir, Dirent } from './dir.js';
68
69
  import { createInterface } from './readline.js';
@@ -133,11 +134,15 @@ async function* applyTransform(source, transform, signal) {
133
134
  }
134
135
  export class FileHandle {
135
136
  context;
136
- fd;
137
137
  vfs;
138
+ _fd;
139
+ /** The file descriptor, or -1 once the handle has been closed */
140
+ get fd() {
141
+ return this._fd;
142
+ }
138
143
  constructor(context, fd) {
139
144
  this.context = context;
140
- this.fd = fd;
145
+ this._fd = fd;
141
146
  this.vfs = fromFD(context, fd);
142
147
  }
143
148
  _emitChange() {
@@ -250,7 +255,7 @@ export class FileHandle {
250
255
  readableWebStream(options = {}) {
251
256
  if (this.vfs.isClosed)
252
257
  throw UV('EBADF', 'readableWebStream', this.vfs.path);
253
- return this.vfs.fs.streamRead(this.vfs.internalPath, options);
258
+ return this.vfs.streamRead(options);
254
259
  }
255
260
  /**
256
261
  * Not part of the Node.js API!
@@ -264,7 +269,7 @@ export class FileHandle {
264
269
  throw UV('EBADF', 'writableWebStream', this.vfs.path);
265
270
  if (this.vfs.inode.flags & InodeFlags.Immutable)
266
271
  throw UV('EPERM', 'writableWebStream', this.vfs.path);
267
- return this.vfs.fs.streamWrite(this.vfs.internalPath, options);
272
+ return this.vfs.streamWrite(options);
268
273
  }
269
274
  /**
270
275
  * Creates a readline Interface object that allows reading the file line by line
@@ -340,7 +345,9 @@ export class FileHandle {
340
345
  */
341
346
  async close() {
342
347
  await this.vfs.close();
343
- deleteFD(this.context, this.fd);
348
+ deleteFD(this.context, this._fd);
349
+ // Like Node, the descriptor is invalidated so later use fails validation
350
+ this._fd = -1;
344
351
  }
345
352
  /**
346
353
  * Asynchronous `writev`. Writes from multiple buffers.
@@ -377,6 +384,7 @@ export class FileHandle {
377
384
  * @param options Options for the readable stream
378
385
  */
379
386
  createReadStream(options = {}) {
387
+ validateFD(this._fd);
380
388
  if (this.vfs.isClosed || this.vfs.flag & constants.O_WRONLY)
381
389
  throw UV('EBADF', 'createReadStream', this.vfs.path);
382
390
  return new ReadStream(options, this);
@@ -386,6 +394,7 @@ export class FileHandle {
386
394
  * @param options Options for the writeable stream.
387
395
  */
388
396
  createWriteStream(options = {}) {
397
+ validateFD(this._fd);
389
398
  if (this.vfs.isClosed)
390
399
  throw UV('EBADF', 'createWriteStream', this.vfs.path);
391
400
  if (this.vfs.inode.flags & InodeFlags.Immutable)
@@ -559,14 +568,26 @@ export async function truncate(path, len = 0) {
559
568
  }
560
569
  truncate;
561
570
  export async function unlink(path) {
562
- path = normalizePath(path);
563
- const { fs, path: resolved } = resolveMount(path, this);
564
- const $ex = { syscall: 'unlink', path };
565
- const stats = await fs.stat(resolved).catch(rethrow($ex));
566
- if (checkAccess && !hasAccess(this, stats, constants.W_OK))
567
- throw UV('EACCES', $ex);
568
- await fs.unlink(resolved).catch(rethrow($ex));
569
- emitChange(this, 'rename', path.toString());
571
+ const env_2 = { stack: [], error: void 0, hasError: false };
572
+ try {
573
+ path = normalizePath(path);
574
+ const { fs, path: resolved } = resolveMount(path, this);
575
+ const $ex = { syscall: 'unlink', path };
576
+ const stats = await fs.stat(resolved).catch(rethrow($ex));
577
+ if (checkAccess && !hasAccess(this, stats, constants.W_OK))
578
+ throw UV('EACCES', $ex);
579
+ const _ = __addDisposableResource(env_2, await lockPath(fs, dirname(resolved), 'rw'), false);
580
+ await fs.unlink(resolved).catch(rethrow($ex));
581
+ cacheOf(fs).remove(resolved);
582
+ emitChange(this, 'rename', path.toString());
583
+ }
584
+ catch (e_2) {
585
+ env_2.error = e_2;
586
+ env_2.hasError = true;
587
+ }
588
+ finally {
589
+ __disposeResources(env_2);
590
+ }
570
591
  }
571
592
  unlink;
572
593
  /**
@@ -581,18 +602,18 @@ export async function open(path, flag = 'r', mode = 0o644) {
581
602
  }
582
603
  open;
583
604
  export async function readFile(path, _options) {
584
- const env_2 = { stack: [], error: void 0, hasError: false };
605
+ const env_3 = { stack: [], error: void 0, hasError: false };
585
606
  try {
586
607
  const options = normalizeOptions(_options, null, 'r', 0o444);
587
- const handle = __addDisposableResource(env_2, typeof path == 'object' && 'fd' in path ? path : await open.call(this, path, options.flag, options.mode), true);
608
+ const handle = __addDisposableResource(env_3, typeof path == 'object' && 'fd' in path ? path : await open.call(this, path, options.flag, options.mode), true);
588
609
  return await handle.readFile(options);
589
610
  }
590
- catch (e_2) {
591
- env_2.error = e_2;
592
- env_2.hasError = true;
611
+ catch (e_3) {
612
+ env_3.error = e_3;
613
+ env_3.hasError = true;
593
614
  }
594
615
  finally {
595
- const result_2 = __disposeResources(env_2);
616
+ const result_2 = __disposeResources(env_3);
596
617
  if (result_2)
597
618
  await result_2;
598
619
  }
@@ -607,21 +628,21 @@ readFile;
607
628
  * @option flag Defaults to `'w'`.
608
629
  */
609
630
  export async function writeFile(path, data, _options) {
610
- const env_3 = { stack: [], error: void 0, hasError: false };
631
+ const env_4 = { stack: [], error: void 0, hasError: false };
611
632
  try {
612
633
  const options = normalizeOptions(_options, 'utf8', 'w+', 0o644);
613
- const handle = __addDisposableResource(env_3, path instanceof FileHandle ? path : await open.call(this, path.toString(), options.flag, options.mode), true);
634
+ const handle = __addDisposableResource(env_4, path instanceof FileHandle ? path : await open.call(this, path.toString(), options.flag, options.mode), true);
614
635
  const _data = typeof data == 'string' ? data : data instanceof DataView ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : data;
615
636
  if (typeof _data != 'string' && !(_data instanceof Uint8Array))
616
637
  throw new TypeError('The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received ' + typeof data);
617
638
  await handle.writeFile(_data, options);
618
639
  }
619
- catch (e_3) {
620
- env_3.error = e_3;
621
- env_3.hasError = true;
640
+ catch (e_4) {
641
+ env_4.error = e_4;
642
+ env_4.hasError = true;
622
643
  }
623
644
  finally {
624
- const result_3 = __disposeResources(env_3);
645
+ const result_3 = __disposeResources(env_4);
625
646
  if (result_3)
626
647
  await result_3;
627
648
  }
@@ -634,41 +655,55 @@ writeFile;
634
655
  * @option flag Defaults to `'a'`.
635
656
  */
636
657
  export async function appendFile(path, data, _options) {
637
- const env_4 = { stack: [], error: void 0, hasError: false };
658
+ const env_5 = { stack: [], error: void 0, hasError: false };
638
659
  try {
639
660
  const options = normalizeOptions(_options, 'utf8', 'a', 0o644);
640
661
  const flag = flags.parse(options.flag);
641
662
  const $ex = { syscall: 'write', path: path instanceof FileHandle ? path['vfs'].path : path.toString() };
642
663
  if (!(flag & constants.O_APPEND))
643
664
  throw UV('EBADF', $ex);
665
+ if (typeof data != 'string' && !ArrayBuffer.isView(data))
666
+ throw new TypeError('The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received ' + typeof data);
644
667
  const encodedData = typeof data == 'string' ? Buffer.from(data, options.encoding) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
645
- const handle = __addDisposableResource(env_4, typeof path == 'object' && 'fd' in path ? path : await open.call(this, path, options.flag, options.mode), true);
668
+ const handle = __addDisposableResource(env_5, typeof path == 'object' && 'fd' in path ? path : await open.call(this, path, options.flag, options.mode), true);
646
669
  await handle.appendFile(encodedData, options);
647
670
  }
648
- catch (e_4) {
649
- env_4.error = e_4;
650
- env_4.hasError = true;
671
+ catch (e_5) {
672
+ env_5.error = e_5;
673
+ env_5.hasError = true;
651
674
  }
652
675
  finally {
653
- const result_4 = __disposeResources(env_4);
676
+ const result_4 = __disposeResources(env_5);
654
677
  if (result_4)
655
678
  await result_4;
656
679
  }
657
680
  }
658
681
  appendFile;
659
682
  export async function rmdir(path) {
660
- path = normalizePath(path);
661
- const { fs, path: resolved } = await _async.resolve(this, path);
662
- const $ex = { syscall: 'rmdir', path };
663
- const stats = await fs.stat(resolved).catch(rethrow($ex));
664
- if (!stats)
665
- throw UV('ENOENT', $ex);
666
- if (!isDirectory(stats))
667
- throw UV('ENOTDIR', $ex);
668
- if (checkAccess && !hasAccess(this, stats, constants.W_OK))
669
- throw UV('EACCES', $ex);
670
- await fs.rmdir(resolved).catch(rethrow($ex));
671
- emitChange(this, 'rename', path.toString());
683
+ const env_6 = { stack: [], error: void 0, hasError: false };
684
+ try {
685
+ path = normalizePath(path);
686
+ const { fs, path: resolved } = await _async.resolve(this, path);
687
+ const $ex = { syscall: 'rmdir', path };
688
+ const stats = await fs.stat(resolved).catch(rethrow($ex));
689
+ if (!stats)
690
+ throw UV('ENOENT', $ex);
691
+ if (!isDirectory(stats))
692
+ throw UV('ENOTDIR', $ex);
693
+ if (checkAccess && !hasAccess(this, stats, constants.W_OK))
694
+ throw UV('EACCES', $ex);
695
+ const _ = __addDisposableResource(env_6, await lockPath(fs, dirname(resolved), 'rw'), false);
696
+ await fs.rmdir(resolved).catch(rethrow($ex));
697
+ cacheOf(fs).remove(resolved);
698
+ emitChange(this, 'rename', path.toString());
699
+ }
700
+ catch (e_6) {
701
+ env_6.error = e_6;
702
+ env_6.hasError = true;
703
+ }
704
+ finally {
705
+ __disposeResources(env_6);
706
+ }
672
707
  }
673
708
  rmdir;
674
709
  export async function mkdir(path, options) {
@@ -684,7 +719,7 @@ export async function readdir(path, options) {
684
719
  const values = [];
685
720
  for (const entry of rawEntries) {
686
721
  if (opt.withFileTypes) {
687
- values.push(Dirent.from(entry, opt.encoding));
722
+ values.push(Dirent.from(entry, opt.encoding, path));
688
723
  }
689
724
  else if (opt.encoding == 'buffer') {
690
725
  values.push(Buffer.from(entry.path));
@@ -707,24 +742,24 @@ link;
707
742
  * @param type can be either `'dir'` or `'file'` (default is `'file'`)
708
743
  */
709
744
  export async function symlink(dest, path, type = 'file') {
710
- const env_5 = { stack: [], error: void 0, hasError: false };
745
+ const env_7 = { stack: [], error: void 0, hasError: false };
711
746
  try {
712
747
  if (!['file', 'dir', 'junction'].includes(type))
713
748
  throw new TypeError('Invalid symlink type: ' + type);
714
749
  path = normalizePath(path);
715
750
  if (await exists.call(this, path))
716
751
  throw UV('EEXIST', 'symlink', path);
717
- const handle = __addDisposableResource(env_5, await _async.open(this, path, { flag: 'w+', mode: 0o644, preserveSymlinks: true }), true);
752
+ const handle = __addDisposableResource(env_7, await _async.open(this, path, { flag: 'w+', mode: 0o644, preserveSymlinks: true }), true);
718
753
  const encoded = encodeUTF8(normalizePath(dest, true));
719
754
  await handle.write(encoded, 0, encoded.length, 0);
720
755
  await handle.chmod(constants.S_IFLNK);
721
756
  }
722
- catch (e_5) {
723
- env_5.error = e_5;
724
- env_5.hasError = true;
757
+ catch (e_7) {
758
+ env_7.error = e_7;
759
+ env_7.hasError = true;
725
760
  }
726
761
  finally {
727
- const result_5 = __disposeResources(env_5);
762
+ const result_5 = __disposeResources(env_7);
728
763
  if (result_5)
729
764
  await result_5;
730
765
  }
@@ -739,26 +774,26 @@ export async function readlink(path, options) {
739
774
  }
740
775
  readlink;
741
776
  export async function chown(path, uid, gid) {
742
- const env_6 = { stack: [], error: void 0, hasError: false };
777
+ const env_8 = { stack: [], error: void 0, hasError: false };
743
778
  try {
744
- const handle = __addDisposableResource(env_6, await open.call(this, path, 'r+'), true);
779
+ const handle = __addDisposableResource(env_8, await open.call(this, path, 'r+'), true);
745
780
  await handle.chown(uid, gid);
746
781
  }
747
- catch (e_6) {
748
- env_6.error = e_6;
749
- env_6.hasError = true;
782
+ catch (e_8) {
783
+ env_8.error = e_8;
784
+ env_8.hasError = true;
750
785
  }
751
786
  finally {
752
- const result_6 = __disposeResources(env_6);
787
+ const result_6 = __disposeResources(env_8);
753
788
  if (result_6)
754
789
  await result_6;
755
790
  }
756
791
  }
757
792
  chown;
758
793
  export async function lchown(path, uid, gid) {
759
- const env_7 = { stack: [], error: void 0, hasError: false };
794
+ const env_9 = { stack: [], error: void 0, hasError: false };
760
795
  try {
761
- const handle = __addDisposableResource(env_7, await _async.open(this, path, {
796
+ const handle = __addDisposableResource(env_9, await _async.open(this, path, {
762
797
  flag: 'r+',
763
798
  mode: 0o644,
764
799
  preserveSymlinks: true,
@@ -766,39 +801,39 @@ export async function lchown(path, uid, gid) {
766
801
  }), true);
767
802
  await handle.chown(uid, gid);
768
803
  }
769
- catch (e_7) {
770
- env_7.error = e_7;
771
- env_7.hasError = true;
804
+ catch (e_9) {
805
+ env_9.error = e_9;
806
+ env_9.hasError = true;
772
807
  }
773
808
  finally {
774
- const result_7 = __disposeResources(env_7);
809
+ const result_7 = __disposeResources(env_9);
775
810
  if (result_7)
776
811
  await result_7;
777
812
  }
778
813
  }
779
814
  lchown;
780
815
  export async function chmod(path, mode) {
781
- const env_8 = { stack: [], error: void 0, hasError: false };
816
+ const env_10 = { stack: [], error: void 0, hasError: false };
782
817
  try {
783
- const handle = __addDisposableResource(env_8, await open.call(this, path, 'r+'), true);
818
+ const handle = __addDisposableResource(env_10, await open.call(this, path, 'r+').catch(rethrow({ syscall: 'chmod', path: normalizePath(path) })), true);
784
819
  await handle.chmod(mode);
785
820
  }
786
- catch (e_8) {
787
- env_8.error = e_8;
788
- env_8.hasError = true;
821
+ catch (e_10) {
822
+ env_10.error = e_10;
823
+ env_10.hasError = true;
789
824
  }
790
825
  finally {
791
- const result_8 = __disposeResources(env_8);
826
+ const result_8 = __disposeResources(env_10);
792
827
  if (result_8)
793
828
  await result_8;
794
829
  }
795
830
  }
796
831
  chmod;
797
832
  export async function lchmod(path, mode) {
798
- const env_9 = { stack: [], error: void 0, hasError: false };
833
+ const env_11 = { stack: [], error: void 0, hasError: false };
799
834
  try {
800
835
  mode = normalizeMode(mode);
801
- const handle = __addDisposableResource(env_9, await _async.open(this, path, {
836
+ const handle = __addDisposableResource(env_11, await _async.open(this, path, {
802
837
  flag: 'r+',
803
838
  mode: 0o644,
804
839
  preserveSymlinks: true,
@@ -806,12 +841,12 @@ export async function lchmod(path, mode) {
806
841
  }), true);
807
842
  await handle.chmod(mode);
808
843
  }
809
- catch (e_9) {
810
- env_9.error = e_9;
811
- env_9.hasError = true;
844
+ catch (e_11) {
845
+ env_11.error = e_11;
846
+ env_11.hasError = true;
812
847
  }
813
848
  finally {
814
- const result_9 = __disposeResources(env_9);
849
+ const result_9 = __disposeResources(env_11);
815
850
  if (result_9)
816
851
  await result_9;
817
852
  }
@@ -821,20 +856,20 @@ lchmod;
821
856
  * Change file timestamps of the file referenced by the supplied path.
822
857
  */
823
858
  export async function utimes(path, atime, mtime) {
824
- const env_10 = { stack: [], error: void 0, hasError: false };
859
+ const env_12 = { stack: [], error: void 0, hasError: false };
825
860
  try {
826
- const handle = __addDisposableResource(env_10, await _async.open(this, path, {
861
+ const handle = __addDisposableResource(env_12, await _async.open(this, path, {
827
862
  flag: 'r+',
828
863
  allowDirectory: true,
829
864
  }), true);
830
865
  await handle.utimes(normalizeTime(atime), normalizeTime(mtime));
831
866
  }
832
- catch (e_10) {
833
- env_10.error = e_10;
834
- env_10.hasError = true;
867
+ catch (e_12) {
868
+ env_12.error = e_12;
869
+ env_12.hasError = true;
835
870
  }
836
871
  finally {
837
- const result_10 = __disposeResources(env_10);
872
+ const result_10 = __disposeResources(env_12);
838
873
  if (result_10)
839
874
  await result_10;
840
875
  }
@@ -844,9 +879,9 @@ utimes;
844
879
  * Change file timestamps of the file referenced by the supplied path.
845
880
  */
846
881
  export async function lutimes(path, atime, mtime) {
847
- const env_11 = { stack: [], error: void 0, hasError: false };
882
+ const env_13 = { stack: [], error: void 0, hasError: false };
848
883
  try {
849
- const handle = __addDisposableResource(env_11, await _async.open(this, path, {
884
+ const handle = __addDisposableResource(env_13, await _async.open(this, path, {
850
885
  flag: 'r+',
851
886
  mode: 0o644,
852
887
  preserveSymlinks: true,
@@ -854,12 +889,12 @@ export async function lutimes(path, atime, mtime) {
854
889
  }), true);
855
890
  await handle.utimes(normalizeTime(atime), normalizeTime(mtime));
856
891
  }
857
- catch (e_11) {
858
- env_11.error = e_11;
859
- env_11.hasError = true;
892
+ catch (e_13) {
893
+ env_13.error = e_13;
894
+ env_13.hasError = true;
860
895
  }
861
896
  finally {
862
- const result_11 = __disposeResources(env_11);
897
+ const result_11 = __disposeResources(env_13);
863
898
  if (result_11)
864
899
  await result_11;
865
900
  }
@@ -878,7 +913,8 @@ export async function realpath(path, options) {
878
913
  }
879
914
  realpath;
880
915
  export function watch(filename, options = {}) {
881
- const watcher = new FSWatcher(this, filename.toString(), typeof options !== 'string' ? options : { encoding: options });
916
+ const opts = typeof options !== 'string' ? options : { encoding: options };
917
+ const watcher = new FSWatcher(this, filename.toString(), opts);
882
918
  // A queue to hold change events, since we need to resolve them in the async iterator
883
919
  const eventQueue = [];
884
920
  let done = false;
@@ -888,12 +924,17 @@ export function watch(filename, options = {}) {
888
924
  function cleanup() {
889
925
  done = true;
890
926
  watcher.close();
927
+ opts.signal?.removeEventListener('abort', cleanup);
891
928
  for (const resolve of eventQueue) {
892
929
  resolve({ value: null, done });
893
930
  }
894
931
  eventQueue.length = 0; // Clear the queue
895
932
  return Promise.resolve({ value: undefined, done: true });
896
933
  }
934
+ if (opts.signal?.aborted)
935
+ void cleanup();
936
+ else
937
+ opts.signal?.addEventListener('abort', cleanup, { once: true });
897
938
  return {
898
939
  async next() {
899
940
  if (done)
@@ -51,6 +51,10 @@ export declare class ReadStream extends Readable implements fs.ReadStream {
51
51
  private ready;
52
52
  constructor(opts: CreateReadStreamOptions | undefined, handleOrPromise: FileHandle | Promise<FileHandle>);
53
53
  _read(): Promise<void>;
54
+ /**
55
+ * Like Node, the callback is invoked once the stream is closed.
56
+ * Closing before the stream has finished reports `ERR_STREAM_PREMATURE_CLOSE`.
57
+ */
54
58
  close(callback?: Callback<[void]>): void;
55
59
  get path(): string;
56
60
  get bytesRead(): number;
@@ -1,6 +1,6 @@
1
1
  import { Errno, Exception, UV } from 'kerium';
2
2
  import { warn } from 'kerium/log';
3
- import { Readable, Writable } from 'readable-stream';
3
+ import { finished, Readable, Writable } from 'readable-stream';
4
4
  /**
5
5
  * A ReadStream implementation that wraps an underlying global ReadableStream.
6
6
  */
@@ -15,7 +15,8 @@ export class ReadStream extends Readable {
15
15
  this.ready = Promise.resolve(handleOrPromise)
16
16
  .then(handle => {
17
17
  this._path = handle['vfs'].path;
18
- const internal = handle.readableWebStream({ start: opts.start, end: opts.end });
18
+ // `end` is inclusive for streams, but the internal range is not
19
+ const internal = handle.readableWebStream({ start: opts.start, end: opts.end === undefined ? undefined : opts.end + 1 });
19
20
  this.reader = internal.getReader();
20
21
  this.pending = false;
21
22
  })
@@ -40,15 +41,13 @@ export class ReadStream extends Readable {
40
41
  this.destroy(new Exception(Errno.EIO, err.toString()));
41
42
  }
42
43
  }
44
+ /**
45
+ * Like Node, the callback is invoked once the stream is closed.
46
+ * Closing before the stream has finished reports `ERR_STREAM_PREMATURE_CLOSE`.
47
+ */
43
48
  close(callback = () => null) {
44
- try {
45
- this.destroy();
46
- this.emit('close');
47
- callback(null);
48
- }
49
- catch (err) {
50
- callback(new Exception(Errno.EIO, err.toString()));
51
- }
49
+ finished(this, error => callback(error ?? null));
50
+ this.destroy();
52
51
  }
53
52
  get path() {
54
53
  return this._path;