@zenfs/core 1.7.2 → 1.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 (62) hide show
  1. package/dist/backends/backend.js +3 -4
  2. package/dist/backends/fetch.d.ts +17 -18
  3. package/dist/backends/fetch.js +95 -58
  4. package/dist/backends/index.d.ts +2 -1
  5. package/dist/backends/index.js +2 -1
  6. package/dist/backends/memory.d.ts +1 -1
  7. package/dist/backends/overlay.d.ts +7 -2
  8. package/dist/backends/overlay.js +32 -9
  9. package/dist/backends/passthrough.d.ts +4 -0
  10. package/dist/backends/passthrough.js +128 -0
  11. package/dist/backends/port/fs.d.ts +9 -44
  12. package/dist/backends/port/fs.js +93 -116
  13. package/dist/backends/port/rpc.d.ts +8 -5
  14. package/dist/backends/port/rpc.js +9 -7
  15. package/dist/backends/store/file_index.d.ts +38 -0
  16. package/dist/backends/store/file_index.js +76 -0
  17. package/dist/backends/store/fs.d.ts +55 -34
  18. package/dist/backends/store/fs.js +417 -233
  19. package/dist/backends/store/index_fs.d.ts +34 -0
  20. package/dist/backends/store/index_fs.js +67 -0
  21. package/dist/backends/store/inode.d.ts +26 -8
  22. package/dist/backends/store/inode.js +92 -91
  23. package/dist/backends/store/simple.d.ts +20 -20
  24. package/dist/backends/store/simple.js +3 -4
  25. package/dist/backends/store/store.d.ts +12 -12
  26. package/dist/backends/store/store.js +4 -6
  27. package/dist/devices.d.ts +11 -10
  28. package/dist/devices.js +15 -11
  29. package/dist/file.d.ts +111 -7
  30. package/dist/file.js +319 -71
  31. package/dist/filesystem.d.ts +22 -4
  32. package/dist/mixins/mutexed.d.ts +7 -2
  33. package/dist/mixins/mutexed.js +56 -0
  34. package/dist/mixins/sync.d.ts +1 -1
  35. package/dist/stats.d.ts +12 -6
  36. package/dist/stats.js +14 -6
  37. package/dist/utils.d.ts +17 -3
  38. package/dist/utils.js +32 -10
  39. package/dist/vfs/constants.d.ts +2 -2
  40. package/dist/vfs/constants.js +2 -2
  41. package/dist/vfs/dir.js +3 -1
  42. package/dist/vfs/index.js +4 -1
  43. package/dist/vfs/promises.js +31 -11
  44. package/dist/vfs/shared.js +2 -0
  45. package/dist/vfs/sync.js +25 -13
  46. package/dist/vfs/types.d.ts +15 -0
  47. package/package.json +2 -3
  48. package/readme.md +2 -2
  49. package/scripts/test.js +73 -11
  50. package/tests/common/mutex.test.ts +1 -1
  51. package/tests/fetch/run.sh +16 -0
  52. package/tests/fetch/server.ts +49 -0
  53. package/tests/fetch/setup.ts +13 -0
  54. package/tests/fs/read.test.ts +10 -10
  55. package/tests/fs/times.test.ts +2 -2
  56. package/tests/setup/index.ts +38 -0
  57. package/tests/setup/port.ts +15 -0
  58. package/dist/backends/file_index.d.ts +0 -63
  59. package/dist/backends/file_index.js +0 -163
  60. package/tests/common/async.test.ts +0 -31
  61. package/tests/setup/cow+fetch.ts +0 -45
  62. /package/tests/fs/{appendFile.test.ts → append.test.ts} +0 -0
package/dist/file.js CHANGED
@@ -3,6 +3,7 @@ import './polyfills.js';
3
3
  import { _chown, Stats } from './stats.js';
4
4
  import { config } from './vfs/config.js';
5
5
  import * as c from './vfs/constants.js';
6
+ const maxByteLength = 0x100000; // 1 MiB
6
7
  const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+'];
7
8
  export function parseFlag(flag) {
8
9
  if (typeof flag === 'number') {
@@ -146,7 +147,7 @@ export class PreloadFile extends File {
146
147
  /**
147
148
  * A buffer containing the entire contents of the file.
148
149
  */
149
- _buffer = new Uint8Array(new ArrayBuffer(0, fs.metadata().noResizableBuffers ? {} : { maxByteLength: c.size_max }))) {
150
+ _buffer = new Uint8Array(new ArrayBuffer(0, fs.metadata().noResizableBuffers ? {} : { maxByteLength }))) {
150
151
  super(fs, path);
151
152
  this.flag = flag;
152
153
  this.stats = stats;
@@ -171,9 +172,10 @@ export class PreloadFile extends File {
171
172
  if (this.stats.size == _buffer.byteLength) {
172
173
  return;
173
174
  }
174
- if (isReadable(this.flag)) {
175
- throw new Error(`Size mismatch: buffer length ${_buffer.byteLength}, stats size ${this.stats.size}`);
175
+ if (!isWriteable(this.flag)) {
176
+ throw new ErrnoError(Errno.EIO, `Size mismatch: buffer length ${_buffer.byteLength}, stats size ${this.stats.size}`, path);
176
177
  }
178
+ this.stats.size = _buffer.byteLength;
177
179
  this.dirty = true;
178
180
  }
179
181
  /**
@@ -201,36 +203,32 @@ export class PreloadFile extends File {
201
203
  this._position = value;
202
204
  }
203
205
  async sync() {
204
- if (this.closed) {
205
- throw ErrnoError.With('EBADF', this.path, 'File.sync');
206
- }
207
- if (!this.dirty) {
206
+ if (this.closed)
207
+ throw ErrnoError.With('EBADF', this.path, 'sync');
208
+ if (!this.dirty)
208
209
  return;
209
- }
210
- await this.fs.sync(this.path, this._buffer, this.stats);
210
+ if (!this.fs.metadata().readonly)
211
+ await this.fs.sync(this.path, this._buffer, this.stats);
211
212
  this.dirty = false;
212
213
  }
213
214
  syncSync() {
214
- if (this.closed) {
215
- throw ErrnoError.With('EBADF', this.path, 'File.sync');
216
- }
217
- if (!this.dirty) {
215
+ if (this.closed)
216
+ throw ErrnoError.With('EBADF', this.path, 'sync');
217
+ if (!this.dirty)
218
218
  return;
219
- }
220
- this.fs.syncSync(this.path, this._buffer, this.stats);
219
+ if (!this.fs.metadata().readonly)
220
+ this.fs.syncSync(this.path, this._buffer, this.stats);
221
221
  this.dirty = false;
222
222
  }
223
223
  async close() {
224
- if (this.closed) {
225
- throw ErrnoError.With('EBADF', this.path, 'File.close');
226
- }
224
+ if (this.closed)
225
+ throw ErrnoError.With('EBADF', this.path, 'close');
227
226
  await this.sync();
228
227
  this.dispose();
229
228
  }
230
229
  closeSync() {
231
- if (this.closed) {
232
- throw ErrnoError.With('EBADF', this.path, 'File.close');
233
- }
230
+ if (this.closed)
231
+ throw ErrnoError.With('EBADF', this.path, 'close');
234
232
  this.syncSync();
235
233
  this.dispose();
236
234
  }
@@ -238,34 +236,26 @@ export class PreloadFile extends File {
238
236
  * Cleans up. This will *not* sync the file data to the FS
239
237
  */
240
238
  dispose(force) {
241
- if (this.closed) {
242
- throw ErrnoError.With('EBADF', this.path, 'File.dispose');
243
- }
239
+ if (this.closed)
240
+ throw ErrnoError.With('EBADF', this.path, 'dispose');
244
241
  if (this.dirty && !force) {
245
- throw ErrnoError.With('EBUSY', this.path, 'File.dispose');
242
+ throw ErrnoError.With('EBUSY', this.path, 'dispose');
246
243
  }
247
- // @ts-expect-error 2790
248
- delete this._buffer;
249
- // @ts-expect-error 2790
250
- delete this.stats;
251
244
  this.closed = true;
252
245
  }
253
246
  stat() {
254
- if (this.closed) {
255
- throw ErrnoError.With('EBADF', this.path, 'File.stat');
256
- }
247
+ if (this.closed)
248
+ throw ErrnoError.With('EBADF', this.path, 'stat');
257
249
  return Promise.resolve(new Stats(this.stats));
258
250
  }
259
251
  statSync() {
260
- if (this.closed) {
261
- throw ErrnoError.With('EBADF', this.path, 'File.stat');
262
- }
252
+ if (this.closed)
253
+ throw ErrnoError.With('EBADF', this.path, 'stat');
263
254
  return new Stats(this.stats);
264
255
  }
265
256
  _truncate(length) {
266
- if (this.closed) {
267
- throw ErrnoError.With('EBADF', this.path, 'File.truncate');
268
- }
257
+ if (this.closed)
258
+ throw ErrnoError.With('EBADF', this.path, 'truncate');
269
259
  this.dirty = true;
270
260
  if (!isWriteable(this.flag)) {
271
261
  throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
@@ -291,10 +281,9 @@ export class PreloadFile extends File {
291
281
  if (config.syncImmediately)
292
282
  this.syncSync();
293
283
  }
294
- _write(buffer, offset = 0, length = this.stats.size, position = this.position) {
295
- if (this.closed) {
296
- throw ErrnoError.With('EBADF', this.path, 'File.write');
297
- }
284
+ _write(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
285
+ if (this.closed)
286
+ throw ErrnoError.With('EBADF', this.path, 'write');
298
287
  if (!isWriteable(this.flag)) {
299
288
  throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
300
289
  }
@@ -316,7 +305,7 @@ export class PreloadFile extends File {
316
305
  }
317
306
  else {
318
307
  // Extend the buffer!
319
- const newBuffer = new Uint8Array(new ArrayBuffer(end, this.fs.metadata().noResizableBuffers ? {} : { maxByteLength: c.size_max }));
308
+ const newBuffer = new Uint8Array(new ArrayBuffer(end, this.fs.metadata().noResizableBuffers ? {} : { maxByteLength }));
320
309
  newBuffer.set(this._buffer);
321
310
  this._buffer = newBuffer;
322
311
  }
@@ -350,16 +339,15 @@ export class PreloadFile extends File {
350
339
  * If position is null, the data will be written at the current position.
351
340
  * @returns bytes written
352
341
  */
353
- writeSync(buffer, offset = 0, length = this.stats.size, position = this.position) {
342
+ writeSync(buffer, offset, length, position) {
354
343
  const bytesWritten = this._write(buffer, offset, length, position);
355
344
  if (config.syncImmediately)
356
345
  this.syncSync();
357
346
  return bytesWritten;
358
347
  }
359
- _read(buffer, offset = 0, length = this.stats.size, position) {
360
- if (this.closed) {
361
- throw ErrnoError.With('EBADF', this.path, 'File.read');
362
- }
348
+ _read(buffer, offset = 0, length = buffer.byteLength - offset, position) {
349
+ if (this.closed)
350
+ throw ErrnoError.With('EBADF', this.path, 'read');
363
351
  if (!isReadable(this.flag)) {
364
352
  throw new ErrnoError(Errno.EPERM, 'File not opened with a readable mode.');
365
353
  }
@@ -378,7 +366,8 @@ export class PreloadFile extends File {
378
366
  // No copy/read. Return immediately for better performance
379
367
  return bytesRead;
380
368
  }
381
- new Uint8Array(buffer.buffer, offset, length).set(this._buffer.slice(position, end));
369
+ const slice = this._buffer.slice(position, end);
370
+ new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength).set(slice, offset);
382
371
  return bytesRead;
383
372
  }
384
373
  /**
@@ -411,65 +400,61 @@ export class PreloadFile extends File {
411
400
  return bytesRead;
412
401
  }
413
402
  async chmod(mode) {
414
- if (this.closed) {
415
- throw ErrnoError.With('EBADF', this.path, 'File.chmod');
416
- }
403
+ if (this.closed)
404
+ throw ErrnoError.With('EBADF', this.path, 'chmod');
417
405
  this.dirty = true;
418
406
  this.stats.mode = (this.stats.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
419
407
  if (config.syncImmediately || mode > c.S_IFMT)
420
408
  await this.sync();
421
409
  }
422
410
  chmodSync(mode) {
423
- if (this.closed) {
424
- throw ErrnoError.With('EBADF', this.path, 'File.chmod');
425
- }
411
+ if (this.closed)
412
+ throw ErrnoError.With('EBADF', this.path, 'chmod');
426
413
  this.dirty = true;
427
414
  this.stats.mode = (this.stats.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
428
415
  if (config.syncImmediately || mode > c.S_IFMT)
429
416
  this.syncSync();
430
417
  }
431
418
  async chown(uid, gid) {
432
- if (this.closed) {
433
- throw ErrnoError.With('EBADF', this.path, 'File.chown');
434
- }
419
+ if (this.closed)
420
+ throw ErrnoError.With('EBADF', this.path, 'chown');
435
421
  this.dirty = true;
436
422
  _chown(this.stats, uid, gid);
437
423
  if (config.syncImmediately)
438
424
  await this.sync();
439
425
  }
440
426
  chownSync(uid, gid) {
441
- if (this.closed) {
442
- throw ErrnoError.With('EBADF', this.path, 'File.chown');
443
- }
427
+ if (this.closed)
428
+ throw ErrnoError.With('EBADF', this.path, 'chown');
444
429
  this.dirty = true;
445
430
  _chown(this.stats, uid, gid);
446
431
  if (config.syncImmediately)
447
432
  this.syncSync();
448
433
  }
449
434
  async utimes(atime, mtime) {
450
- if (this.closed) {
451
- throw ErrnoError.With('EBADF', this.path, 'File.utimes');
452
- }
435
+ if (this.closed)
436
+ throw ErrnoError.With('EBADF', this.path, 'utimes');
453
437
  this.dirty = true;
454
- this.stats.atime = atime;
455
- this.stats.mtime = mtime;
438
+ this.stats.atimeMs = atime;
439
+ this.stats.mtimeMs = mtime;
456
440
  if (config.syncImmediately)
457
441
  await this.sync();
458
442
  }
459
443
  utimesSync(atime, mtime) {
460
- if (this.closed) {
461
- throw ErrnoError.With('EBADF', this.path, 'File.utimes');
462
- }
444
+ if (this.closed)
445
+ throw ErrnoError.With('EBADF', this.path, 'utimes');
463
446
  this.dirty = true;
464
- this.stats.atime = atime;
465
- this.stats.mtime = mtime;
447
+ this.stats.atimeMs = atime;
448
+ this.stats.mtimeMs = mtime;
466
449
  if (config.syncImmediately)
467
450
  this.syncSync();
468
451
  }
469
452
  }
470
453
  /**
471
454
  * For the file systems which do not sync to anything.
455
+ * @deprecated
472
456
  */
457
+ /* node:coverage disable */
473
458
  export class NoSyncFile extends PreloadFile {
474
459
  sync() {
475
460
  return Promise.resolve();
@@ -480,3 +465,266 @@ export class NoSyncFile extends PreloadFile {
480
465
  }
481
466
  closeSync() { }
482
467
  }
468
+ /* node:coverage enable */
469
+ /**
470
+ * An implementation of `File` that uses the FS
471
+ */
472
+ export class LazyFile extends File {
473
+ /**
474
+ * Get the current file position.
475
+ *
476
+ * We emulate the following bug mentioned in the Node documentation:
477
+ *
478
+ * On Linux, positional writes don't work when the file is opened in append mode.
479
+ * The kernel ignores the position argument and always appends the data to the end of the file.
480
+ * @returns The current file position.
481
+ */
482
+ get position() {
483
+ return isAppendable(this.flag) ? this.stats.size : this._position;
484
+ }
485
+ set position(value) {
486
+ this._position = value;
487
+ }
488
+ /**
489
+ * Creates a file with `path` and, optionally, the given contents.
490
+ * Note that, if contents is specified, it will be mutated by the file.
491
+ */
492
+ constructor(fs, path, flag, stats) {
493
+ super(fs, path);
494
+ this.flag = flag;
495
+ this.stats = stats;
496
+ /**
497
+ * Current position
498
+ */
499
+ this._position = 0;
500
+ /**
501
+ * Whether the file has changes which have not been written to the FS
502
+ */
503
+ this.dirty = false;
504
+ /**
505
+ * Whether the file is open or closed
506
+ */
507
+ this.closed = false;
508
+ this.dirty = true;
509
+ }
510
+ async sync() {
511
+ if (this.closed)
512
+ throw ErrnoError.With('EBADF', this.path, 'sync');
513
+ if (!this.dirty)
514
+ return;
515
+ if (!this.fs.metadata().readonly)
516
+ await this.fs.sync(this.path, undefined, this.stats);
517
+ this.dirty = false;
518
+ }
519
+ syncSync() {
520
+ if (this.closed)
521
+ throw ErrnoError.With('EBADF', this.path, 'sync');
522
+ if (!this.dirty)
523
+ return;
524
+ if (!this.fs.metadata().readonly)
525
+ this.fs.syncSync(this.path, undefined, this.stats);
526
+ this.dirty = false;
527
+ }
528
+ async close() {
529
+ if (this.closed)
530
+ throw ErrnoError.With('EBADF', this.path, 'close');
531
+ await this.sync();
532
+ this.dispose();
533
+ }
534
+ closeSync() {
535
+ if (this.closed)
536
+ throw ErrnoError.With('EBADF', this.path, 'close');
537
+ this.syncSync();
538
+ this.dispose();
539
+ }
540
+ /**
541
+ * Cleans up. This will *not* sync the file data to the FS
542
+ */
543
+ dispose(force) {
544
+ if (this.closed)
545
+ throw ErrnoError.With('EBADF', this.path, 'dispose');
546
+ if (this.dirty && !force)
547
+ throw ErrnoError.With('EBUSY', this.path, 'dispose');
548
+ this.closed = true;
549
+ }
550
+ stat() {
551
+ if (this.closed)
552
+ throw ErrnoError.With('EBADF', this.path, 'stat');
553
+ return Promise.resolve(new Stats(this.stats));
554
+ }
555
+ statSync() {
556
+ if (this.closed)
557
+ throw ErrnoError.With('EBADF', this.path, 'stat');
558
+ return new Stats(this.stats);
559
+ }
560
+ async truncate(length) {
561
+ if (this.closed)
562
+ throw ErrnoError.With('EBADF', this.path, 'truncate');
563
+ this.dirty = true;
564
+ if (!isWriteable(this.flag)) {
565
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
566
+ }
567
+ this.stats.mtimeMs = Date.now();
568
+ this.stats.size = length;
569
+ if (config.syncImmediately)
570
+ await this.sync();
571
+ }
572
+ truncateSync(length) {
573
+ if (this.closed)
574
+ throw ErrnoError.With('EBADF', this.path, 'truncate');
575
+ this.dirty = true;
576
+ if (!isWriteable(this.flag)) {
577
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
578
+ }
579
+ this.stats.mtimeMs = Date.now();
580
+ this.stats.size = length;
581
+ if (config.syncImmediately)
582
+ this.syncSync();
583
+ }
584
+ prepareWrite(buffer, offset, length, position) {
585
+ if (this.closed)
586
+ throw ErrnoError.With('EBADF', this.path, 'write');
587
+ if (!isWriteable(this.flag)) {
588
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
589
+ }
590
+ this.dirty = true;
591
+ const end = position + length;
592
+ const slice = buffer.slice(offset, offset + length);
593
+ if (end > this.stats.size) {
594
+ this.stats.size = end;
595
+ }
596
+ this.stats.mtimeMs = Date.now();
597
+ this._position = position + slice.byteLength;
598
+ return slice;
599
+ }
600
+ /**
601
+ * Write buffer to the file.
602
+ * @param buffer Uint8Array containing the data to write to the file.
603
+ * @param offset Offset in the buffer to start reading data from.
604
+ * @param length The amount of bytes to write to the file.
605
+ * @param position Offset from the beginning of the file where this data should be written.
606
+ * If position is null, the data will be written at the current position.
607
+ */
608
+ async write(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
609
+ const slice = this.prepareWrite(buffer, offset, length, position);
610
+ await this.fs.write(this.path, slice, offset);
611
+ if (config.syncImmediately)
612
+ await this.sync();
613
+ return slice.byteLength;
614
+ }
615
+ /**
616
+ * Write buffer to the file.
617
+ * @param buffer Uint8Array containing the data to write to the file.
618
+ * @param offset Offset in the buffer to start reading data from.
619
+ * @param length The amount of bytes to write to the file.
620
+ * @param position Offset from the beginning of the file where this data should be written.
621
+ * If position is null, the data will be written at the current position.
622
+ * @returns bytes written
623
+ */
624
+ writeSync(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
625
+ const slice = this.prepareWrite(buffer, offset, length, position);
626
+ this.fs.writeSync(this.path, slice, offset);
627
+ if (config.syncImmediately)
628
+ this.syncSync();
629
+ return slice.byteLength;
630
+ }
631
+ /**
632
+ * Computes position information for reading
633
+ */
634
+ prepareRead(length, position) {
635
+ if (this.closed)
636
+ throw ErrnoError.With('EBADF', this.path, 'read');
637
+ if (!isReadable(this.flag))
638
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a readable mode.');
639
+ if (config.updateOnRead)
640
+ this.dirty = true;
641
+ this.stats.atimeMs = Date.now();
642
+ let end = position + length;
643
+ if (end > this.stats.size) {
644
+ end = position + Math.max(this.stats.size - position, 0);
645
+ }
646
+ this._position = end;
647
+ return end - position;
648
+ }
649
+ /**
650
+ * Read data from the file.
651
+ * @param buffer The buffer that the data will be written to.
652
+ * @param offset The offset within the buffer where writing will start.
653
+ * @param length An integer specifying the number of bytes to read.
654
+ * @param position An integer specifying where to begin reading from in the file.
655
+ * If position is unset, data will be read from the current file position.
656
+ */
657
+ async read(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
658
+ const bytesRead = this.prepareRead(length, position);
659
+ new Uint8Array(buffer.buffer, offset, length).set(await this.fs.read(this.path, position, bytesRead));
660
+ if (config.syncImmediately)
661
+ await this.sync();
662
+ return { bytesRead, buffer };
663
+ }
664
+ /**
665
+ * Read data from the file.
666
+ * @param buffer The buffer that the data will be written to.
667
+ * @param offset The offset within the buffer where writing will start.
668
+ * @param length An integer specifying the number of bytes to read.
669
+ * @param position An integer specifying where to begin reading from in the file.
670
+ * If position is null, data will be read from the current file position.
671
+ * @returns number of bytes written
672
+ */
673
+ readSync(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
674
+ const bytesRead = this.prepareRead(length, position);
675
+ new Uint8Array(buffer.buffer, offset, length).set(this.fs.readSync(this.path, position, bytesRead));
676
+ if (config.syncImmediately)
677
+ this.syncSync();
678
+ return bytesRead;
679
+ }
680
+ async chmod(mode) {
681
+ if (this.closed)
682
+ throw ErrnoError.With('EBADF', this.path, 'chmod');
683
+ this.dirty = true;
684
+ this.stats.mode = (this.stats.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
685
+ if (config.syncImmediately || mode > c.S_IFMT)
686
+ await this.sync();
687
+ }
688
+ chmodSync(mode) {
689
+ if (this.closed)
690
+ throw ErrnoError.With('EBADF', this.path, 'chmod');
691
+ this.dirty = true;
692
+ this.stats.mode = (this.stats.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
693
+ if (config.syncImmediately || mode > c.S_IFMT)
694
+ this.syncSync();
695
+ }
696
+ async chown(uid, gid) {
697
+ if (this.closed)
698
+ throw ErrnoError.With('EBADF', this.path, 'chown');
699
+ this.dirty = true;
700
+ _chown(this.stats, uid, gid);
701
+ if (config.syncImmediately)
702
+ await this.sync();
703
+ }
704
+ chownSync(uid, gid) {
705
+ if (this.closed)
706
+ throw ErrnoError.With('EBADF', this.path, 'chown');
707
+ this.dirty = true;
708
+ _chown(this.stats, uid, gid);
709
+ if (config.syncImmediately)
710
+ this.syncSync();
711
+ }
712
+ async utimes(atime, mtime) {
713
+ if (this.closed)
714
+ throw ErrnoError.With('EBADF', this.path, 'utimes');
715
+ this.dirty = true;
716
+ this.stats.atimeMs = atime;
717
+ this.stats.mtimeMs = mtime;
718
+ if (config.syncImmediately)
719
+ await this.sync();
720
+ }
721
+ utimesSync(atime, mtime) {
722
+ if (this.closed)
723
+ throw ErrnoError.With('EBADF', this.path, 'utimes');
724
+ this.dirty = true;
725
+ this.stats.atimeMs = atime;
726
+ this.stats.mtimeMs = mtime;
727
+ if (config.syncImmediately)
728
+ this.syncSync();
729
+ }
730
+ }
@@ -1,5 +1,5 @@
1
1
  import type { File } from './file.js';
2
- import type { Stats } from './stats.js';
2
+ import type { Stats, StatsLike } from './stats.js';
3
3
  export type FileContents = ArrayBufferView | string;
4
4
  /**
5
5
  * Metadata about a FileSystem
@@ -58,7 +58,8 @@ export interface FileSystemMetadata {
58
58
  features?: ('setid' | '')[];
59
59
  }
60
60
  /**
61
- * Options used when creating files and directories
61
+ * Options used when creating files and directories.
62
+ * This weird naming and such is to preserve backward compatibility.
62
63
  * @todo [BREAKING] Move the `mode` parameter of `createFile` and `mkdir` into this
63
64
  * @internal
64
65
  */
@@ -73,6 +74,19 @@ export interface CreationOptions {
73
74
  * This is ignored if the FS supports setgid and the setgid bit is set
74
75
  */
75
76
  gid: number;
77
+ /**
78
+ * The mode to create the file with.
79
+ */
80
+ mode?: number;
81
+ }
82
+ /**
83
+ * This is the correct type that will be used when the API is updated in a breaking release
84
+ */
85
+ export interface PureCreationOptions extends CreationOptions {
86
+ /**
87
+ * The mode to create the file with.
88
+ */
89
+ mode: number;
76
90
  }
77
91
  /**
78
92
  * Provides a consistent and easy to use internal API.
@@ -135,6 +149,10 @@ export declare abstract class FileSystem {
135
149
  existsSync(path: string): boolean;
136
150
  abstract link(target: string, link: string): Promise<void>;
137
151
  abstract linkSync(target: string, link: string): void;
138
- abstract sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
139
- abstract syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
152
+ abstract sync(path: string, data?: Uint8Array, stats?: Partial<Readonly<StatsLike>>): Promise<void>;
153
+ abstract syncSync(path: string, data?: Uint8Array, stats?: Partial<Readonly<StatsLike>>): void;
154
+ abstract read(path: string, offset: number, length: number): Promise<Uint8Array>;
155
+ abstract readSync(path: string, offset: number, length: number): Uint8Array;
156
+ abstract write(path: string, buffer: Uint8Array, offset: number): Promise<void>;
157
+ abstract writeSync(path: string, buffer: Uint8Array, offset: number): void;
140
158
  }
@@ -3,6 +3,7 @@ import type { CreationOptions, FileSystem, FileSystemMetadata } from '../filesys
3
3
  import type { Stats } from '../stats.js';
4
4
  import type { Concrete } from '../utils.js';
5
5
  import '../polyfills.js';
6
+ import type { InodeLike } from '../backends/index.js';
6
7
  export declare class MutexLock {
7
8
  protected readonly previous?: MutexLock | undefined;
8
9
  protected current: PromiseWithResolvers<void>;
@@ -68,8 +69,12 @@ export declare class _MutexedFS<T extends FileSystem> implements FileSystem {
68
69
  existsSync(path: string): boolean;
69
70
  link(srcpath: string, dstpath: string): Promise<void>;
70
71
  linkSync(srcpath: string, dstpath: string): void;
71
- sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
72
- syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
72
+ sync(path: string, data?: Uint8Array, stats?: Readonly<InodeLike>): Promise<void>;
73
+ syncSync(path: string, data?: Uint8Array, stats?: Readonly<InodeLike>): void;
74
+ read(path: string, offset: number, length: number): Promise<Uint8Array>;
75
+ readSync(path: string, offset: number, length: number): Uint8Array;
76
+ write(path: string, buffer: Uint8Array, offset: number): Promise<void>;
77
+ writeSync(path: string, buffer: Uint8Array, offset: number): void;
73
78
  }
74
79
  /**
75
80
  * This serializes access to an underlying async filesystem.
@@ -448,6 +448,62 @@ export class _MutexedFS {
448
448
  __disposeResources(env_22);
449
449
  }
450
450
  }
451
+ async read(path, offset, length) {
452
+ const env_23 = { stack: [], error: void 0, hasError: false };
453
+ try {
454
+ const _ = __addDisposableResource(env_23, await this.lock(path, 'read'), false);
455
+ return await this._fs.read(path, offset, length);
456
+ }
457
+ catch (e_23) {
458
+ env_23.error = e_23;
459
+ env_23.hasError = true;
460
+ }
461
+ finally {
462
+ __disposeResources(env_23);
463
+ }
464
+ }
465
+ readSync(path, offset, length) {
466
+ const env_24 = { stack: [], error: void 0, hasError: false };
467
+ try {
468
+ const _ = __addDisposableResource(env_24, this.lockSync(path, 'read'), false);
469
+ return this._fs.readSync(path, offset, length);
470
+ }
471
+ catch (e_24) {
472
+ env_24.error = e_24;
473
+ env_24.hasError = true;
474
+ }
475
+ finally {
476
+ __disposeResources(env_24);
477
+ }
478
+ }
479
+ async write(path, buffer, offset) {
480
+ const env_25 = { stack: [], error: void 0, hasError: false };
481
+ try {
482
+ const _ = __addDisposableResource(env_25, await this.lock(path, 'write'), false);
483
+ return await this._fs.write(path, buffer, offset);
484
+ }
485
+ catch (e_25) {
486
+ env_25.error = e_25;
487
+ env_25.hasError = true;
488
+ }
489
+ finally {
490
+ __disposeResources(env_25);
491
+ }
492
+ }
493
+ writeSync(path, buffer, offset) {
494
+ const env_26 = { stack: [], error: void 0, hasError: false };
495
+ try {
496
+ const _ = __addDisposableResource(env_26, this.lockSync(path, 'write'), false);
497
+ return this._fs.writeSync(path, buffer, offset);
498
+ }
499
+ catch (e_26) {
500
+ env_26.error = e_26;
501
+ env_26.hasError = true;
502
+ }
503
+ finally {
504
+ __disposeResources(env_26);
505
+ }
506
+ }
451
507
  }
452
508
  /**
453
509
  * This serializes access to an underlying async filesystem.
@@ -1,5 +1,5 @@
1
1
  import type { FileSystem } from '../filesystem.js';
2
- import type { Mixin, AsyncFSMethods } from './shared.js';
2
+ import type { AsyncFSMethods, Mixin } from './shared.js';
3
3
  /**
4
4
  * Implements the asynchronous API in terms of the synchronous API.
5
5
  */