@zenfs/core 2.7.6 → 2.8.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 (70) hide show
  1. package/README.md +5 -0
  2. package/dist/backends/index.d.ts +3 -1
  3. package/dist/backends/index.js +3 -1
  4. package/dist/backends/single_buffer/backend.d.ts +42 -0
  5. package/dist/backends/single_buffer/backend.js +204 -0
  6. package/dist/backends/{single_buffer.d.ts → single_buffer/v1.d.ts} +24 -34
  7. package/dist/backends/{single_buffer.js → single_buffer/v1.js} +10 -26
  8. package/dist/backends/single_buffer/v2.d.ts +257 -0
  9. package/dist/backends/single_buffer/v2.js +1283 -0
  10. package/dist/backends/store/fs.d.ts +1 -1
  11. package/dist/backends/store/fs.js +35 -18
  12. package/dist/backends/store/map.d.ts +6 -6
  13. package/dist/backends/store/map.js +6 -6
  14. package/dist/backends/store/store.d.ts +18 -14
  15. package/dist/backends/store/store.js +12 -14
  16. package/dist/config.d.ts +7 -0
  17. package/dist/config.js +9 -3
  18. package/dist/internal/contexts.d.ts +5 -0
  19. package/dist/internal/contexts.js +7 -1
  20. package/dist/internal/error.d.ts +5 -0
  21. package/dist/internal/error.js +10 -0
  22. package/dist/internal/inode.d.ts +25 -0
  23. package/dist/internal/inode.js +75 -10
  24. package/dist/mixins/async.js +47 -30
  25. package/dist/node/promises.js +23 -124
  26. package/dist/node/stats.d.ts +1 -1
  27. package/dist/node/stats.js +4 -1
  28. package/dist/node/sync.js +20 -51
  29. package/dist/polyfills.js +16 -0
  30. package/dist/vfs/async.d.ts +4 -1
  31. package/dist/vfs/async.js +149 -69
  32. package/dist/vfs/config.d.ts +10 -1
  33. package/dist/vfs/config.js +5 -2
  34. package/dist/vfs/file.js +10 -4
  35. package/dist/vfs/ioctl.js +2 -2
  36. package/dist/vfs/shared.d.ts +2 -0
  37. package/dist/vfs/shared.js +3 -4
  38. package/dist/vfs/sync.d.ts +4 -1
  39. package/dist/vfs/sync.js +142 -64
  40. package/dist/vfs/vcache.d.ts +14 -14
  41. package/dist/vfs/vcache.js +65 -57
  42. package/dist/vfs/watchers.d.ts +1 -0
  43. package/dist/vfs/watchers.js +2 -4
  44. package/dist/vfs/xattr.js +18 -17
  45. package/package.json +3 -4
  46. package/scripts/make-index.js +1 -1
  47. package/tests/backend/single-buffer-rotate.worker.js +1 -1
  48. package/tests/backend/single-buffer-v2.test.ts +342 -0
  49. package/tests/backend/single-buffer-v2.worker.js +51 -0
  50. package/tests/backend/single-buffer.test.ts +6 -6
  51. package/tests/backend/single-buffer.worker.js +1 -0
  52. package/tests/common/async.test.ts +46 -0
  53. package/tests/common/config.test.ts +1 -1
  54. package/tests/common/context.test.ts +30 -0
  55. package/tests/common/file_index.test.ts +1 -1
  56. package/tests/common/mounts.test.ts +12 -0
  57. package/tests/common/vcache.test.ts +13 -0
  58. package/tests/fs/concurrency.test.ts +3 -3
  59. package/tests/fs/links.test.ts +76 -0
  60. package/tests/fs/permissions.test.ts +253 -0
  61. package/tests/fs/stat.test.ts +1 -1
  62. package/tests/fs/streams.test.ts +1 -1
  63. package/tests/fs/watch.test.ts +14 -0
  64. package/tests/fs/xattr.test.ts +10 -0
  65. package/tests/perf/config.json +31 -1
  66. package/tests/perf/single-buffer-random.ts +38 -0
  67. package/tests/perf/single-buffer-read.ts +28 -0
  68. package/tests/perf/single-buffer-write.ts +41 -0
  69. package/tests/setup/single-buffer-v2.ts +10 -0
  70. package/tests/setup/single-buffer.ts +1 -0
@@ -57,6 +57,18 @@ import { StoreFS } from '../backends/store/fs.js';
57
57
  import { isDirectory } from '../internal/inode.js';
58
58
  import { join } from '../path.js';
59
59
  import { _asyncFSKeys } from './shared.js';
60
+ /**
61
+ * Whether the stack of `error` has a patched method or `key`'s sync method below the innermost patched method.
62
+ */
63
+ function isInLoop(key, error = new Error()) {
64
+ return new RegExp(`__patched__[^]*(__patched__|\\b${key}Sync\\b)`).test(error.stack ?? '');
65
+ }
66
+ /**
67
+ * Whether the stack has a queued replay below the innermost patched method.
68
+ */
69
+ function isReplay() {
70
+ return /__patched__[^]*__replay__/.test(new Error().stack ?? '');
71
+ }
60
72
  /**
61
73
  * Async() implements synchronous methods on an asynchronous file system
62
74
  *
@@ -82,8 +94,21 @@ export function Async(FS) {
82
94
  return this.sync();
83
95
  }
84
96
  _promise = Promise.resolve();
97
+ /** How many queued operations have not finished */
98
+ _pending = 0;
99
+ /** The async methods before patching, which do not update the sync cache */
100
+ _unpatched = {};
85
101
  _async(thunk) {
86
- this._promise = this._promise.finally(() => thunk());
102
+ this._pending++;
103
+ const replay = async function __replay__() {
104
+ try {
105
+ await thunk();
106
+ }
107
+ finally {
108
+ this._pending--;
109
+ }
110
+ };
111
+ this._promise = this._promise.finally(replay.bind(this));
87
112
  }
88
113
  _isInitialized = false;
89
114
  /** Tracks how many updates to the sync. cache we skipped during initialization */
@@ -132,7 +157,7 @@ export function Async(FS) {
132
157
  renameSync(oldPath, newPath) {
133
158
  this.checkSync();
134
159
  this._sync.renameSync(oldPath, newPath);
135
- this._async(() => this.rename(oldPath, newPath));
160
+ this._async(() => this._unpatched.rename(oldPath, newPath));
136
161
  }
137
162
  statSync(path) {
138
163
  this.checkSync();
@@ -141,28 +166,28 @@ export function Async(FS) {
141
166
  touchSync(path, metadata) {
142
167
  this.checkSync();
143
168
  this._sync.touchSync(path, metadata);
144
- this._async(() => this.touch(path, metadata));
169
+ this._async(() => this._unpatched.touch(path, metadata));
145
170
  }
146
171
  createFileSync(path, options) {
147
172
  this.checkSync();
148
173
  const result = this._sync.createFileSync(path, options);
149
- this._async(() => this.createFile(path, options));
174
+ this._async(() => this._unpatched.createFile(path, options));
150
175
  return result;
151
176
  }
152
177
  unlinkSync(path) {
153
178
  this.checkSync();
154
179
  this._sync.unlinkSync(path);
155
- this._async(() => this.unlink(path));
180
+ this._async(() => this._unpatched.unlink(path));
156
181
  }
157
182
  rmdirSync(path) {
158
183
  this.checkSync();
159
184
  this._sync.rmdirSync(path);
160
- this._async(() => this.rmdir(path));
185
+ this._async(() => this._unpatched.rmdir(path));
161
186
  }
162
187
  mkdirSync(path, options) {
163
188
  this.checkSync();
164
189
  const result = this._sync.mkdirSync(path, options);
165
- this._async(() => this.mkdir(path, options));
190
+ this._async(() => this._unpatched.mkdir(path, options));
166
191
  return result;
167
192
  }
168
193
  readdirSync(path) {
@@ -172,7 +197,7 @@ export function Async(FS) {
172
197
  linkSync(srcpath, dstpath) {
173
198
  this.checkSync();
174
199
  this._sync.linkSync(srcpath, dstpath);
175
- this._async(() => this.link(srcpath, dstpath));
200
+ this._async(() => this._unpatched.link(srcpath, dstpath));
176
201
  }
177
202
  async sync() {
178
203
  if (!this.attributes.has('no_async_preload') && this._sync)
@@ -194,7 +219,7 @@ export function Async(FS) {
194
219
  writeSync(path, buffer, offset) {
195
220
  this.checkSync();
196
221
  this._sync.writeSync(path, buffer, offset);
197
- this._async(() => this.write(path, buffer, offset));
222
+ this._async(() => this._unpatched.write(path, buffer, offset));
198
223
  }
199
224
  ioctlSync(context, command, ...args) {
200
225
  this.checkSync();
@@ -253,30 +278,19 @@ export function Async(FS) {
253
278
  }
254
279
  /**
255
280
  * @internal
256
- * Patch all async methods to also call their synchronous counterparts unless called from themselves (either sync or async)
281
+ * Patch all async methods to wait for queued operations,
282
+ * and methods that change the file system to also call their synchronous counterparts unless called from themselves (either sync or async)
257
283
  */
258
284
  _patchAsync() {
259
- const noPatch = ['read', 'readdir', 'stat', 'exists'];
260
- const toPatch = _asyncFSKeys.filter(key => !noPatch.includes(key));
261
- for (const key of toPatch) {
285
+ const reads = ['read', 'readdir', 'stat', 'exists'];
286
+ for (const key of _asyncFSKeys) {
262
287
  // TS does not narrow the union based on the key
263
288
  const originalMethod = this[key].bind(this);
264
- function isInLoop(depth, error) {
265
- if (!error) {
266
- error = new Error();
267
- Error.captureStackTrace(error, isInLoop);
268
- }
269
- if (!error.stack)
270
- return false;
271
- const stack = error.stack.split('\n').slice(depth).join('\n');
272
- // From the async queue
273
- return (stack.includes(`at <computed> [as ${key}]`)
274
- || stack.includes(`at async <computed> [as ${key}]`)
275
- || stack.includes(`${key}Sync `));
276
- }
277
- this[key] = async (...args) => {
289
+ const fn = async function __patched__(...args) {
290
+ if (this._pending && !isReplay())
291
+ await this._promise.catch(() => { });
278
292
  const result = await originalMethod(...args);
279
- if (isInLoop(2))
293
+ if (reads.includes(key) || isInLoop(key))
280
294
  return result;
281
295
  if (!this._isInitialized) {
282
296
  this._skippedCacheUpdates++;
@@ -287,15 +301,18 @@ export function Async(FS) {
287
301
  this._sync?.[`${key}Sync`]?.(...args);
288
302
  }
289
303
  catch (e) {
290
- if (isInLoop(3, e))
304
+ if (isInLoop(key, e))
291
305
  return result;
292
306
  e.message += ' (Out of sync!)';
293
307
  throw err(e);
294
308
  }
295
309
  return result;
296
310
  };
311
+ Object.defineProperty(fn, 'name', { get: () => key });
312
+ this._unpatched[key] = originalMethod;
313
+ this[key] = fn;
297
314
  }
298
- debug(`Async: patched ${toPatch.length} methods`);
315
+ debug(`Async: patched ${_asyncFSKeys.length} methods`);
299
316
  }
300
317
  }
301
318
  return AsyncFS;
@@ -55,7 +55,7 @@ import { Exception, rethrow, UV } from 'kerium';
55
55
  import { encodeUTF8 } from 'utilium';
56
56
  import * as constants from '../constants.js';
57
57
  import { contextOf } from '../internal/contexts.js';
58
- import { hasAccess, InodeFlags, isDirectory } from '../internal/inode.js';
58
+ import { assertRemovable, hasAccess, InodeFlags, isDirectory } from '../internal/inode.js';
59
59
  import { dirname, join, matchesGlob, relative, resolve } from '../path.js';
60
60
  import '../polyfills.js';
61
61
  import { _isNoEntry, _tempDirName, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime, validateFD } from '../utils.js';
@@ -63,8 +63,7 @@ import * as _async from '../vfs/async.js';
63
63
  import { checkAccess } from '../vfs/config.js';
64
64
  import { deleteFD, fromFD, toFD } from '../vfs/file.js';
65
65
  import * as flags from '../vfs/flags.js';
66
- import { _statfs, resolveMount } from '../vfs/shared.js';
67
- import { cacheOf, lockPath } from '../vfs/vcache.js';
66
+ import { _statfs } from '../vfs/shared.js';
68
67
  import { emitChange, FSWatcher } from '../vfs/watchers.js';
69
68
  import { Dir, Dirent } from './dir.js';
70
69
  import { createInterface } from './readline.js';
@@ -535,7 +534,7 @@ export async function exists(path) {
535
534
  return await fs.exists(resolved);
536
535
  }
537
536
  catch (e) {
538
- if (e instanceof Exception && e.code == 'ENOENT') {
537
+ if (e instanceof Exception && (e.code == 'ENOENT' || e.code == 'ENOTDIR' || e.code == 'EACCES')) {
539
538
  return false;
540
539
  }
541
540
  throw e;
@@ -580,14 +579,13 @@ export async function unlink(path) {
580
579
  const env_2 = { stack: [], error: void 0, hasError: false };
581
580
  try {
582
581
  path = normalizePath.call(this, path);
583
- const { fs, path: resolved } = resolveMount(path, this);
584
582
  const $ex = { syscall: 'unlink', path };
585
- const stats = await fs.stat(resolved).catch(rethrow($ex));
586
- if (checkAccess && !hasAccess(this, stats, constants.W_OK))
587
- throw UV('EACCES', $ex);
588
- const _ = __addDisposableResource(env_2, await lockPath(fs, dirname(resolved), 'rw'), false);
583
+ const { fs, cache, path: resolved } = await _async.resolve(this, path, true, $ex);
584
+ const stats = await cache.stat(resolved).catch(rethrow($ex));
585
+ assertRemovable(this, await cache.stat(dirname(resolved)).catch(rethrow($ex)), stats, $ex);
586
+ const _ = __addDisposableResource(env_2, await cache.lock(dirname(resolved), 'rw'), false);
589
587
  await fs.unlink(resolved).catch(rethrow($ex));
590
- cacheOf(fs).remove(resolved);
588
+ cache.remove(resolved);
591
589
  emitChange(this, 'rename', path.toString());
592
590
  }
593
591
  catch (e_2) {
@@ -692,18 +690,17 @@ export async function rmdir(path) {
692
690
  const env_6 = { stack: [], error: void 0, hasError: false };
693
691
  try {
694
692
  path = normalizePath.call(this, path);
695
- const { fs, path: resolved } = await _async.resolve(this, path);
693
+ const { fs, cache, path: resolved } = await _async.resolve(this, path, true);
696
694
  const $ex = { syscall: 'rmdir', path };
697
- const stats = await fs.stat(resolved).catch(rethrow($ex));
695
+ const stats = await cache.stat(resolved).catch(rethrow($ex));
698
696
  if (!stats)
699
697
  throw UV('ENOENT', $ex);
700
698
  if (!isDirectory(stats))
701
699
  throw UV('ENOTDIR', $ex);
702
- if (checkAccess && !hasAccess(this, stats, constants.W_OK))
703
- throw UV('EACCES', $ex);
704
- const _ = __addDisposableResource(env_6, await lockPath(fs, dirname(resolved), 'rw'), false);
700
+ assertRemovable(this, await cache.stat(dirname(resolved)).catch(rethrow($ex)), stats, $ex);
701
+ const _ = __addDisposableResource(env_6, await cache.lock(dirname(resolved), 'rw'), false);
705
702
  await fs.rmdir(resolved).catch(rethrow($ex));
706
- cacheOf(fs).remove(resolved);
703
+ cache.remove(resolved);
707
704
  emitChange(this, 'rename', path.toString());
708
705
  }
709
706
  catch (e_6) {
@@ -761,7 +758,7 @@ export async function symlink(dest, path, type = 'file') {
761
758
  const handle = __addDisposableResource(env_7, await _async.open(this, path, { flag: 'w+', mode: 0o644, preserveSymlinks: true }), true);
762
759
  const encoded = encodeUTF8(normalizePath.call(this, dest, true));
763
760
  await handle.write(encoded, 0, encoded.length, 0);
764
- await handle.chmod(constants.S_IFLNK);
761
+ await handle.chmod(constants.S_IFLNK | 0o777);
765
762
  }
766
763
  catch (e_7) {
767
764
  env_7.error = e_7;
@@ -783,130 +780,33 @@ export async function readlink(path, options) {
783
780
  }
784
781
  readlink;
785
782
  export async function chown(path, uid, gid) {
786
- const env_8 = { stack: [], error: void 0, hasError: false };
787
- try {
788
- const handle = __addDisposableResource(env_8, await open.call(this, path, 'r+'), true);
789
- await handle.chown(uid, gid);
790
- }
791
- catch (e_8) {
792
- env_8.error = e_8;
793
- env_8.hasError = true;
794
- }
795
- finally {
796
- const result_6 = __disposeResources(env_8);
797
- if (result_6)
798
- await result_6;
799
- }
783
+ await _async.chown.call(this, path, uid, gid, false);
800
784
  }
801
785
  chown;
802
786
  export async function lchown(path, uid, gid) {
803
- const env_9 = { stack: [], error: void 0, hasError: false };
804
- try {
805
- const handle = __addDisposableResource(env_9, await _async.open(this, path, {
806
- flag: 'r+',
807
- mode: 0o644,
808
- preserveSymlinks: true,
809
- allowDirectory: true,
810
- }), true);
811
- await handle.chown(uid, gid);
812
- }
813
- catch (e_9) {
814
- env_9.error = e_9;
815
- env_9.hasError = true;
816
- }
817
- finally {
818
- const result_7 = __disposeResources(env_9);
819
- if (result_7)
820
- await result_7;
821
- }
787
+ await _async.chown.call(this, path, uid, gid, true);
822
788
  }
823
789
  lchown;
824
790
  export async function chmod(path, mode) {
825
- const env_10 = { stack: [], error: void 0, hasError: false };
826
- try {
827
- const handle = __addDisposableResource(env_10, await open.call(this, path, 'r+').catch(rethrow({ syscall: 'chmod', path: normalizePath.call(this, path) })), true);
828
- await handle.chmod(mode);
829
- }
830
- catch (e_10) {
831
- env_10.error = e_10;
832
- env_10.hasError = true;
833
- }
834
- finally {
835
- const result_8 = __disposeResources(env_10);
836
- if (result_8)
837
- await result_8;
838
- }
791
+ await _async.chmod.call(this, path, mode, false);
839
792
  }
840
793
  chmod;
841
794
  export async function lchmod(path, mode) {
842
- const env_11 = { stack: [], error: void 0, hasError: false };
843
- try {
844
- mode = normalizeMode(mode);
845
- const handle = __addDisposableResource(env_11, await _async.open(this, path, {
846
- flag: 'r+',
847
- mode: 0o644,
848
- preserveSymlinks: true,
849
- allowDirectory: true,
850
- }), true);
851
- await handle.chmod(mode);
852
- }
853
- catch (e_11) {
854
- env_11.error = e_11;
855
- env_11.hasError = true;
856
- }
857
- finally {
858
- const result_9 = __disposeResources(env_11);
859
- if (result_9)
860
- await result_9;
861
- }
795
+ await _async.chmod.call(this, path, mode, true);
862
796
  }
863
797
  lchmod;
864
798
  /**
865
799
  * Change file timestamps of the file referenced by the supplied path.
866
800
  */
867
801
  export async function utimes(path, atime, mtime) {
868
- const env_12 = { stack: [], error: void 0, hasError: false };
869
- try {
870
- const handle = __addDisposableResource(env_12, await _async.open(this, path, {
871
- flag: 'r+',
872
- allowDirectory: true,
873
- }), true);
874
- await handle.utimes(normalizeTime(atime), normalizeTime(mtime));
875
- }
876
- catch (e_12) {
877
- env_12.error = e_12;
878
- env_12.hasError = true;
879
- }
880
- finally {
881
- const result_10 = __disposeResources(env_12);
882
- if (result_10)
883
- await result_10;
884
- }
802
+ await _async.utimes.call(this, path, atime, mtime, false);
885
803
  }
886
804
  utimes;
887
805
  /**
888
806
  * Change file timestamps of the file referenced by the supplied path.
889
807
  */
890
808
  export async function lutimes(path, atime, mtime) {
891
- const env_13 = { stack: [], error: void 0, hasError: false };
892
- try {
893
- const handle = __addDisposableResource(env_13, await _async.open(this, path, {
894
- flag: 'r+',
895
- mode: 0o644,
896
- preserveSymlinks: true,
897
- allowDirectory: true,
898
- }), true);
899
- await handle.utimes(normalizeTime(atime), normalizeTime(mtime));
900
- }
901
- catch (e_13) {
902
- env_13.error = e_13;
903
- env_13.hasError = true;
904
- }
905
- finally {
906
- const result_11 = __disposeResources(env_13);
907
- if (result_11)
908
- await result_11;
909
- }
809
+ await _async.utimes.call(this, path, atime, mtime, true);
910
810
  }
911
811
  lutimes;
912
812
  export async function realpath(path, options) {
@@ -1079,8 +979,7 @@ export async function cp(source, destination, opts) {
1079
979
  const [entries] = await Promise.all([
1080
980
  readdir.call(this, source, { withFileTypes: true }),
1081
981
  mkdir.call(this, destination, { recursive: true }),
1082
- ] // Ensure the destination directory exists
1083
- );
982
+ ]);
1084
983
  const _cp = async (dirent) => {
1085
984
  if (opts.filter && !opts.filter(join(source, dirent.name), join(destination, dirent.name))) {
1086
985
  return; // Skip if the filter returns false
@@ -1117,8 +1016,8 @@ export async function cp(source, destination, opts) {
1117
1016
  cp;
1118
1017
  export async function statfs(path, opts) {
1119
1018
  path = normalizePath.call(this, path);
1120
- const { fs } = resolveMount(path, this);
1121
- return Promise.resolve(_statfs(fs, opts?.bigint));
1019
+ const { fs } = await _async.resolve(this, path);
1020
+ return _statfs(fs, opts?.bigint);
1122
1021
  }
1123
1022
  export function glob(pattern, opt) {
1124
1023
  pattern = Array.isArray(pattern) ? pattern : [pattern];
@@ -65,7 +65,7 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
65
65
  /** @internal Used by inodes */
66
66
  version?: number;
67
67
  /** Creates a new stats instance from a stats-like object. Can be used to copy stats (note) */
68
- constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, ...rest }?: Partial<InodeLike<number | bigint>>);
68
+ constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, rdev, dev, blksize, ...rest }?: Partial<InodeLike<number | bigint> & StatsExtra<number | bigint>>);
69
69
  isFile(): boolean;
70
70
  isDirectory(): boolean;
71
71
  isSymbolicLink(): boolean;
@@ -96,7 +96,7 @@ export class StatsCommon {
96
96
  /** @internal Used by inodes */
97
97
  version;
98
98
  /** Creates a new stats instance from a stats-like object. Can be used to copy stats (note) */
99
- constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, ...rest } = {}) {
99
+ constructor({ atimeMs, mtimeMs, ctimeMs, birthtimeMs, uid, gid, size, mode, ino, rdev, dev, blksize, ...rest } = {}) {
100
100
  const now = Date.now();
101
101
  this.atimeMs = this._convert(atimeMs ?? now);
102
102
  this.mtimeMs = this._convert(mtimeMs ?? now);
@@ -107,6 +107,9 @@ export class StatsCommon {
107
107
  this.size = this._convert(size ?? 0);
108
108
  this.ino = this._convert(ino ?? 0);
109
109
  this.mode = this._convert(mode ?? 0o644 & c.S_IFREG);
110
+ this.rdev = this._convert(rdev ?? 0);
111
+ this.dev = this._convert(dev ?? 0);
112
+ this.blksize = this._convert(blksize ?? 4096);
110
113
  if ((this.mode & c.S_IFMT) == 0) {
111
114
  this.mode = (this.mode | this._convert(c.S_IFREG));
112
115
  }
package/dist/node/sync.js CHANGED
@@ -56,15 +56,14 @@ import { encodeUTF8 } from 'utilium';
56
56
  import * as constants from '../constants.js';
57
57
  import { contextOf } from '../internal/contexts.js';
58
58
  import { wrap } from '../internal/error.js';
59
- import { hasAccess, isDirectory } from '../internal/inode.js';
59
+ import { assertRemovable, hasAccess, isDirectory } from '../internal/inode.js';
60
60
  import { dirname, join, matchesGlob, relative, resolve } from '../path.js';
61
61
  import { _isNoEntry, _tempDirName, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.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
- import { _statfs, resolveMount } from '../vfs/shared.js';
65
+ import { _statfs } from '../vfs/shared.js';
66
66
  import * as _sync from '../vfs/sync.js';
67
- import { cacheOf, lockPathSync } from '../vfs/vcache.js';
68
67
  import { emitChange } from '../vfs/watchers.js';
69
68
  import { Dir, Dirent } from './dir.js';
70
69
  import { BigIntStats, Stats } from './stats.js';
@@ -82,7 +81,7 @@ export function existsSync(path) {
82
81
  return fs.existsSync(resolvedPath);
83
82
  }
84
83
  catch (e) {
85
- if (e.errno == Errno.ENOENT)
84
+ if (e.errno == Errno.ENOENT || e.errno == Errno.ENOTDIR || e.errno == Errno.EACCES)
86
85
  return false;
87
86
  throw e;
88
87
  }
@@ -134,16 +133,14 @@ export function truncateSync(path, len = 0) {
134
133
  truncateSync;
135
134
  export function unlinkSync(path) {
136
135
  path = normalizePath.call(this, path);
137
- const { fs, path: resolved } = resolveMount(path, this);
136
+ const { fs, cache, path: resolved } = _sync.resolve(this, path, true, { syscall: 'unlink', path });
138
137
  try {
139
138
  const env_2 = { stack: [], error: void 0, hasError: false };
140
139
  try {
141
- if (checkAccess && !hasAccess(this, fs.statSync(resolved), constants.W_OK)) {
142
- throw UV('EACCES', 'unlink');
143
- }
144
- const _ = __addDisposableResource(env_2, lockPathSync(fs, dirname(resolved), 'rw'), false);
140
+ assertRemovable(this, cache.statSync(dirname(resolved)), cache.statSync(resolved));
141
+ const _ = __addDisposableResource(env_2, cache.lockSync(dirname(resolved), 'rw'), false);
145
142
  fs.unlinkSync(resolved);
146
- cacheOf(fs).remove(resolved);
143
+ cache.remove(resolved);
147
144
  }
148
145
  catch (e_2) {
149
146
  env_2.error = e_2;
@@ -372,15 +369,14 @@ export function rmdirSync(path) {
372
369
  const env_5 = { stack: [], error: void 0, hasError: false };
373
370
  try {
374
371
  path = normalizePath.call(this, path);
375
- const { fs, path: resolved } = _sync.resolve(this, path);
376
- const stats = wrap(fs, 'statSync', { path, syscall: 'rmdir' })(resolved);
372
+ const { fs, cache, path: resolved } = _sync.resolve(this, path, true);
373
+ const stats = cache.statSync(resolved, { path, syscall: 'rmdir' });
377
374
  if (!isDirectory(stats))
378
375
  throw UV('ENOTDIR', 'rmdir', path);
379
- if (checkAccess && !hasAccess(this, stats, constants.W_OK))
380
- throw UV('EACCES', 'rmdir', path);
381
- const _ = __addDisposableResource(env_5, lockPathSync(fs, dirname(resolved), 'rw'), false);
376
+ assertRemovable(this, cache.statSync(dirname(resolved), { path, syscall: 'rmdir' }), stats, { syscall: 'rmdir', path });
377
+ const _ = __addDisposableResource(env_5, cache.lockSync(dirname(resolved), 'rw'), false);
382
378
  wrap(fs, 'rmdirSync', path)(resolved);
383
- cacheOf(fs).remove(resolved);
379
+ cache.remove(resolved);
384
380
  emitChange(this, 'rename', path.toString());
385
381
  }
386
382
  catch (e_5) {
@@ -435,7 +431,7 @@ export function symlinkSync(target, path, type = 'file') {
435
431
  path = normalizePath.call(this, path);
436
432
  const file = __addDisposableResource(env_6, _sync.open(this, path, { flag: 'wx', mode: 0o644 }), false);
437
433
  file.writeSync(encodeUTF8(normalizePath.call(this, target, true)));
438
- file.chmodSync(constants.S_IFLNK);
434
+ file.chmodSync(constants.S_IFLNK | 0o777);
439
435
  }
440
436
  catch (e_6) {
441
437
  env_6.error = e_6;
@@ -457,60 +453,33 @@ export function readlinkSync(path, options) {
457
453
  }
458
454
  readlinkSync;
459
455
  export function chownSync(path, uid, gid) {
460
- const env_7 = { stack: [], error: void 0, hasError: false };
461
- try {
462
- const handle = __addDisposableResource(env_7, _sync.open(this, path, { flag: 'r+', mode: constants.F_OK }), false);
463
- handle.chownSync(uid, gid);
464
- }
465
- catch (e_7) {
466
- env_7.error = e_7;
467
- env_7.hasError = true;
468
- }
469
- finally {
470
- __disposeResources(env_7);
471
- }
456
+ _sync.chown.call(this, path, uid, gid);
472
457
  }
473
458
  chownSync;
474
459
  export function lchownSync(path, uid, gid) {
475
- const fd = lopenSync.call(this, path, 'r+');
476
- fchownSync.call(this, fd, uid, gid);
477
- closeSync.call(this, fd);
460
+ _sync.chown.call(this, path, uid, gid, true);
478
461
  }
479
462
  lchownSync;
480
463
  export function chmodSync(path, mode) {
481
- let fd;
482
- try {
483
- fd = openSync.call(this, path, 'r+');
484
- }
485
- catch (e) {
486
- throw setUVMessage(Object.assign(e, { syscall: 'chmod', path: normalizePath.call(this, path) }));
487
- }
488
- fchmodSync.call(this, fd, mode);
489
- closeSync.call(this, fd);
464
+ _sync.chmod.call(this, path, mode);
490
465
  }
491
466
  chmodSync;
492
467
  export function lchmodSync(path, mode) {
493
- const fd = lopenSync.call(this, path, 'r+');
494
- fchmodSync.call(this, fd, mode);
495
- closeSync.call(this, fd);
468
+ _sync.chmod.call(this, path, mode, true);
496
469
  }
497
470
  lchmodSync;
498
471
  /**
499
472
  * Change file timestamps of the file referenced by the supplied path.
500
473
  */
501
474
  export function utimesSync(path, atime, mtime) {
502
- const fd = openSync.call(this, path, 'r+');
503
- futimesSync.call(this, fd, atime, mtime);
504
- closeSync.call(this, fd);
475
+ _sync.utimes.call(this, path, atime, mtime);
505
476
  }
506
477
  utimesSync;
507
478
  /**
508
479
  * Change file timestamps of the file referenced by the supplied path.
509
480
  */
510
481
  export function lutimesSync(path, atime, mtime) {
511
- const fd = lopenSync.call(this, path, 'r+');
512
- futimesSync.call(this, fd, atime, mtime);
513
- closeSync.call(this, fd);
482
+ _sync.utimes.call(this, path, atime, mtime, true);
514
483
  }
515
484
  lutimesSync;
516
485
  export function realpathSync(path, options) {
@@ -706,7 +675,7 @@ export function cpSync(source, destination, opts) {
706
675
  cpSync;
707
676
  export function statfsSync(path, options) {
708
677
  path = normalizePath.call(this, path);
709
- const { fs } = resolveMount(path, this);
678
+ const { fs } = _sync.resolve(this, path);
710
679
  return _statfs(fs, options?.bigint);
711
680
  }
712
681
  export function globSync(pattern, options = {}) {
package/dist/polyfills.js CHANGED
@@ -66,4 +66,20 @@ Array.fromAsync ??=
66
66
  }
67
67
  return results;
68
68
  });
69
+ Map.prototype.getOrInsert ??=
70
+ (warn('Using a polyfill of Map.prototype.getOrInsert'),
71
+ function getOrInsert(key, defaultValue) {
72
+ if (!this.has(key))
73
+ this.set(key, defaultValue);
74
+ return this.get(key);
75
+ });
76
+ Map.prototype.getOrInsertComputed ??=
77
+ (warn('Using a polyfill of Map.prototype.getOrInsertComputed'),
78
+ function getOrInsertComputed(key, callback) {
79
+ if (this.has(key))
80
+ return this.get(key);
81
+ const value = callback(key);
82
+ this.set(key, value);
83
+ return value;
84
+ });
69
85
  /* node:coverage enable */
@@ -1,4 +1,4 @@
1
- import type { PathLike } from 'node:fs';
1
+ import type { Mode, PathLike, TimeLike } from 'node:fs';
2
2
  import type { V_Context } from '../internal/contexts.js';
3
3
  import type { MkdirOptions, OpenOptions, ReaddirOptions, ResolvedPath } from './shared.js';
4
4
  import { type ExceptionExtra } from 'kerium';
@@ -22,3 +22,6 @@ export declare function readdir(this: V_Context, path: PathLike, options?: Readd
22
22
  export declare function rename(this: V_Context, oldPath: PathLike, newPath: PathLike): Promise<void>;
23
23
  export declare function link(this: V_Context, target: PathLike, link: PathLike): Promise<void>;
24
24
  export declare function stat(this: V_Context, path: PathLike, lstat: boolean): Promise<InodeLike>;
25
+ export declare function chmod(this: V_Context, path: PathLike, mode: Mode, noFollow?: boolean): Promise<void>;
26
+ export declare function chown(this: V_Context, path: PathLike, uid: number, gid: number, noFollow?: boolean): Promise<void>;
27
+ export declare function utimes(this: V_Context, path: PathLike, atime: TimeLike, mtime: TimeLike, noFollow?: boolean): Promise<void>;