@zenfs/core 1.7.2 → 1.8.1

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 (68) 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 +8 -14
  8. package/dist/backends/overlay.js +38 -31
  9. package/dist/backends/passthrough.d.ts +8 -3
  10. package/dist/backends/passthrough.js +148 -4
  11. package/dist/backends/port/fs.d.ts +15 -49
  12. package/dist/backends/port/fs.js +28 -116
  13. package/dist/backends/port/rpc.d.ts +13 -6
  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 +39 -34
  18. package/dist/backends/store/fs.js +407 -238
  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 +44 -21
  28. package/dist/devices.js +110 -55
  29. package/dist/file.d.ts +111 -7
  30. package/dist/file.js +324 -92
  31. package/dist/filesystem.d.ts +44 -4
  32. package/dist/mixins/async.js +12 -6
  33. package/dist/mixins/mutexed.d.ts +8 -3
  34. package/dist/mixins/mutexed.js +57 -1
  35. package/dist/mixins/readonly.d.ts +17 -16
  36. package/dist/mixins/readonly.js +6 -0
  37. package/dist/mixins/sync.d.ts +1 -1
  38. package/dist/stats.d.ts +12 -6
  39. package/dist/stats.js +14 -6
  40. package/dist/utils.d.ts +23 -3
  41. package/dist/utils.js +58 -10
  42. package/dist/vfs/async.js +1 -1
  43. package/dist/vfs/constants.d.ts +2 -2
  44. package/dist/vfs/constants.js +2 -2
  45. package/dist/vfs/dir.js +3 -1
  46. package/dist/vfs/index.js +4 -1
  47. package/dist/vfs/promises.js +33 -13
  48. package/dist/vfs/shared.js +2 -0
  49. package/dist/vfs/sync.js +25 -13
  50. package/dist/vfs/types.d.ts +15 -0
  51. package/eslint.shared.js +1 -0
  52. package/package.json +2 -3
  53. package/readme.md +2 -2
  54. package/scripts/test.js +73 -11
  55. package/tests/common/mutex.test.ts +1 -1
  56. package/tests/fetch/run.sh +16 -0
  57. package/tests/fetch/server.ts +49 -0
  58. package/tests/fetch/setup.ts +13 -0
  59. package/tests/fs/read.test.ts +10 -10
  60. package/tests/fs/times.test.ts +2 -2
  61. package/tests/fs/write.test.ts +6 -11
  62. package/tests/setup/index.ts +38 -0
  63. package/tests/setup/port.ts +15 -0
  64. package/dist/backends/file_index.d.ts +0 -63
  65. package/dist/backends/file_index.js +0 -163
  66. package/tests/common/async.test.ts +0 -31
  67. package/tests/setup/cow+fetch.ts +0 -45
  68. /package/tests/fs/{appendFile.test.ts → append.test.ts} +0 -0
package/dist/file.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { Errno, ErrnoError } from './error.js';
2
2
  import './polyfills.js';
3
3
  import { _chown, Stats } from './stats.js';
4
+ import { growBuffer } from './utils.js';
4
5
  import { config } from './vfs/config.js';
5
6
  import * as c from './vfs/constants.js';
7
+ const maxByteLength = 0x100000; // 1 MiB
6
8
  const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+'];
7
9
  export function parseFlag(flag) {
8
10
  if (typeof flag === 'number') {
@@ -146,7 +148,7 @@ export class PreloadFile extends File {
146
148
  /**
147
149
  * A buffer containing the entire contents of the file.
148
150
  */
149
- _buffer = new Uint8Array(new ArrayBuffer(0, fs.metadata().noResizableBuffers ? {} : { maxByteLength: c.size_max }))) {
151
+ _buffer = new Uint8Array(new ArrayBuffer(0, fs.metadata().noResizableBuffers ? {} : { maxByteLength }))) {
150
152
  super(fs, path);
151
153
  this.flag = flag;
152
154
  this.stats = stats;
@@ -171,9 +173,10 @@ export class PreloadFile extends File {
171
173
  if (this.stats.size == _buffer.byteLength) {
172
174
  return;
173
175
  }
174
- if (isReadable(this.flag)) {
175
- throw new Error(`Size mismatch: buffer length ${_buffer.byteLength}, stats size ${this.stats.size}`);
176
+ if (!isWriteable(this.flag)) {
177
+ throw new ErrnoError(Errno.EIO, `Size mismatch: buffer length ${_buffer.byteLength}, stats size ${this.stats.size}`, path);
176
178
  }
179
+ this.stats.size = _buffer.byteLength;
177
180
  this.dirty = true;
178
181
  }
179
182
  /**
@@ -201,36 +204,32 @@ export class PreloadFile extends File {
201
204
  this._position = value;
202
205
  }
203
206
  async sync() {
204
- if (this.closed) {
205
- throw ErrnoError.With('EBADF', this.path, 'File.sync');
206
- }
207
- if (!this.dirty) {
207
+ if (this.closed)
208
+ throw ErrnoError.With('EBADF', this.path, 'sync');
209
+ if (!this.dirty)
208
210
  return;
209
- }
210
- await this.fs.sync(this.path, this._buffer, this.stats);
211
+ if (!this.fs.metadata().readonly)
212
+ await this.fs.sync(this.path, this._buffer, this.stats);
211
213
  this.dirty = false;
212
214
  }
213
215
  syncSync() {
214
- if (this.closed) {
215
- throw ErrnoError.With('EBADF', this.path, 'File.sync');
216
- }
217
- if (!this.dirty) {
216
+ if (this.closed)
217
+ throw ErrnoError.With('EBADF', this.path, 'sync');
218
+ if (!this.dirty)
218
219
  return;
219
- }
220
- this.fs.syncSync(this.path, this._buffer, this.stats);
220
+ if (!this.fs.metadata().readonly)
221
+ this.fs.syncSync(this.path, this._buffer, this.stats);
221
222
  this.dirty = false;
222
223
  }
223
224
  async close() {
224
- if (this.closed) {
225
- throw ErrnoError.With('EBADF', this.path, 'File.close');
226
- }
225
+ if (this.closed)
226
+ throw ErrnoError.With('EBADF', this.path, 'close');
227
227
  await this.sync();
228
228
  this.dispose();
229
229
  }
230
230
  closeSync() {
231
- if (this.closed) {
232
- throw ErrnoError.With('EBADF', this.path, 'File.close');
233
- }
231
+ if (this.closed)
232
+ throw ErrnoError.With('EBADF', this.path, 'close');
234
233
  this.syncSync();
235
234
  this.dispose();
236
235
  }
@@ -238,34 +237,26 @@ export class PreloadFile extends File {
238
237
  * Cleans up. This will *not* sync the file data to the FS
239
238
  */
240
239
  dispose(force) {
241
- if (this.closed) {
242
- throw ErrnoError.With('EBADF', this.path, 'File.dispose');
243
- }
240
+ if (this.closed)
241
+ throw ErrnoError.With('EBADF', this.path, 'dispose');
244
242
  if (this.dirty && !force) {
245
- throw ErrnoError.With('EBUSY', this.path, 'File.dispose');
243
+ throw ErrnoError.With('EBUSY', this.path, 'dispose');
246
244
  }
247
- // @ts-expect-error 2790
248
- delete this._buffer;
249
- // @ts-expect-error 2790
250
- delete this.stats;
251
245
  this.closed = true;
252
246
  }
253
247
  stat() {
254
- if (this.closed) {
255
- throw ErrnoError.With('EBADF', this.path, 'File.stat');
256
- }
248
+ if (this.closed)
249
+ throw ErrnoError.With('EBADF', this.path, 'stat');
257
250
  return Promise.resolve(new Stats(this.stats));
258
251
  }
259
252
  statSync() {
260
- if (this.closed) {
261
- throw ErrnoError.With('EBADF', this.path, 'File.stat');
262
- }
253
+ if (this.closed)
254
+ throw ErrnoError.With('EBADF', this.path, 'stat');
263
255
  return new Stats(this.stats);
264
256
  }
265
257
  _truncate(length) {
266
- if (this.closed) {
267
- throw ErrnoError.With('EBADF', this.path, 'File.truncate');
268
- }
258
+ if (this.closed)
259
+ throw ErrnoError.With('EBADF', this.path, 'truncate');
269
260
  this.dirty = true;
270
261
  if (!isWriteable(this.flag)) {
271
262
  throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
@@ -279,7 +270,7 @@ export class PreloadFile extends File {
279
270
  }
280
271
  this.stats.size = length;
281
272
  // Truncate.
282
- this._buffer = length ? this._buffer.slice(0, length) : new Uint8Array();
273
+ this._buffer = length ? this._buffer.subarray(0, length) : new Uint8Array();
283
274
  }
284
275
  async truncate(length) {
285
276
  this._truncate(length);
@@ -291,37 +282,18 @@ export class PreloadFile extends File {
291
282
  if (config.syncImmediately)
292
283
  this.syncSync();
293
284
  }
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
- }
285
+ _write(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
286
+ if (this.closed)
287
+ throw ErrnoError.With('EBADF', this.path, 'write');
298
288
  if (!isWriteable(this.flag)) {
299
289
  throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
300
290
  }
301
291
  this.dirty = true;
302
292
  const end = position + length;
303
- const slice = buffer.slice(offset, offset + length);
304
- if (end > this.stats.size) {
293
+ const slice = buffer.subarray(offset, offset + length);
294
+ this._buffer = growBuffer(this._buffer, end);
295
+ if (end > this.stats.size)
305
296
  this.stats.size = end;
306
- if (end > this._buffer.byteLength) {
307
- const { buffer } = this._buffer;
308
- if ('resizable' in buffer && buffer.resizable && buffer.maxByteLength <= end) {
309
- buffer.resize(end);
310
- }
311
- else if ('growable' in buffer && buffer.growable && buffer.maxByteLength <= end) {
312
- buffer.grow(end);
313
- }
314
- else if (config.unsafeBufferReplace) {
315
- this._buffer = slice;
316
- }
317
- else {
318
- // Extend the buffer!
319
- const newBuffer = new Uint8Array(new ArrayBuffer(end, this.fs.metadata().noResizableBuffers ? {} : { maxByteLength: c.size_max }));
320
- newBuffer.set(this._buffer);
321
- this._buffer = newBuffer;
322
- }
323
- }
324
- }
325
297
  this._buffer.set(slice, position);
326
298
  this.stats.mtimeMs = Date.now();
327
299
  this.position = position + slice.byteLength;
@@ -350,16 +322,15 @@ export class PreloadFile extends File {
350
322
  * If position is null, the data will be written at the current position.
351
323
  * @returns bytes written
352
324
  */
353
- writeSync(buffer, offset = 0, length = this.stats.size, position = this.position) {
325
+ writeSync(buffer, offset, length, position) {
354
326
  const bytesWritten = this._write(buffer, offset, length, position);
355
327
  if (config.syncImmediately)
356
328
  this.syncSync();
357
329
  return bytesWritten;
358
330
  }
359
- _read(buffer, offset = 0, length = this.stats.size, position) {
360
- if (this.closed) {
361
- throw ErrnoError.With('EBADF', this.path, 'File.read');
362
- }
331
+ _read(buffer, offset = 0, length = buffer.byteLength - offset, position) {
332
+ if (this.closed)
333
+ throw ErrnoError.With('EBADF', this.path, 'read');
363
334
  if (!isReadable(this.flag)) {
364
335
  throw new ErrnoError(Errno.EPERM, 'File not opened with a readable mode.');
365
336
  }
@@ -378,7 +349,8 @@ export class PreloadFile extends File {
378
349
  // No copy/read. Return immediately for better performance
379
350
  return bytesRead;
380
351
  }
381
- new Uint8Array(buffer.buffer, offset, length).set(this._buffer.slice(position, end));
352
+ const slice = this._buffer.subarray(position, end);
353
+ new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength).set(slice, offset);
382
354
  return bytesRead;
383
355
  }
384
356
  /**
@@ -411,65 +383,61 @@ export class PreloadFile extends File {
411
383
  return bytesRead;
412
384
  }
413
385
  async chmod(mode) {
414
- if (this.closed) {
415
- throw ErrnoError.With('EBADF', this.path, 'File.chmod');
416
- }
386
+ if (this.closed)
387
+ throw ErrnoError.With('EBADF', this.path, 'chmod');
417
388
  this.dirty = true;
418
389
  this.stats.mode = (this.stats.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
419
390
  if (config.syncImmediately || mode > c.S_IFMT)
420
391
  await this.sync();
421
392
  }
422
393
  chmodSync(mode) {
423
- if (this.closed) {
424
- throw ErrnoError.With('EBADF', this.path, 'File.chmod');
425
- }
394
+ if (this.closed)
395
+ throw ErrnoError.With('EBADF', this.path, 'chmod');
426
396
  this.dirty = true;
427
397
  this.stats.mode = (this.stats.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
428
398
  if (config.syncImmediately || mode > c.S_IFMT)
429
399
  this.syncSync();
430
400
  }
431
401
  async chown(uid, gid) {
432
- if (this.closed) {
433
- throw ErrnoError.With('EBADF', this.path, 'File.chown');
434
- }
402
+ if (this.closed)
403
+ throw ErrnoError.With('EBADF', this.path, 'chown');
435
404
  this.dirty = true;
436
405
  _chown(this.stats, uid, gid);
437
406
  if (config.syncImmediately)
438
407
  await this.sync();
439
408
  }
440
409
  chownSync(uid, gid) {
441
- if (this.closed) {
442
- throw ErrnoError.With('EBADF', this.path, 'File.chown');
443
- }
410
+ if (this.closed)
411
+ throw ErrnoError.With('EBADF', this.path, 'chown');
444
412
  this.dirty = true;
445
413
  _chown(this.stats, uid, gid);
446
414
  if (config.syncImmediately)
447
415
  this.syncSync();
448
416
  }
449
417
  async utimes(atime, mtime) {
450
- if (this.closed) {
451
- throw ErrnoError.With('EBADF', this.path, 'File.utimes');
452
- }
418
+ if (this.closed)
419
+ throw ErrnoError.With('EBADF', this.path, 'utimes');
453
420
  this.dirty = true;
454
- this.stats.atime = atime;
455
- this.stats.mtime = mtime;
421
+ this.stats.atimeMs = atime;
422
+ this.stats.mtimeMs = mtime;
456
423
  if (config.syncImmediately)
457
424
  await this.sync();
458
425
  }
459
426
  utimesSync(atime, mtime) {
460
- if (this.closed) {
461
- throw ErrnoError.With('EBADF', this.path, 'File.utimes');
462
- }
427
+ if (this.closed)
428
+ throw ErrnoError.With('EBADF', this.path, 'utimes');
463
429
  this.dirty = true;
464
- this.stats.atime = atime;
465
- this.stats.mtime = mtime;
430
+ this.stats.atimeMs = atime;
431
+ this.stats.mtimeMs = mtime;
466
432
  if (config.syncImmediately)
467
433
  this.syncSync();
468
434
  }
469
435
  }
470
436
  /**
471
437
  * For the file systems which do not sync to anything.
438
+ * @deprecated
472
439
  */
440
+ /* node:coverage disable */
473
441
  export class NoSyncFile extends PreloadFile {
474
442
  sync() {
475
443
  return Promise.resolve();
@@ -480,3 +448,267 @@ export class NoSyncFile extends PreloadFile {
480
448
  }
481
449
  closeSync() { }
482
450
  }
451
+ /* node:coverage enable */
452
+ /**
453
+ * An implementation of `File` that uses the FS
454
+ */
455
+ export class LazyFile extends File {
456
+ /**
457
+ * Get the current file position.
458
+ *
459
+ * We emulate the following bug mentioned in the Node documentation:
460
+ *
461
+ * On Linux, positional writes don't work when the file is opened in append mode.
462
+ * The kernel ignores the position argument and always appends the data to the end of the file.
463
+ * @returns The current file position.
464
+ */
465
+ get position() {
466
+ return isAppendable(this.flag) ? this.stats.size : this._position;
467
+ }
468
+ set position(value) {
469
+ this._position = value;
470
+ }
471
+ /**
472
+ * Creates a file with `path` and, optionally, the given contents.
473
+ * Note that, if contents is specified, it will be mutated by the file.
474
+ */
475
+ constructor(fs, path, flag, stats) {
476
+ super(fs, path);
477
+ this.flag = flag;
478
+ this.stats = stats;
479
+ /**
480
+ * Current position
481
+ */
482
+ this._position = 0;
483
+ /**
484
+ * Whether the file has changes which have not been written to the FS
485
+ */
486
+ this.dirty = false;
487
+ /**
488
+ * Whether the file is open or closed
489
+ */
490
+ this.closed = false;
491
+ this.dirty = true;
492
+ }
493
+ async sync() {
494
+ if (this.closed)
495
+ throw ErrnoError.With('EBADF', this.path, 'sync');
496
+ if (!this.dirty)
497
+ return;
498
+ if (!this.fs.metadata().readonly)
499
+ await this.fs.sync(this.path, undefined, this.stats);
500
+ this.dirty = false;
501
+ }
502
+ syncSync() {
503
+ if (this.closed)
504
+ throw ErrnoError.With('EBADF', this.path, 'sync');
505
+ if (!this.dirty)
506
+ return;
507
+ if (!this.fs.metadata().readonly)
508
+ this.fs.syncSync(this.path, undefined, this.stats);
509
+ this.dirty = false;
510
+ }
511
+ async close() {
512
+ if (this.closed)
513
+ throw ErrnoError.With('EBADF', this.path, 'close');
514
+ await this.sync();
515
+ this.dispose();
516
+ }
517
+ closeSync() {
518
+ if (this.closed)
519
+ throw ErrnoError.With('EBADF', this.path, 'close');
520
+ this.syncSync();
521
+ this.dispose();
522
+ }
523
+ /**
524
+ * Cleans up. This will *not* sync the file data to the FS
525
+ */
526
+ dispose(force) {
527
+ if (this.closed)
528
+ throw ErrnoError.With('EBADF', this.path, 'dispose');
529
+ if (this.dirty && !force)
530
+ throw ErrnoError.With('EBUSY', this.path, 'dispose');
531
+ this.closed = true;
532
+ }
533
+ stat() {
534
+ if (this.closed)
535
+ throw ErrnoError.With('EBADF', this.path, 'stat');
536
+ return Promise.resolve(new Stats(this.stats));
537
+ }
538
+ statSync() {
539
+ if (this.closed)
540
+ throw ErrnoError.With('EBADF', this.path, 'stat');
541
+ return new Stats(this.stats);
542
+ }
543
+ async truncate(length) {
544
+ if (this.closed)
545
+ throw ErrnoError.With('EBADF', this.path, 'truncate');
546
+ this.dirty = true;
547
+ if (!isWriteable(this.flag)) {
548
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
549
+ }
550
+ this.stats.mtimeMs = Date.now();
551
+ this.stats.size = length;
552
+ if (config.syncImmediately)
553
+ await this.sync();
554
+ }
555
+ truncateSync(length) {
556
+ if (this.closed)
557
+ throw ErrnoError.With('EBADF', this.path, 'truncate');
558
+ this.dirty = true;
559
+ if (!isWriteable(this.flag)) {
560
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
561
+ }
562
+ this.stats.mtimeMs = Date.now();
563
+ this.stats.size = length;
564
+ if (config.syncImmediately)
565
+ this.syncSync();
566
+ }
567
+ prepareWrite(buffer, offset, length, position) {
568
+ if (this.closed)
569
+ throw ErrnoError.With('EBADF', this.path, 'write');
570
+ if (!isWriteable(this.flag)) {
571
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a writeable mode.');
572
+ }
573
+ this.dirty = true;
574
+ const end = position + length;
575
+ const slice = buffer.subarray(offset, offset + length);
576
+ if (end > this.stats.size)
577
+ this.stats.size = end;
578
+ this.stats.mtimeMs = Date.now();
579
+ this._position = position + slice.byteLength;
580
+ return slice;
581
+ }
582
+ /**
583
+ * Write buffer to the file.
584
+ * @param buffer Uint8Array containing the data to write to the file.
585
+ * @param offset Offset in the buffer to start reading data from.
586
+ * @param length The amount of bytes to write to the file.
587
+ * @param position Offset from the beginning of the file where this data should be written.
588
+ * If position is null, the data will be written at the current position.
589
+ */
590
+ async write(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
591
+ const slice = this.prepareWrite(buffer, offset, length, position);
592
+ await this.fs.write(this.path, slice, position);
593
+ if (config.syncImmediately)
594
+ await this.sync();
595
+ return slice.byteLength;
596
+ }
597
+ /**
598
+ * Write buffer to the file.
599
+ * @param buffer Uint8Array containing the data to write to the file.
600
+ * @param offset Offset in the buffer to start reading data from.
601
+ * @param length The amount of bytes to write to the file.
602
+ * @param position Offset from the beginning of the file where this data should be written.
603
+ * If position is null, the data will be written at the current position.
604
+ * @returns bytes written
605
+ */
606
+ writeSync(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
607
+ const slice = this.prepareWrite(buffer, offset, length, position);
608
+ this.fs.writeSync(this.path, slice, position);
609
+ if (config.syncImmediately)
610
+ this.syncSync();
611
+ return slice.byteLength;
612
+ }
613
+ /**
614
+ * Computes position information for reading
615
+ */
616
+ prepareRead(length, position) {
617
+ if (this.closed)
618
+ throw ErrnoError.With('EBADF', this.path, 'read');
619
+ if (!isReadable(this.flag))
620
+ throw new ErrnoError(Errno.EPERM, 'File not opened with a readable mode.');
621
+ if (config.updateOnRead)
622
+ this.dirty = true;
623
+ this.stats.atimeMs = Date.now();
624
+ let end = position + length;
625
+ if (end > this.stats.size) {
626
+ end = position + Math.max(this.stats.size - position, 0);
627
+ }
628
+ this._position = end;
629
+ return end - position;
630
+ }
631
+ /**
632
+ * Read data from the file.
633
+ * @param buffer The buffer that the data will be written to.
634
+ * @param offset The offset within the buffer where writing will start.
635
+ * @param length An integer specifying the number of bytes to read.
636
+ * @param position An integer specifying where to begin reading from in the file.
637
+ * If position is unset, data will be read from the current file position.
638
+ */
639
+ async read(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
640
+ const bytesRead = this.prepareRead(length, position);
641
+ const uint8 = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
642
+ await this.fs.read(this.path, uint8.subarray(offset, offset + length), position, bytesRead);
643
+ if (config.syncImmediately)
644
+ await this.sync();
645
+ return { bytesRead, buffer };
646
+ }
647
+ /**
648
+ * Read data from the file.
649
+ * @param buffer The buffer that the data will be written to.
650
+ * @param offset The offset within the buffer where writing will start.
651
+ * @param length An integer specifying the number of bytes to read.
652
+ * @param position An integer specifying where to begin reading from in the file.
653
+ * If position is null, data will be read from the current file position.
654
+ * @returns number of bytes written
655
+ */
656
+ readSync(buffer, offset = 0, length = buffer.byteLength - offset, position = this.position) {
657
+ const bytesRead = this.prepareRead(length, position);
658
+ const uint8 = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
659
+ this.fs.readSync(this.path, uint8.subarray(offset, offset + length), position, bytesRead);
660
+ if (config.syncImmediately)
661
+ this.syncSync();
662
+ return bytesRead;
663
+ }
664
+ async chmod(mode) {
665
+ if (this.closed)
666
+ throw ErrnoError.With('EBADF', this.path, 'chmod');
667
+ this.dirty = true;
668
+ this.stats.mode = (this.stats.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
669
+ if (config.syncImmediately || mode > c.S_IFMT)
670
+ await this.sync();
671
+ }
672
+ chmodSync(mode) {
673
+ if (this.closed)
674
+ throw ErrnoError.With('EBADF', this.path, 'chmod');
675
+ this.dirty = true;
676
+ this.stats.mode = (this.stats.mode & (mode > c.S_IFMT ? ~c.S_IFMT : c.S_IFMT)) | mode;
677
+ if (config.syncImmediately || mode > c.S_IFMT)
678
+ this.syncSync();
679
+ }
680
+ async chown(uid, gid) {
681
+ if (this.closed)
682
+ throw ErrnoError.With('EBADF', this.path, 'chown');
683
+ this.dirty = true;
684
+ _chown(this.stats, uid, gid);
685
+ if (config.syncImmediately)
686
+ await this.sync();
687
+ }
688
+ chownSync(uid, gid) {
689
+ if (this.closed)
690
+ throw ErrnoError.With('EBADF', this.path, 'chown');
691
+ this.dirty = true;
692
+ _chown(this.stats, uid, gid);
693
+ if (config.syncImmediately)
694
+ this.syncSync();
695
+ }
696
+ async utimes(atime, mtime) {
697
+ if (this.closed)
698
+ throw ErrnoError.With('EBADF', this.path, 'utimes');
699
+ this.dirty = true;
700
+ this.stats.atimeMs = atime;
701
+ this.stats.mtimeMs = mtime;
702
+ if (config.syncImmediately)
703
+ await this.sync();
704
+ }
705
+ utimesSync(atime, mtime) {
706
+ if (this.closed)
707
+ throw ErrnoError.With('EBADF', this.path, 'utimes');
708
+ this.dirty = true;
709
+ this.stats.atimeMs = atime;
710
+ this.stats.mtimeMs = mtime;
711
+ if (config.syncImmediately)
712
+ this.syncSync();
713
+ }
714
+ }
@@ -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,32 @@ 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
+ /**
155
+ * Reads into a buffer
156
+ * @param buffer The buffer to read into. You must set the `byteOffset` and `byteLength` appropriately!
157
+ * @param offset The offset into the file to start reading from
158
+ * @param end The position in the file to stop reading
159
+ */
160
+ abstract read(path: string, buffer: Uint8Array, offset: number, end: number): Promise<void>;
161
+ /**
162
+ * Reads into a buffer
163
+ * @param buffer The buffer to read into. You must set the `byteOffset` and `byteLength` appropriately!
164
+ * @param offset The offset into the file to start reading from
165
+ * @param end The position in the file to stop reading
166
+ */
167
+ abstract readSync(path: string, buffer: Uint8Array, offset: number, end: number): void;
168
+ /**
169
+ * Writes a buffer to a file
170
+ * @param buffer The buffer to write. You must set the `byteOffset` and `byteLength` appropriately!
171
+ * @param offset The offset in the file to start writing
172
+ */
173
+ abstract write(path: string, buffer: Uint8Array, offset: number): Promise<void>;
174
+ /**
175
+ * Writes a buffer to a file
176
+ * @param buffer The buffer to write. You must set the `byteOffset` and `byteLength` appropriately!
177
+ * @param offset The offset in the file to start writing
178
+ */
179
+ abstract writeSync(path: string, buffer: Uint8Array, offset: number): void;
140
180
  }
@@ -52,7 +52,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
52
52
  });
53
53
  import { StoreFS } from '../backends/store/fs.js';
54
54
  import { Errno, ErrnoError } from '../error.js';
55
- import { parseFlag, PreloadFile } from '../file.js';
55
+ import { LazyFile, parseFlag } from '../file.js';
56
56
  import { join } from '../vfs/path.js';
57
57
  /**
58
58
  * Async() implements synchronous methods on an asynchronous file system
@@ -138,11 +138,8 @@ export function Async(FS) {
138
138
  }
139
139
  openFileSync(path, flag) {
140
140
  this.checkSync(path, 'openFile');
141
- const file = this._sync.openFileSync(path, flag + '+');
142
- const stats = file.statSync();
143
- const buffer = new Uint8Array(stats.size);
144
- file.readSync(buffer);
145
- return new PreloadFile(this, path, flag, stats, buffer);
141
+ const stats = this._sync.statSync(path);
142
+ return new LazyFile(this, path, flag, stats);
146
143
  }
147
144
  unlinkSync(path) {
148
145
  this.checkSync(path, 'unlinkSync');
@@ -177,6 +174,15 @@ export function Async(FS) {
177
174
  this.checkSync(path, 'exists');
178
175
  return this._sync.existsSync(path);
179
176
  }
177
+ readSync(path, buffer, offset, end) {
178
+ this.checkSync(path, 'read');
179
+ this._sync.readSync(path, buffer, offset, end);
180
+ }
181
+ writeSync(path, buffer, offset) {
182
+ this.checkSync(path, 'write');
183
+ this._sync.writeSync(path, buffer, offset);
184
+ this.queue('write', path, buffer, offset);
185
+ }
180
186
  /**
181
187
  * @internal
182
188
  */