@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
package/dist/node/sync.js CHANGED
@@ -56,13 +56,14 @@ import { encodeUTF8 } from 'utilium';
56
56
  import * as constants from '../constants.js';
57
57
  import { wrap } from '../internal/error.js';
58
58
  import { hasAccess, isDirectory } from '../internal/inode.js';
59
- import { join, matchesGlob } from '../path.js';
59
+ import { dirname, join, matchesGlob } from '../path.js';
60
60
  import { _tempDirName, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
61
61
  import { checkAccess } from '../vfs/config.js';
62
62
  import { deleteFD, fromFD, toFD } from '../vfs/file.js';
63
63
  import * as flags from '../vfs/flags.js';
64
64
  import { _statfs, resolveMount } from '../vfs/shared.js';
65
65
  import * as _sync from '../vfs/sync.js';
66
+ import { cacheOf, lockPathSync } from '../vfs/vcache.js';
66
67
  import { emitChange } from '../vfs/watchers.js';
67
68
  import { Dir, Dirent } from './dir.js';
68
69
  import { BigIntStats, Stats } from './stats.js';
@@ -118,13 +119,25 @@ export function unlinkSync(path) {
118
119
  path = normalizePath(path);
119
120
  const { fs, path: resolved } = resolveMount(path, this);
120
121
  try {
121
- if (checkAccess && !hasAccess(this, fs.statSync(resolved), constants.W_OK)) {
122
- throw UV('EACCES', 'unlink');
122
+ const env_2 = { stack: [], error: void 0, hasError: false };
123
+ try {
124
+ if (checkAccess && !hasAccess(this, fs.statSync(resolved), constants.W_OK)) {
125
+ throw UV('EACCES', 'unlink');
126
+ }
127
+ const _ = __addDisposableResource(env_2, lockPathSync(fs, dirname(resolved), 'rw'), false);
128
+ fs.unlinkSync(resolved);
129
+ cacheOf(fs).remove(resolved);
130
+ }
131
+ catch (e_2) {
132
+ env_2.error = e_2;
133
+ env_2.hasError = true;
134
+ }
135
+ finally {
136
+ __disposeResources(env_2);
123
137
  }
124
- fs.unlinkSync(resolved);
125
138
  }
126
139
  catch (e) {
127
- throw setUVMessage(Object.assign(e, { path }));
140
+ throw setUVMessage(Object.assign(e, { syscall: 'unlink', path }));
128
141
  }
129
142
  emitChange(this, 'rename', path.toString());
130
143
  }
@@ -146,13 +159,13 @@ export function lopenSync(path, flag, mode) {
146
159
  return toFD(_sync.open.call(this, path, { flag, mode, preserveSymlinks: true }));
147
160
  }
148
161
  export function readFileSync(path, _options = {}) {
149
- const env_2 = { stack: [], error: void 0, hasError: false };
162
+ const env_3 = { stack: [], error: void 0, hasError: false };
150
163
  try {
151
164
  const options = normalizeOptions(_options, null, 'r', 0o644);
152
165
  const flag = flags.parse(options.flag);
153
166
  if (flag & constants.O_WRONLY)
154
167
  throw UV('EBADF', 'read', path.toString());
155
- const file = __addDisposableResource(env_2, typeof path == 'number'
168
+ const file = __addDisposableResource(env_3, typeof path == 'number'
156
169
  ? fromFD(this, path)
157
170
  : _sync.open.call(this, path.toString(), { flag: options.flag, mode: 0o644, preserveSymlinks: false }), false);
158
171
  const { size } = file.stat();
@@ -160,17 +173,17 @@ export function readFileSync(path, _options = {}) {
160
173
  file.readSync(data, 0, size, 0);
161
174
  return options.encoding ? data.toString(options.encoding) : data;
162
175
  }
163
- catch (e_2) {
164
- env_2.error = e_2;
165
- env_2.hasError = true;
176
+ catch (e_3) {
177
+ env_3.error = e_3;
178
+ env_3.hasError = true;
166
179
  }
167
180
  finally {
168
- __disposeResources(env_2);
181
+ __disposeResources(env_3);
169
182
  }
170
183
  }
171
184
  readFileSync;
172
185
  export function writeFileSync(path, data, _options = {}) {
173
- const env_3 = { stack: [], error: void 0, hasError: false };
186
+ const env_4 = { stack: [], error: void 0, hasError: false };
174
187
  try {
175
188
  const options = normalizeOptions(_options, 'utf8', 'w+', 0o644);
176
189
  const flag = flags.parse(options.flag);
@@ -184,7 +197,7 @@ export function writeFileSync(path, data, _options = {}) {
184
197
  if (!encodedData) {
185
198
  throw new Exception(Errno.EINVAL, 'Data not specified');
186
199
  }
187
- const file = __addDisposableResource(env_3, typeof path == 'number'
200
+ const file = __addDisposableResource(env_4, typeof path == 'number'
188
201
  ? fromFD(this, path)
189
202
  : _sync.open.call(this, path.toString(), {
190
203
  flag,
@@ -194,12 +207,12 @@ export function writeFileSync(path, data, _options = {}) {
194
207
  file.writeSync(encodedData, 0, encodedData.byteLength, 0);
195
208
  emitChange(this, 'change', path.toString());
196
209
  }
197
- catch (e_3) {
198
- env_3.error = e_3;
199
- env_3.hasError = true;
210
+ catch (e_4) {
211
+ env_4.error = e_4;
212
+ env_4.hasError = true;
200
213
  }
201
214
  finally {
202
- __disposeResources(env_3);
215
+ __disposeResources(env_4);
203
216
  }
204
217
  }
205
218
  writeFileSync;
@@ -324,15 +337,27 @@ export function futimesSync(fd, atime, mtime) {
324
337
  }
325
338
  futimesSync;
326
339
  export function rmdirSync(path) {
327
- path = normalizePath(path);
328
- const { fs, path: resolved } = _sync.resolve(this, path);
329
- const stats = wrap(fs, 'statSync', path)(resolved);
330
- if (!isDirectory(stats))
331
- throw UV('ENOTDIR', 'rmdir', path);
332
- if (checkAccess && !hasAccess(this, stats, constants.W_OK))
333
- throw UV('EACCES', 'rmdir', path);
334
- wrap(fs, 'rmdirSync', path)(resolved);
335
- emitChange(this, 'rename', path.toString());
340
+ const env_5 = { stack: [], error: void 0, hasError: false };
341
+ try {
342
+ path = normalizePath(path);
343
+ const { fs, path: resolved } = _sync.resolve(this, path);
344
+ const stats = wrap(fs, 'statSync', { path, syscall: 'rmdir' })(resolved);
345
+ if (!isDirectory(stats))
346
+ throw UV('ENOTDIR', 'rmdir', path);
347
+ if (checkAccess && !hasAccess(this, stats, constants.W_OK))
348
+ throw UV('EACCES', 'rmdir', path);
349
+ const _ = __addDisposableResource(env_5, lockPathSync(fs, dirname(resolved), 'rw'), false);
350
+ wrap(fs, 'rmdirSync', path)(resolved);
351
+ cacheOf(fs).remove(resolved);
352
+ emitChange(this, 'rename', path.toString());
353
+ }
354
+ catch (e_5) {
355
+ env_5.error = e_5;
356
+ env_5.hasError = true;
357
+ }
358
+ finally {
359
+ __disposeResources(env_5);
360
+ }
336
361
  }
337
362
  rmdirSync;
338
363
  export function mkdirSync(path, options) {
@@ -348,7 +373,7 @@ export function readdirSync(path, options) {
348
373
  const rawEntries = _sync.readdir.call(this, path, options ?? undefined);
349
374
  for (const raw of rawEntries) {
350
375
  if (options?.withFileTypes) {
351
- entries.push(Dirent.from(raw, options.encoding));
376
+ entries.push(Dirent.from(raw, options.encoding, path));
352
377
  }
353
378
  else if (options?.encoding == 'buffer') {
354
379
  entries.push(Buffer.from(raw.path));
@@ -371,21 +396,21 @@ linkSync;
371
396
  * @param type can be either `'dir'` or `'file'` (default is `'file'`)
372
397
  */
373
398
  export function symlinkSync(target, path, type = 'file') {
374
- const env_4 = { stack: [], error: void 0, hasError: false };
399
+ const env_6 = { stack: [], error: void 0, hasError: false };
375
400
  try {
376
401
  if (!['file', 'dir', 'junction'].includes(type))
377
402
  throw new TypeError('Invalid symlink type: ' + type);
378
403
  path = normalizePath(path);
379
- const file = __addDisposableResource(env_4, _sync.open.call(this, path, { flag: 'wx', mode: 0o644 }), false);
404
+ const file = __addDisposableResource(env_6, _sync.open.call(this, path, { flag: 'wx', mode: 0o644 }), false);
380
405
  file.writeSync(encodeUTF8(normalizePath(target, true)));
381
406
  file.chmodSync(constants.S_IFLNK);
382
407
  }
383
- catch (e_4) {
384
- env_4.error = e_4;
385
- env_4.hasError = true;
408
+ catch (e_6) {
409
+ env_6.error = e_6;
410
+ env_6.hasError = true;
386
411
  }
387
412
  finally {
388
- __disposeResources(env_4);
413
+ __disposeResources(env_6);
389
414
  }
390
415
  }
391
416
  symlinkSync;
@@ -400,17 +425,17 @@ export function readlinkSync(path, options) {
400
425
  }
401
426
  readlinkSync;
402
427
  export function chownSync(path, uid, gid) {
403
- const env_5 = { stack: [], error: void 0, hasError: false };
428
+ const env_7 = { stack: [], error: void 0, hasError: false };
404
429
  try {
405
- const handle = __addDisposableResource(env_5, _sync.open.call(this, path, { flag: 'r+', mode: constants.F_OK }), false);
430
+ const handle = __addDisposableResource(env_7, _sync.open.call(this, path, { flag: 'r+', mode: constants.F_OK }), false);
406
431
  handle.chownSync(uid, gid);
407
432
  }
408
- catch (e_5) {
409
- env_5.error = e_5;
410
- env_5.hasError = true;
433
+ catch (e_7) {
434
+ env_7.error = e_7;
435
+ env_7.hasError = true;
411
436
  }
412
437
  finally {
413
- __disposeResources(env_5);
438
+ __disposeResources(env_7);
414
439
  }
415
440
  }
416
441
  chownSync;
@@ -421,7 +446,13 @@ export function lchownSync(path, uid, gid) {
421
446
  }
422
447
  lchownSync;
423
448
  export function chmodSync(path, mode) {
424
- const fd = openSync.call(this, path, 'r+');
449
+ let fd;
450
+ try {
451
+ fd = openSync.call(this, path, 'r+');
452
+ }
453
+ catch (e) {
454
+ throw setUVMessage(Object.assign(e, { syscall: 'chmod', path: normalizePath(path) }));
455
+ }
425
456
  fchmodSync.call(this, fd, mode);
426
457
  closeSync.call(this, fd);
427
458
  }
package/dist/utils.d.ts CHANGED
@@ -25,7 +25,14 @@ export type Callback<Args extends unknown[] = [], NoError = null> = (e: Exceptio
25
25
  */
26
26
  export declare function normalizeMode(mode: unknown, def?: number): number;
27
27
  /**
28
- * Normalizes a time
28
+ * Checks that `fd` is in the range Node accepts for a file descriptor.
29
+ * Note this throws a `RangeError` rather than an `Exception`, since that is what Node does.
30
+ * @internal
31
+ */
32
+ export declare function validateFD(fd: number): void;
33
+ /**
34
+ * Normalizes a time to milliseconds since the epoch.
35
+ * Note numbers and numeric strings are seconds, like Node's `toUnixTimestamp`.
29
36
  * @internal
30
37
  */
31
38
  export declare function normalizeTime(time: fs.TimeLike): number;
package/dist/utils.js CHANGED
@@ -35,18 +35,36 @@ export function normalizeMode(mode, def) {
35
35
  throw withErrno('EINVAL', 'Invalid mode: ' + mode?.toString());
36
36
  }
37
37
  /**
38
- * Normalizes a time
38
+ * The largest valid file descriptor, since Node validates them as 32-bit signed integers
39
+ * @internal @hidden
40
+ */
41
+ const fd_max = 0x7fffffff;
42
+ /**
43
+ * Checks that `fd` is in the range Node accepts for a file descriptor.
44
+ * Note this throws a `RangeError` rather than an `Exception`, since that is what Node does.
45
+ * @internal
46
+ */
47
+ export function validateFD(fd) {
48
+ if (Number.isInteger(fd) && fd >= 0 && fd <= fd_max)
49
+ return;
50
+ throw Object.assign(new RangeError(`The value of "fd" is out of range. It must be >= 0 && <= ${fd_max}. Received ${fd}`), {
51
+ code: 'ERR_OUT_OF_RANGE',
52
+ });
53
+ }
54
+ /**
55
+ * Normalizes a time to milliseconds since the epoch.
56
+ * Note numbers and numeric strings are seconds, like Node's `toUnixTimestamp`.
39
57
  * @internal
40
58
  */
41
59
  export function normalizeTime(time) {
60
+ // Dates are already in milliseconds
42
61
  if (time instanceof Date)
43
62
  return time.getTime();
44
- try {
45
- return Number(time);
46
- }
47
- catch {
63
+ const seconds = Number(time);
64
+ if (!Number.isFinite(seconds))
48
65
  throw withErrno('EINVAL', 'Invalid time.');
49
- }
66
+ // Negative times mean "now"
67
+ return seconds < 0 ? Date.now() : seconds * 1000;
50
68
  }
51
69
  /**
52
70
  * Normalizes a path
@@ -119,5 +137,6 @@ export async function waitOnline(worker) {
119
137
  * @internal @hidden
120
138
  */
121
139
  export function _tempDirName(prefix) {
122
- return `/tmp/${normalizePath(prefix, true)}${Date.now()}-${Math.random().toString(36).slice(2)}`;
140
+ // Like Node, the prefix is a path. A relative one is resolved against the working directory, not `/tmp`.
141
+ return `${normalizePath(prefix, true)}${Date.now()}-${Math.random().toString(36).slice(2)}`;
123
142
  }