@zenfs/core 2.2.3 → 2.3.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 (53) hide show
  1. package/dist/backends/backend.js +6 -9
  2. package/dist/backends/cow.js +4 -4
  3. package/dist/backends/fetch.js +8 -6
  4. package/dist/backends/memory.js +4 -2
  5. package/dist/backends/passthrough.js +2 -0
  6. package/dist/backends/port.d.ts +16 -89
  7. package/dist/backends/port.js +35 -171
  8. package/dist/backends/single_buffer.d.ts +4 -2
  9. package/dist/backends/single_buffer.js +169 -196
  10. package/dist/backends/store/fs.js +50 -73
  11. package/dist/backends/store/map.js +1 -2
  12. package/dist/backends/store/store.js +23 -27
  13. package/dist/config.js +2 -3
  14. package/dist/context.js +2 -2
  15. package/dist/internal/devices.js +7 -10
  16. package/dist/internal/file_index.js +3 -8
  17. package/dist/internal/filesystem.js +19 -12
  18. package/dist/internal/index_fs.js +3 -4
  19. package/dist/internal/inode.d.ts +2 -0
  20. package/dist/internal/inode.js +148 -185
  21. package/dist/internal/rpc.d.ts +143 -0
  22. package/dist/internal/rpc.js +251 -0
  23. package/dist/mixins/async.js +5 -6
  24. package/dist/mixins/mutexed.js +16 -10
  25. package/dist/path.js +3 -4
  26. package/dist/polyfills.js +51 -22
  27. package/dist/readline.js +32 -30
  28. package/dist/utils.d.ts +2 -0
  29. package/dist/utils.js +11 -5
  30. package/dist/vfs/acl.d.ts +2 -0
  31. package/dist/vfs/acl.js +48 -66
  32. package/dist/vfs/async.js +4 -4
  33. package/dist/vfs/dir.js +12 -8
  34. package/dist/vfs/file.js +22 -18
  35. package/dist/vfs/ioctl.js +39 -62
  36. package/dist/vfs/promises.js +48 -39
  37. package/dist/vfs/shared.js +4 -5
  38. package/dist/vfs/stats.js +104 -77
  39. package/dist/vfs/streams.js +11 -8
  40. package/dist/vfs/sync.js +23 -26
  41. package/dist/vfs/watchers.js +9 -3
  42. package/dist/vfs/xattr.js +6 -12
  43. package/package.json +1 -1
  44. package/scripts/test.js +14 -7
  45. package/tests/backend/fetch.test.ts +14 -14
  46. package/tests/backend/port.test.ts +25 -17
  47. package/tests/common/handle.test.ts +5 -3
  48. package/tests/fetch/run.sh +2 -1
  49. package/tests/fs/scaling.test.ts +32 -0
  50. package/tests/fs/watch.test.ts +2 -5
  51. package/tests/setup/single-buffer.ts +1 -1
  52. package/tests/tsconfig.json +3 -2
  53. package/types/uint8array.d.ts +64 -0
package/dist/vfs/sync.js CHANGED
@@ -134,7 +134,7 @@ export function statSync(path, options) {
134
134
  }
135
135
  if (checkAccess && !hasAccess(this, stats, constants.R_OK))
136
136
  throw UV('EACCES', { syscall: 'stat', path });
137
- return (options === null || options === void 0 ? void 0 : options.bigint) ? new BigIntStats(stats) : new Stats(stats);
137
+ return options?.bigint ? new BigIntStats(stats) : new Stats(stats);
138
138
  }
139
139
  statSync;
140
140
  export function lstatSync(path, options) {
@@ -143,14 +143,14 @@ export function lstatSync(path, options) {
143
143
  const stats = wrap(fs, 'statSync', path)(resolved);
144
144
  if (checkAccess && !hasAccess(this, stats, constants.R_OK))
145
145
  throw UV('EACCES', { syscall: 'lstat', path });
146
- return (options === null || options === void 0 ? void 0 : options.bigint) ? new BigIntStats(stats) : new Stats(stats);
146
+ return options?.bigint ? new BigIntStats(stats) : new Stats(stats);
147
147
  }
148
148
  lstatSync;
149
149
  export function truncateSync(path, len = 0) {
150
150
  const env_1 = { stack: [], error: void 0, hasError: false };
151
151
  try {
152
152
  const file = __addDisposableResource(env_1, _openSync.call(this, path, { flag: 'r+' }), false);
153
- len || (len = 0);
153
+ len ||= 0;
154
154
  if (len < 0)
155
155
  throw UV('EINVAL', 'truncate', path.toString());
156
156
  file.truncate(len);
@@ -180,7 +180,6 @@ export function unlinkSync(path) {
180
180
  }
181
181
  unlinkSync;
182
182
  function _openSync(path, opt) {
183
- var _a;
184
183
  path = normalizePath(path);
185
184
  const mode = normalizeMode(opt.mode, 0o644), flag = flags.parse(opt.flag);
186
185
  path = opt.preserveSymlinks ? path : realpathSync.call(this, path);
@@ -209,7 +208,7 @@ function _openSync(path, opt) {
209
208
  if (checkAccess && !hasAccess(this, parentStats, constants.W_OK)) {
210
209
  throw UV('EACCES', 'open', path);
211
210
  }
212
- const { euid: uid, egid: gid } = (_a = this === null || this === void 0 ? void 0 : this.credentials) !== null && _a !== void 0 ? _a : defaultContext.credentials;
211
+ const { euid: uid, egid: gid } = this?.credentials ?? defaultContext.credentials;
213
212
  const inode = fs.createFileSync(resolved, {
214
213
  mode,
215
214
  uid: parentStats.mode & constants.S_ISUID ? parentStats.uid : uid,
@@ -339,7 +338,7 @@ export function appendFileSync(filename, data, _options = {}) {
339
338
  appendFileSync;
340
339
  export function fstatSync(fd, options) {
341
340
  const stats = fromFD(this, fd).stat();
342
- return (options === null || options === void 0 ? void 0 : options.bigint) ? new BigIntStats(stats) : new Stats(stats);
341
+ return options?.bigint ? new BigIntStats(stats) : new Stats(stats);
343
342
  }
344
343
  fstatSync;
345
344
  export function closeSync(fd) {
@@ -348,7 +347,7 @@ export function closeSync(fd) {
348
347
  }
349
348
  closeSync;
350
349
  export function ftruncateSync(fd, len = 0) {
351
- len || (len = 0);
350
+ len ||= 0;
352
351
  if (len < 0) {
353
352
  throw new Exception(Errno.EINVAL);
354
353
  }
@@ -381,7 +380,7 @@ export function writeSync(fd, data, posOrOff, lenOrEnc, pos) {
381
380
  position = typeof pos === 'number' ? pos : null;
382
381
  }
383
382
  const file = fromFD(this, fd);
384
- position !== null && position !== void 0 ? position : (position = file.position);
383
+ position ??= file.position;
385
384
  const bytesWritten = file.write(buffer, offset, length, position);
386
385
  emitChange(this, 'change', file.path);
387
386
  return bytesWritten;
@@ -441,10 +440,9 @@ export function rmdirSync(path) {
441
440
  }
442
441
  rmdirSync;
443
442
  export function mkdirSync(path, options) {
444
- var _a;
445
- const { euid: uid, egid: gid } = (_a = this === null || this === void 0 ? void 0 : this.credentials) !== null && _a !== void 0 ? _a : defaultContext.credentials;
443
+ const { euid: uid, egid: gid } = this?.credentials ?? defaultContext.credentials;
446
444
  options = typeof options === 'object' ? options : { mode: options };
447
- const mode = normalizeMode(options === null || options === void 0 ? void 0 : options.mode, 0o777);
445
+ const mode = normalizeMode(options?.mode, 0o777);
448
446
  path = realpathSync.call(this, path);
449
447
  const { fs, path: resolved } = resolveMount(path, this);
450
448
  const __create = (path, resolved, parent) => {
@@ -458,7 +456,7 @@ export function mkdirSync(path, options) {
458
456
  emitChange(this, 'rename', path);
459
457
  return inode;
460
458
  };
461
- if (!(options === null || options === void 0 ? void 0 : options.recursive)) {
459
+ if (!options?.recursive) {
462
460
  __create(path, resolved, wrap(fs, 'statSync', dirname(path))(dirname(resolved)));
463
461
  return;
464
462
  }
@@ -495,16 +493,16 @@ export function readdirSync(path, options) {
495
493
  catch {
496
494
  continue;
497
495
  }
498
- if (options === null || options === void 0 ? void 0 : options.withFileTypes) {
496
+ if (options?.withFileTypes) {
499
497
  values.push(new Dirent(entry, entryStat, options.encoding));
500
498
  }
501
- else if ((options === null || options === void 0 ? void 0 : options.encoding) == 'buffer') {
499
+ else if (options?.encoding == 'buffer') {
502
500
  values.push(Buffer.from(entry));
503
501
  }
504
502
  else {
505
503
  values.push(entry);
506
504
  }
507
- if (!isDirectory(entryStat) || !(options === null || options === void 0 ? void 0 : options.recursive))
505
+ if (!isDirectory(entryStat) || !options?.recursive)
508
506
  continue;
509
507
  for (const subEntry of readdirSync.call(this, join(path, entry), options)) {
510
508
  if (subEntry instanceof Dirent) {
@@ -576,12 +574,12 @@ export function readlinkSync(path, options) {
576
574
  const size = handle.inode.size;
577
575
  const data = Buffer.alloc(size);
578
576
  handle.read(data, 0, size, 0);
579
- const encoding = typeof options == 'object' ? options === null || options === void 0 ? void 0 : options.encoding : options;
577
+ const encoding = typeof options == 'object' ? options?.encoding : options;
580
578
  if (encoding == 'buffer') {
581
579
  return data;
582
580
  }
583
581
  // always defaults to utf-8 to avoid wrangler (cloudflare) worker "unknown encoding" exception
584
- return data.toString(encoding !== null && encoding !== void 0 ? encoding : 'utf-8');
582
+ return data.toString(encoding ?? 'utf-8');
585
583
  }
586
584
  catch (e_6) {
587
585
  env_6.error = e_6;
@@ -680,8 +678,7 @@ function _resolveSync($, path, preserveSymlinks) {
680
678
  return _resolveSync($, target);
681
679
  }
682
680
  export function realpathSync(path, options) {
683
- var _a;
684
- const encoding = typeof options == 'string' ? options : ((_a = options === null || options === void 0 ? void 0 : options.encoding) !== null && _a !== void 0 ? _a : 'utf8');
681
+ const encoding = typeof options == 'string' ? options : (options?.encoding ?? 'utf8');
685
682
  path = normalizePath(path);
686
683
  const { fullPath } = _resolveSync(this, path);
687
684
  if (encoding == 'utf8' || encoding == 'utf-8')
@@ -711,14 +708,14 @@ export function rmSync(path, options) {
711
708
  stats = lstatSync.bind(this)(path);
712
709
  }
713
710
  catch (error) {
714
- if (error.code != 'ENOENT' || !(options === null || options === void 0 ? void 0 : options.force))
711
+ if (error.code != 'ENOENT' || !options?.force)
715
712
  throw error;
716
713
  }
717
714
  if (!stats)
718
715
  return;
719
716
  switch (stats.mode & constants.S_IFMT) {
720
717
  case constants.S_IFDIR:
721
- if (options === null || options === void 0 ? void 0 : options.recursive) {
718
+ if (options?.recursive) {
722
719
  for (const entry of readdirSync.call(this, path)) {
723
720
  rmSync.call(this, join(path, entry), options);
724
721
  }
@@ -739,7 +736,7 @@ export function rmSync(path, options) {
739
736
  }
740
737
  rmSync;
741
738
  export function mkdtempSync(prefix, options) {
742
- const encoding = typeof options === 'object' ? options === null || options === void 0 ? void 0 : options.encoding : options || 'utf8';
739
+ const encoding = typeof options === 'object' ? options?.encoding : options || 'utf8';
743
740
  const fsName = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`;
744
741
  const resolvedPath = '/tmp/' + fsName;
745
742
  mkdirSync.call(this, resolvedPath);
@@ -820,11 +817,11 @@ export function cpSync(source, destination, opts) {
820
817
  source = normalizePath(source);
821
818
  destination = normalizePath(destination);
822
819
  const srcStats = lstatSync.call(this, source); // Use lstat to follow symlinks if not dereferencing
823
- if ((opts === null || opts === void 0 ? void 0 : opts.errorOnExist) && existsSync.call(this, destination))
820
+ if (opts?.errorOnExist && existsSync.call(this, destination))
824
821
  throw UV('EEXIST', 'cp', destination);
825
822
  switch (srcStats.mode & constants.S_IFMT) {
826
823
  case constants.S_IFDIR:
827
- if (!(opts === null || opts === void 0 ? void 0 : opts.recursive))
824
+ if (!opts?.recursive)
828
825
  throw UV('EISDIR', 'cp', source);
829
826
  mkdirSync.call(this, destination, { recursive: true }); // Ensure the destination directory exists
830
827
  for (const dirent of readdirSync.call(this, source, { withFileTypes: true })) {
@@ -846,7 +843,7 @@ export function cpSync(source, destination, opts) {
846
843
  throw UV('ENOSYS', 'cp', source);
847
844
  }
848
845
  // Optionally preserve timestamps
849
- if (opts === null || opts === void 0 ? void 0 : opts.preserveTimestamps) {
846
+ if (opts?.preserveTimestamps) {
850
847
  utimesSync.call(this, destination, srcStats.atime, srcStats.mtime);
851
848
  }
852
849
  }
@@ -854,7 +851,7 @@ cpSync;
854
851
  export function statfsSync(path, options) {
855
852
  path = normalizePath(path);
856
853
  const { fs } = resolveMount(path, this);
857
- return _statfs(fs, options === null || options === void 0 ? void 0 : options.bigint);
854
+ return _statfs(fs, options?.bigint);
858
855
  }
859
856
  export function globSync(pattern, options = {}) {
860
857
  pattern = Array.isArray(pattern) ? pattern : [pattern];
@@ -11,6 +11,8 @@ import { statSync } from './sync.js';
11
11
  * @template TEvents The type of events emitted by the watcher.
12
12
  */
13
13
  class Watcher extends EventEmitter {
14
+ _context;
15
+ path;
14
16
  /* eslint-disable @typescript-eslint/no-explicit-any */
15
17
  off(event, fn, context, once) {
16
18
  return super.off(event, fn, context, once);
@@ -56,10 +58,12 @@ class Watcher extends EventEmitter {
56
58
  * @template T The type of the filename, either `string` or `Buffer`.
57
59
  */
58
60
  export class FSWatcher extends Watcher {
61
+ options;
62
+ realpath;
59
63
  constructor(context, path, options) {
60
64
  super(context, path);
61
65
  this.options = options;
62
- this.realpath = (context === null || context === void 0 ? void 0 : context.root) ? join(context.root, path) : path;
66
+ this.realpath = context?.root ? join(context.root, path) : path;
63
67
  addWatcher(this.realpath, this);
64
68
  }
65
69
  close() {
@@ -76,6 +80,9 @@ export class FSWatcher extends Watcher {
76
80
  * Instances of `StatWatcher` are used by `fs.watchFile()` to monitor changes to a file's statistics.
77
81
  */
78
82
  export class StatWatcher extends Watcher {
83
+ options;
84
+ intervalId;
85
+ previous;
79
86
  constructor(context, path, options) {
80
87
  super(context, path);
81
88
  this.options = options;
@@ -139,9 +146,8 @@ export function removeWatcher(path, watcher) {
139
146
  * @internal @hidden
140
147
  */
141
148
  export function emitChange($, eventType, filename) {
142
- var _a;
143
149
  if ($)
144
- filename = join((_a = $.root) !== null && _a !== void 0 ? _a : '/', filename);
150
+ filename = join($.root ?? '/', filename);
145
151
  filename = normalizePath(filename);
146
152
  // Notify watchers, including ones on parent directories if they are watching recursively
147
153
  for (let path = filename; path != '/'; path = dirname(path)) {
package/dist/vfs/xattr.js CHANGED
@@ -16,14 +16,13 @@ function checkName($, name, path, syscall) {
16
16
  throw UV('ENOTSUP', syscall, path);
17
17
  }
18
18
  export async function get(path, name, opt = {}) {
19
- var _a;
20
19
  path = normalizePath(path);
21
20
  const { fs, path: resolved } = resolveMount(path, this);
22
21
  checkName(this, name, path, 'xattr.get');
23
22
  const inode = await fs.stat(resolved).catch(rethrow('xattr.get', path));
24
23
  if (checkAccess && !hasAccess(this, inode, R_OK))
25
24
  throw UV('EACCES', 'xattr.get', path);
26
- (_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
25
+ inode.attributes ??= new Attributes();
27
26
  const value = inode.attributes.get(name);
28
27
  if (!value)
29
28
  throw UV('ENODATA', 'xattr.get', path);
@@ -31,7 +30,6 @@ export async function get(path, name, opt = {}) {
31
30
  return opt.encoding == 'buffer' || !opt.encoding ? buffer : buffer.toString(opt.encoding);
32
31
  }
33
32
  export function getSync(path, name, opt = {}) {
34
- var _a;
35
33
  path = normalizePath(path);
36
34
  checkName(this, name, path, 'xattr.get');
37
35
  const { fs, path: resolved } = resolveMount(path, this);
@@ -44,7 +42,7 @@ export function getSync(path, name, opt = {}) {
44
42
  }
45
43
  if (checkAccess && !hasAccess(this, inode, R_OK))
46
44
  throw UV('EACCES', 'xattr.get', path);
47
- (_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
45
+ inode.attributes ??= new Attributes();
48
46
  const value = inode.attributes.get(name);
49
47
  if (!value)
50
48
  throw UV('ENODATA', 'xattr.get', path);
@@ -60,14 +58,13 @@ export function getSync(path, name, opt = {}) {
60
58
  * @param opt Options for the operation
61
59
  */
62
60
  export async function set(path, name, value, opt = {}) {
63
- var _a;
64
61
  path = normalizePath(path);
65
62
  const { fs, path: resolved } = resolveMount(path, this);
66
63
  checkName(this, name, path, 'xattr.set');
67
64
  const inode = await fs.stat(resolved).catch(rethrow('xattr.set', path));
68
65
  if (checkAccess && !hasAccess(this, inode, W_OK))
69
66
  throw UV('EACCES', 'xattr.set', path);
70
- (_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
67
+ inode.attributes ??= new Attributes();
71
68
  const attr = inode.attributes.get(name);
72
69
  if (opt.create && attr)
73
70
  throw UV('EEXIST', 'xattr.set', path);
@@ -85,7 +82,6 @@ export async function set(path, name, value, opt = {}) {
85
82
  * @param opt Options for the operation
86
83
  */
87
84
  export function setSync(path, name, value, opt = {}) {
88
- var _a;
89
85
  path = normalizePath(path);
90
86
  const { fs, path: resolved } = resolveMount(path, this);
91
87
  checkName(this, name, path, 'xattr.set');
@@ -98,7 +94,7 @@ export function setSync(path, name, value, opt = {}) {
98
94
  }
99
95
  if (checkAccess && !hasAccess(this, inode, W_OK))
100
96
  throw UV('EACCES', 'xattr.set', path);
101
- (_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
97
+ inode.attributes ??= new Attributes();
102
98
  const attr = inode.attributes.get(name);
103
99
  if (opt.create && attr)
104
100
  throw UV('EEXIST', 'xattr.set', path);
@@ -119,14 +115,13 @@ export function setSync(path, name, value, opt = {}) {
119
115
  * @param name Name of the attribute to remove
120
116
  */
121
117
  export async function remove(path, name) {
122
- var _a;
123
118
  path = normalizePath(path);
124
119
  const { fs, path: resolved } = resolveMount(path, this);
125
120
  checkName(this, name, path, 'xattr.remove');
126
121
  const inode = await fs.stat(resolved).catch(rethrow('xattr.remove', path));
127
122
  if (checkAccess && !hasAccess(this, inode, W_OK))
128
123
  throw UV('EACCES', 'xattr.remove', path);
129
- (_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
124
+ inode.attributes ??= new Attributes();
130
125
  const attr = inode.attributes.get(name);
131
126
  if (!attr)
132
127
  throw UV('ENODATA', 'xattr.remove', path);
@@ -140,7 +135,6 @@ export async function remove(path, name) {
140
135
  * @param name Name of the attribute to remove
141
136
  */
142
137
  export function removeSync(path, name) {
143
- var _a;
144
138
  path = normalizePath(path);
145
139
  const { fs, path: resolved } = resolveMount(path, this);
146
140
  checkName(this, name, path, 'xattr.remove');
@@ -153,7 +147,7 @@ export function removeSync(path, name) {
153
147
  }
154
148
  if (checkAccess && !hasAccess(this, inode, W_OK))
155
149
  throw UV('EACCES', 'xattr.remove', path);
156
- (_a = inode.attributes) !== null && _a !== void 0 ? _a : (inode.attributes = new Attributes());
150
+ inode.attributes ??= new Attributes();
157
151
  const attr = inode.attributes.get(name);
158
152
  if (!attr)
159
153
  throw UV('ENODATA', 'xattr.remove', path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "2.2.3",
3
+ "version": "2.3.0",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
package/scripts/test.js CHANGED
@@ -89,11 +89,12 @@ if (options.ci) ci = await import('./ci.js');
89
89
  options.verbose && options.force && console.debug('Forcing tests to exit (--test-force-exit)');
90
90
 
91
91
  if (options.build) {
92
- !options.quiet && console.log('Building...');
92
+ !options.quiet && process.stdout.write('Building... ');
93
93
  try {
94
94
  execSync('npm run build');
95
+ console.log('done.');
95
96
  } catch {
96
- console.warn('Build failed, continuing without it.');
97
+ console.warn('failed, continuing without it.');
97
98
  }
98
99
  }
99
100
 
@@ -141,17 +142,19 @@ async function status(name) {
141
142
  return color(`(${delta} ${unit})`, '2;37');
142
143
  };
143
144
 
145
+ const maybeName = options.verbose ? `: ${name}` : '';
146
+
144
147
  return {
145
148
  async pass() {
146
- if (!options.quiet) console.log(`${color('passed', 32)}: ${name} ${time()}`);
149
+ if (!options.quiet) console.log(`${color('passed', 32)}${maybeName} ${time()}`);
147
150
  if (options.ci) await ci.completeCheck(name, 'success');
148
151
  },
149
152
  async skip() {
150
- if (!options.quiet) console.log(`${color('skipped', 33)}: ${name} ${time()}`);
153
+ if (!options.quiet) console.log(`${color('skipped', 33)}${maybeName} ${time()}`);
151
154
  if (options.ci) await ci.completeCheck(name, 'skipped');
152
155
  },
153
156
  async fail() {
154
- console.error(`${color('failed', '1;31')}: ${name} ${time()}`);
157
+ console.error(`${color('failed', '1;31')}${maybeName} ${time()}`);
155
158
  if (options.ci) await ci.completeCheck(name, 'failure');
156
159
  process.exitCode = 1;
157
160
  if (options['exit-on-fail']) process.exit();
@@ -163,7 +166,7 @@ if (!options.preserve) rmSync(options.coverage, { force: true, recursive: true }
163
166
  mkdirSync(options.coverage, { recursive: true });
164
167
 
165
168
  if (options.common) {
166
- !options.quiet && console.log('Running common tests...');
169
+ !options.quiet && process.stdout.write('Running common tests...' + (options.verbose ? '\n' : ' '));
167
170
  const { pass, fail } = await status('Common tests');
168
171
  try {
169
172
  execSync(
@@ -187,10 +190,14 @@ for (const setupFile of positionals) {
187
190
  }
188
191
 
189
192
  process.env.SETUP = setupFile;
193
+ process.env.VERBOSE = +options.verbose;
190
194
 
191
195
  const name = options['file-names'] && !options.ci ? setupFile : parse(setupFile).name;
192
196
 
193
- !options.quiet && console.log('Running tests:', name);
197
+ if (!options.quiet) {
198
+ if (options.verbose) console.log('Running tests:', name);
199
+ else process.stdout.write(`Running tests: ${name}... `);
200
+ }
194
201
 
195
202
  const { pass, fail, skip } = await status(name);
196
203
 
@@ -1,6 +1,6 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { join } from 'node:path';
3
- import { suite, test } from 'node:test';
3
+ import { after, suite, test } from 'node:test';
4
4
  import { Worker } from 'node:worker_threads';
5
5
  import { Fetch, configureSingle, fs, mounts, type FetchFS } from '../../dist/index.js';
6
6
  import { baseUrl, defaultEntries, indexPath, whenServerReady } from '../fetch/config.js';
@@ -12,17 +12,15 @@ const server = new Worker(join(import.meta.dirname, '../fetch/server.js'));
12
12
 
13
13
  await whenServerReady();
14
14
 
15
- await suite('Fetch with `disableAsyncCache`', () => {
16
- test('Configuration', async () => {
17
- await configureSingle({
18
- backend: Fetch,
19
- disableAsyncCache: true,
20
- remoteWrite: true,
21
- baseUrl,
22
- index: baseUrl + indexPath,
23
- });
24
- });
15
+ await configureSingle({
16
+ backend: Fetch,
17
+ disableAsyncCache: true,
18
+ remoteWrite: true,
19
+ baseUrl,
20
+ index: baseUrl + indexPath,
21
+ });
25
22
 
23
+ suite('Fetch with `disableAsyncCache`', () => {
26
24
  test('Read and write file', async () => {
27
25
  await fs.promises.writeFile('/example', 'test');
28
26
 
@@ -45,9 +43,11 @@ await suite('Fetch with `disableAsyncCache`', () => {
45
43
 
46
44
  test('Uncached synchronous operations throw', async () => {
47
45
  assert.throws(() => fs.readFileSync('/x.txt', 'utf8'), { code: 'EAGAIN' });
48
- await (mounts.get('/') as FetchFS)._asyncDone;
49
46
  });
50
47
  });
51
48
 
52
- await server.terminate();
53
- server.unref();
49
+ after(async () => {
50
+ await (mounts.get('/') as FetchFS)._asyncDone;
51
+ await server.terminate();
52
+ server.unref();
53
+ });
@@ -1,9 +1,9 @@
1
1
  import assert from 'node:assert/strict';
2
- import { suite, test } from 'node:test';
2
+ import { after, suite, test } from 'node:test';
3
3
  import { MessageChannel, Worker } from 'node:worker_threads';
4
- import { Port, attachFS, waitOnline } from '../../dist/backends/port.js';
4
+ import { Port, attachFS } from '../../dist/backends/port.js';
5
5
  import type { InMemoryStore, StoreFS } from '../../dist/index.js';
6
- import { InMemory, configure, configureSingle, fs, resolveMountConfig } from '../../dist/index.js';
6
+ import { InMemory, configure, configureSingle, fs, resolveMountConfig, waitOnline } from '../../dist/index.js';
7
7
  import { setupLogs } from '../logs.js';
8
8
  setupLogs();
9
9
 
@@ -29,16 +29,18 @@ await suite('Timeout', { timeout: 1000 }, () => {
29
29
 
30
30
  await assert.rejects(configured, { code: 'EIO', message: /RPC Failed/ });
31
31
  });
32
- });
33
32
 
34
- timeoutChannel.port1.unref();
33
+ after(() => {
34
+ timeoutChannel.port1.unref();
35
+ });
36
+ });
35
37
 
36
38
  // Test configuration
37
39
 
38
40
  const configPort = new Worker(import.meta.dirname + '/config.worker.js');
39
41
  await waitOnline(configPort);
40
42
 
41
- await suite('Remote FS with resolveRemoteMount', () => {
43
+ suite('Remote FS with resolveRemoteMount', () => {
42
44
  const content = 'FS is in a port';
43
45
 
44
46
  test('Configuration', async () => {
@@ -52,10 +54,12 @@ await suite('Remote FS with resolveRemoteMount', () => {
52
54
  test('Read', async () => {
53
55
  assert.equal(await fs.promises.readFile('/test', 'utf8'), content);
54
56
  });
55
- });
56
57
 
57
- await configPort.terminate();
58
- configPort.unref();
58
+ after(async () => {
59
+ await configPort.terminate();
60
+ configPort.unref();
61
+ });
62
+ });
59
63
 
60
64
  // Test using a message channel
61
65
 
@@ -87,12 +91,14 @@ await suite('FS with MessageChannel', () => {
87
91
  test('readFileSync should throw', () => {
88
92
  assert.throws(() => fs.readFileSync('/test', 'utf8'), { code: 'ENOTSUP' });
89
93
  });
90
- });
91
94
 
92
- channel.port1.close();
93
- channel.port2.close();
94
- channel.port1.unref();
95
- channel.port2.unref();
95
+ after(() => {
96
+ channel.port1.close();
97
+ channel.port2.close();
98
+ channel.port1.unref();
99
+ channel.port2.unref();
100
+ });
101
+ });
96
102
 
97
103
  // Test using a worker
98
104
 
@@ -112,7 +118,9 @@ await suite('Remote FS', () => {
112
118
  test('Read', async () => {
113
119
  assert.equal(await fs.promises.readFile('/test', 'utf8'), content);
114
120
  });
115
- });
116
121
 
117
- await remotePort.terminate();
118
- remotePort.unref();
122
+ after(async () => {
123
+ await remotePort.terminate();
124
+ remotePort.unref();
125
+ });
126
+ });
@@ -1,14 +1,14 @@
1
1
  import assert from 'node:assert/strict';
2
- import { suite, test } from 'node:test';
2
+ import { after, suite, test } from 'node:test';
3
3
  import { wait } from 'utilium';
4
4
  import { constants, type FileHandle, open } from '../../dist/vfs/promises.js';
5
5
 
6
6
  const content = 'The cake is a lie',
7
7
  appended = '\nAnother lie';
8
8
 
9
- await using handle: FileHandle = await open('./test.txt', 'ws+');
9
+ const handle: FileHandle = await open('./test.txt', 'ws+');
10
10
 
11
- await suite('FileHandle', () => {
11
+ suite('FileHandle', () => {
12
12
  test('writeFile', async () => {
13
13
  await handle.writeFile(content);
14
14
  await handle.sync();
@@ -63,3 +63,5 @@ await suite('FileHandle', () => {
63
63
  assert.deepEqual(lines, ['first line', 'second line', 'third line']);
64
64
  });
65
65
  });
66
+
67
+ after(() => handle.close());
@@ -6,7 +6,8 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6
6
  node $SCRIPT_DIR/server.js &
7
7
  PID=$!
8
8
 
9
- echo "Waiting for server to start..."
9
+ echo -n "Waiting for server to start..."
10
+ if [ -n "$VERBOSE" ]; then echo; fi
10
11
  until nc -z localhost 26514; do
11
12
  sleep 0.25
12
13
  done
@@ -0,0 +1,32 @@
1
+ import assert from 'node:assert/strict';
2
+ import { suite, test } from 'node:test';
3
+ import { fs } from '../common.js';
4
+
5
+ const n_files = 130;
6
+ const huge_size = 0x1000000;
7
+
8
+ // Tests for having a lot of various things (number of inodes/files, individual file size, etc.).
9
+ suite('Scaling', () => {
10
+ test('Lots of inodes/files', async () => {
11
+ fs.mkdirSync('/n');
12
+
13
+ for (let i = 0; i < n_files; i++) {
14
+ fs.writeFileSync('/n/' + i, i.toString(16));
15
+ }
16
+
17
+ assert.equal(fs.readdirSync('/n').length, n_files);
18
+
19
+ const results = [];
20
+
21
+ for (let i = 0; i < n_files; i++) {
22
+ results.push(fs.promises.readFile('/n/' + i, 'utf8').then(val => assert.equal(val, i.toString(16))));
23
+ }
24
+
25
+ await Promise.all(results);
26
+ });
27
+
28
+ test('Singular file size', () => {
29
+ fs.writeFileSync('/huge', new Uint8Array(huge_size));
30
+ assert.equal(fs.statSync('/huge').size, huge_size);
31
+ });
32
+ });
@@ -9,9 +9,9 @@ await fs.promises.mkdir(testDir);
9
9
  await fs.promises.writeFile(testFile, 'Initial content');
10
10
 
11
11
  /**
12
- * @todo convert using watcher to void discards pending ES proposal
12
+ * @todo convert `using watcher = ...` to void discards pending ES proposal
13
13
  */
14
- await suite('Watch', () => {
14
+ suite('Watch', async () => {
15
15
  test('Events emitted on file change', async () => {
16
16
  const { promise, resolve } = Promise.withResolvers<[string, string]>();
17
17
 
@@ -155,6 +155,3 @@ await suite('Watch', () => {
155
155
  await promise;
156
156
  });
157
157
  });
158
-
159
- await fs.promises.rm(testFile);
160
- await fs.promises.rm(testDir, { recursive: true, force: true });
@@ -3,7 +3,7 @@ import { copySync, data } from '../setup.js';
3
3
 
4
4
  await configureSingle({
5
5
  backend: SingleBuffer,
6
- buffer: new ArrayBuffer(0x100000),
6
+ buffer: new ArrayBuffer(0x1100000),
7
7
  });
8
8
 
9
9
  copySync(data);
@@ -6,7 +6,8 @@
6
6
  "noEmit": true,
7
7
  "esModuleInterop": true,
8
8
  "allowSyntheticDefaultImports": true,
9
- "allowJs": true
9
+ "allowJs": true,
10
+ "rootDir": ".."
10
11
  },
11
- "include": ["**/*.ts", "*.ts", "**/*.js"]
12
+ "include": ["**/*.ts", "*.ts", "**/*.js", "../types/uint8array.d.ts"]
12
13
  }