@zenfs/core 0.16.3 → 0.17.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 (74) hide show
  1. package/dist/backends/backend.d.ts +3 -4
  2. package/dist/backends/fetch.d.ts +8 -3
  3. package/dist/backends/fetch.js +3 -2
  4. package/dist/backends/{index/fs.d.ts → file_index.d.ts} +49 -10
  5. package/dist/backends/{index/fs.js → file_index.js} +84 -5
  6. package/dist/backends/memory.d.ts +6 -1
  7. package/dist/backends/memory.js +2 -1
  8. package/dist/backends/overlay.d.ts +16 -16
  9. package/dist/backends/overlay.js +59 -82
  10. package/dist/backends/port/fs.d.ts +6 -2
  11. package/dist/backends/port/fs.js +4 -2
  12. package/dist/backends/store/fs.js +484 -304
  13. package/dist/backends/store/simple.js +5 -1
  14. package/dist/backends/store/store.d.ts +4 -1
  15. package/dist/backends/store/store.js +9 -5
  16. package/dist/browser.min.js +4 -4
  17. package/dist/browser.min.js.map +4 -4
  18. package/dist/config.d.ts +3 -3
  19. package/dist/emulation/async.d.ts +0 -3
  20. package/dist/emulation/async.js +6 -2
  21. package/dist/emulation/dir.d.ts +4 -0
  22. package/dist/emulation/dir.js +8 -6
  23. package/dist/emulation/promises.d.ts +1 -3
  24. package/dist/emulation/promises.js +26 -3
  25. package/dist/emulation/sync.js +1 -2
  26. package/dist/emulation/watchers.d.ts +9 -4
  27. package/dist/emulation/watchers.js +7 -0
  28. package/dist/file.d.ts +17 -1
  29. package/dist/file.js +86 -1
  30. package/dist/filesystem.d.ts +0 -63
  31. package/dist/filesystem.js +0 -311
  32. package/dist/index.d.ts +1 -2
  33. package/dist/index.js +1 -2
  34. package/dist/mixins/async.d.ts +39 -0
  35. package/dist/mixins/async.js +216 -0
  36. package/dist/mixins/mutexed.d.ts +33 -0
  37. package/dist/mixins/mutexed.js +465 -0
  38. package/dist/mixins/readonly.d.ts +25 -0
  39. package/dist/mixins/readonly.js +57 -0
  40. package/dist/mixins/shared.d.ts +12 -0
  41. package/dist/mixins/shared.js +4 -0
  42. package/dist/mixins/sync.d.ts +6 -0
  43. package/dist/mixins/sync.js +43 -0
  44. package/package.json +1 -1
  45. package/src/backends/backend.ts +3 -4
  46. package/src/backends/fetch.ts +7 -3
  47. package/src/backends/{index/fs.ts → file_index.ts} +106 -8
  48. package/src/backends/memory.ts +5 -1
  49. package/src/backends/overlay.ts +64 -90
  50. package/src/backends/port/fs.ts +7 -2
  51. package/src/backends/{index/readme.md → readme.md} +1 -1
  52. package/src/backends/store/fs.ts +97 -155
  53. package/src/backends/store/simple.ts +5 -1
  54. package/src/backends/store/store.ts +10 -5
  55. package/src/config.ts +3 -1
  56. package/src/emulation/async.ts +15 -6
  57. package/src/emulation/dir.ts +19 -16
  58. package/src/emulation/promises.ts +30 -8
  59. package/src/emulation/sync.ts +2 -3
  60. package/src/emulation/watchers.ts +10 -4
  61. package/src/file.ts +94 -1
  62. package/src/filesystem.ts +3 -366
  63. package/src/index.ts +1 -2
  64. package/src/mixins/async.ts +211 -0
  65. package/src/mixins/mutexed.ts +245 -0
  66. package/src/mixins/readonly.ts +97 -0
  67. package/src/mixins/shared.ts +20 -0
  68. package/src/mixins/sync.ts +59 -0
  69. package/dist/backends/index/index.d.ts +0 -43
  70. package/dist/backends/index/index.js +0 -83
  71. package/dist/backends/locked.d.ts +0 -92
  72. package/dist/backends/locked.js +0 -487
  73. package/src/backends/index/index.ts +0 -104
  74. package/src/backends/locked.ts +0 -264
@@ -65,8 +65,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
65
65
  * @todo Make rename compatible with the cache.
66
66
  */
67
67
  public async rename(oldPath: string, newPath: string, cred: Cred): Promise<void> {
68
- const tx = this.store.transaction(),
69
- oldParent = dirname(oldPath),
68
+ await using tx = this.store.transaction();
69
+ const oldParent = dirname(oldPath),
70
70
  oldName = basename(oldPath),
71
71
  newParent = dirname(newPath),
72
72
  newName = basename(newPath),
@@ -108,13 +108,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
108
108
  // If it's a file, delete it.
109
109
  const newNameNode = await this.getINode(tx, newDirList[newName], newPath);
110
110
  if (newNameNode.toStats().isFile()) {
111
- try {
112
- await tx.remove(newNameNode.ino);
113
- await tx.remove(newDirList[newName]);
114
- } catch (e) {
115
- await tx.abort();
116
- throw e;
117
- }
111
+ await tx.remove(newNameNode.ino);
112
+ await tx.remove(newDirList[newName]);
118
113
  } else {
119
114
  // If it's a directory, throw a permissions error.
120
115
  throw ErrnoError.With('EPERM', newPath, 'rename');
@@ -122,20 +117,14 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
122
117
  }
123
118
  newDirList[newName] = nodeId;
124
119
  // Commit the two changed directory listings.
125
- try {
126
- await tx.set(oldDirNode.ino, encodeDirListing(oldDirList));
127
- await tx.set(newDirNode.ino, encodeDirListing(newDirList));
128
- } catch (e) {
129
- await tx.abort();
130
- throw e;
131
- }
132
-
120
+ await tx.set(oldDirNode.ino, encodeDirListing(oldDirList));
121
+ await tx.set(newDirNode.ino, encodeDirListing(newDirList));
133
122
  await tx.commit();
134
123
  }
135
124
 
136
125
  public renameSync(oldPath: string, newPath: string, cred: Cred): void {
137
- const tx = this.store.transaction(),
138
- oldParent = dirname(oldPath),
126
+ using tx = this.store.transaction();
127
+ const oldParent = dirname(oldPath),
139
128
  oldName = basename(oldPath),
140
129
  newParent = dirname(newPath),
141
130
  newName = basename(newPath),
@@ -177,13 +166,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
177
166
  // If it's a file, delete it.
178
167
  const newNameNode = this.getINodeSync(tx, newDirList[newName], newPath);
179
168
  if (newNameNode.toStats().isFile()) {
180
- try {
181
- tx.removeSync(newNameNode.ino);
182
- tx.removeSync(newDirList[newName]);
183
- } catch (e) {
184
- tx.abortSync();
185
- throw e;
186
- }
169
+ tx.removeSync(newNameNode.ino);
170
+ tx.removeSync(newDirList[newName]);
187
171
  } else {
188
172
  // If it's a directory, throw a permissions error.
189
173
  throw ErrnoError.With('EPERM', newPath, 'rename');
@@ -192,19 +176,13 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
192
176
  newDirList[newName] = ino;
193
177
 
194
178
  // Commit the two changed directory listings.
195
- try {
196
- tx.setSync(oldDirNode.ino, encodeDirListing(oldDirList));
197
- tx.setSync(newDirNode.ino, encodeDirListing(newDirList));
198
- } catch (e) {
199
- tx.abortSync();
200
- throw e;
201
- }
202
-
179
+ tx.setSync(oldDirNode.ino, encodeDirListing(oldDirList));
180
+ tx.setSync(newDirNode.ino, encodeDirListing(newDirList));
203
181
  tx.commitSync();
204
182
  }
205
183
 
206
184
  public async stat(path: string, cred: Cred): Promise<Stats> {
207
- const tx = this.store.transaction();
185
+ await using tx = this.store.transaction();
208
186
  const inode = await this.findINode(tx, path);
209
187
  if (!inode) {
210
188
  throw ErrnoError.With('ENOENT', path, 'stat');
@@ -217,8 +195,9 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
217
195
  }
218
196
 
219
197
  public statSync(path: string, cred: Cred): Stats {
198
+ using tx = this.store.transaction();
220
199
  // Get the inode to the item, convert it into a Stats object.
221
- const stats = this.findINodeSync(this.store.transaction(), path).toStats();
200
+ const stats = this.findINodeSync(tx, path).toStats();
222
201
  if (!stats.hasAccess(R_OK, cred)) {
223
202
  throw ErrnoError.With('EACCES', path, 'stat');
224
203
  }
@@ -226,9 +205,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
226
205
  }
227
206
 
228
207
  public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<PreloadFile<this>> {
229
- const data = new Uint8Array(0);
230
- const file = await this.commitNew(this.store.transaction(), path, S_IFREG, mode, cred, data);
231
- return new PreloadFile(this, path, flag, file.toStats(), data);
208
+ const node = await this.commitNew(path, S_IFREG, mode, cred, new Uint8Array(0));
209
+ return new PreloadFile(this, path, flag, node.toStats(), new Uint8Array(0));
232
210
  }
233
211
 
234
212
  public createFileSync(path: string, flag: string, mode: number, cred: Cred): PreloadFile<this> {
@@ -237,8 +215,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
237
215
  }
238
216
 
239
217
  public async openFile(path: string, flag: string, cred: Cred): Promise<PreloadFile<this>> {
240
- const tx = this.store.transaction(),
241
- node = await this.findINode(tx, path),
218
+ await using tx = this.store.transaction();
219
+ const node = await this.findINode(tx, path),
242
220
  data = await tx.get(node.ino);
243
221
  if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
244
222
  throw ErrnoError.With('EACCES', path, 'openFile');
@@ -250,8 +228,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
250
228
  }
251
229
 
252
230
  public openFileSync(path: string, flag: string, cred: Cred): PreloadFile<this> {
253
- const tx = this.store.transaction(),
254
- node = this.findINodeSync(tx, path),
231
+ using tx = this.store.transaction();
232
+ const node = this.findINodeSync(tx, path),
255
233
  data = tx.getSync(node.ino);
256
234
  if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
257
235
  throw ErrnoError.With('EACCES', path, 'openFile');
@@ -289,9 +267,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
289
267
  }
290
268
 
291
269
  public async mkdir(path: string, mode: number, cred: Cred): Promise<void> {
292
- const tx = this.store.transaction(),
293
- data = encode('{}');
294
- await this.commitNew(tx, path, S_IFDIR, mode, cred, data);
270
+ await this.commitNew(path, S_IFDIR, mode, cred, encode('{}'));
295
271
  }
296
272
 
297
273
  public mkdirSync(path: string, mode: number, cred: Cred): void {
@@ -299,7 +275,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
299
275
  }
300
276
 
301
277
  public async readdir(path: string, cred: Cred): Promise<string[]> {
302
- const tx = this.store.transaction();
278
+ await using tx = this.store.transaction();
303
279
  const node = await this.findINode(tx, path);
304
280
  if (!node.toStats().hasAccess(R_OK, cred)) {
305
281
  throw ErrnoError.With('EACCES', path, 'readdur');
@@ -308,7 +284,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
308
284
  }
309
285
 
310
286
  public readdirSync(path: string, cred: Cred): string[] {
311
- const tx = this.store.transaction();
287
+ using tx = this.store.transaction();
312
288
  const node = this.findINodeSync(tx, path);
313
289
  if (!node.toStats().hasAccess(R_OK, cred)) {
314
290
  throw ErrnoError.With('EACCES', path, 'readdir');
@@ -321,23 +297,19 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
321
297
  * @todo Ensure mtime updates properly, and use that to determine if a data update is required.
322
298
  */
323
299
  public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
324
- const tx = this.store.transaction(),
325
- // We use _findInode because we actually need the INode id.
326
- fileInodeId = await this._findINode(tx, dirname(path), basename(path)),
300
+ await using tx = this.store.transaction();
301
+ // We use _findInode because we actually need the INode id.
302
+ const fileInodeId = await this._findINode(tx, dirname(path), basename(path)),
327
303
  fileInode = await this.getINode(tx, fileInodeId, path),
328
304
  inodeChanged = fileInode.update(stats);
329
305
 
330
- try {
331
- // Sync data.
332
- await tx.set(fileInode.ino, data);
333
- // Sync metadata.
334
- if (inodeChanged) {
335
- await tx.set(fileInodeId, fileInode.data);
336
- }
337
- } catch (e) {
338
- await tx.abort();
339
- throw e;
306
+ // Sync data.
307
+ await tx.set(fileInode.ino, data);
308
+ // Sync metadata.
309
+ if (inodeChanged) {
310
+ await tx.set(fileInodeId, fileInode.data);
340
311
  }
312
+
341
313
  await tx.commit();
342
314
  }
343
315
 
@@ -346,29 +318,25 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
346
318
  * @todo Ensure mtime updates properly, and use that to determine if a data update is required.
347
319
  */
348
320
  public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void {
349
- const tx = this.store.transaction(),
350
- // We use _findInode because we actually need the INode id.
351
- fileInodeId = this._findINodeSync(tx, dirname(path), basename(path)),
321
+ using tx = this.store.transaction();
322
+ // We use _findInode because we actually need the INode id.
323
+ const fileInodeId = this._findINodeSync(tx, dirname(path), basename(path)),
352
324
  fileInode = this.getINodeSync(tx, fileInodeId, path),
353
325
  inodeChanged = fileInode.update(stats);
354
326
 
355
- try {
356
- // Sync data.
357
- tx.setSync(fileInode.ino, data);
358
- // Sync metadata.
359
- if (inodeChanged) {
360
- tx.setSync(fileInodeId, fileInode.data);
361
- }
362
- } catch (e) {
363
- tx.abortSync();
364
- throw e;
327
+ // Sync data.
328
+ tx.setSync(fileInode.ino, data);
329
+ // Sync metadata.
330
+ if (inodeChanged) {
331
+ tx.setSync(fileInodeId, fileInode.data);
365
332
  }
333
+
366
334
  tx.commitSync();
367
335
  }
368
336
 
369
337
  public async link(existing: string, newpath: string, cred: Cred): Promise<void> {
370
- const tx = this.store.transaction(),
371
- existingDir: string = dirname(existing),
338
+ await using tx = this.store.transaction();
339
+ const existingDir: string = dirname(existing),
372
340
  existingDirNode = await this.findINode(tx, existingDir);
373
341
 
374
342
  if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
@@ -392,19 +360,15 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
392
360
 
393
361
  node.nlink++;
394
362
  newListing[basename(newpath)] = ino;
395
- try {
396
- tx.setSync(ino, node.data);
397
- tx.setSync(newDirNode.ino, encodeDirListing(newListing));
398
- } catch (e) {
399
- tx.abortSync();
400
- throw e;
401
- }
363
+
364
+ tx.setSync(ino, node.data);
365
+ tx.setSync(newDirNode.ino, encodeDirListing(newListing));
402
366
  tx.commitSync();
403
367
  }
404
368
 
405
369
  public linkSync(existing: string, newpath: string, cred: Cred): void {
406
- const tx = this.store.transaction(),
407
- existingDir: string = dirname(existing),
370
+ using tx = this.store.transaction();
371
+ const existingDir: string = dirname(existing),
408
372
  existingDirNode = this.findINodeSync(tx, existingDir);
409
373
 
410
374
  if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
@@ -427,13 +391,9 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
427
391
  }
428
392
  node.nlink++;
429
393
  newListing[basename(newpath)] = ino;
430
- try {
431
- tx.setSync(ino, node.data);
432
- tx.setSync(newDirNode.ino, encodeDirListing(newListing));
433
- } catch (e) {
434
- tx.abortSync();
435
- throw e;
436
- }
394
+
395
+ tx.setSync(ino, node.data);
396
+ tx.setSync(newDirNode.ino, encodeDirListing(newListing));
437
397
  tx.commitSync();
438
398
  }
439
399
 
@@ -441,7 +401,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
441
401
  * Checks if the root directory exists. Creates it if it doesn't.
442
402
  */
443
403
  public async checkRoot(): Promise<void> {
444
- const tx = this.store.transaction();
404
+ await using tx = this.store.transaction();
445
405
  if (await tx.get(rootIno)) {
446
406
  return;
447
407
  }
@@ -458,7 +418,7 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
458
418
  * Checks if the root directory exists. Creates it if it doesn't.
459
419
  */
460
420
  public checkRootSync(): void {
461
- const tx = this.store.transaction();
421
+ using tx = this.store.transaction();
462
422
  if (tx.getSync(rootIno)) {
463
423
  return;
464
424
  }
@@ -656,7 +616,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
656
616
  * @param cred The UID/GID to create the file with
657
617
  * @param data The data to store at the file's data node.
658
618
  */
659
- private async commitNew(tx: Transaction, path: string, type: FileType, mode: number, cred: Cred, data: Uint8Array): Promise<Inode> {
619
+ private async commitNew(path: string, type: FileType, mode: number, cred: Cred, data: Uint8Array): Promise<Inode> {
620
+ await using tx = this.store.transaction();
660
621
  const parentPath = dirname(path),
661
622
  parent = await this.findINode(tx, parentPath);
662
623
 
@@ -682,25 +643,20 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
682
643
  await tx.abort();
683
644
  throw ErrnoError.With('EEXIST', path, 'commitNewFile');
684
645
  }
685
- try {
686
- // Commit data.
687
-
688
- const inode = new Inode();
689
- inode.ino = await this.addNew(tx, data, path);
690
- inode.mode = mode | type;
691
- inode.uid = cred.uid;
692
- inode.gid = cred.gid;
693
- inode.size = data.length;
694
-
695
- // Update and commit parent directory listing.
696
- listing[fname] = await this.addNew(tx, inode.data, path);
697
- await tx.set(parent.ino, encodeDirListing(listing));
698
- await tx.commit();
699
- return inode;
700
- } catch (e) {
701
- await tx.abort();
702
- throw e;
703
- }
646
+
647
+ // Commit data.
648
+ const inode = new Inode();
649
+ inode.ino = await this.addNew(tx, data, path);
650
+ inode.mode = mode | type;
651
+ inode.uid = cred.uid;
652
+ inode.gid = cred.gid;
653
+ inode.size = data.length;
654
+
655
+ // Update and commit parent directory listing.
656
+ listing[fname] = await this.addNew(tx, inode.data, path);
657
+ await tx.set(parent.ino, encodeDirListing(listing));
658
+ await tx.commit();
659
+ return inode;
704
660
  }
705
661
 
706
662
  /**
@@ -713,8 +669,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
713
669
  * @return The Inode for the new file.
714
670
  */
715
671
  protected commitNewSync(path: string, type: FileType, mode: number, cred: Cred, data: Uint8Array = new Uint8Array()): Inode {
716
- const tx = this.store.transaction(),
717
- parentPath = dirname(path),
672
+ using tx = this.store.transaction();
673
+ const parentPath = dirname(path),
718
674
  parent = this.findINodeSync(tx, parentPath);
719
675
 
720
676
  //Check that the creater has correct access
@@ -739,21 +695,16 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
739
695
  throw ErrnoError.With('EEXIST', path, 'commitNewFile');
740
696
  }
741
697
 
698
+ // Commit data.
742
699
  const node = new Inode();
743
- try {
744
- // Commit data.
745
- node.ino = this.addNewSync(tx, data, path);
746
- node.size = data.length;
747
- node.mode = mode | type;
748
- node.uid = cred.uid;
749
- node.gid = cred.gid;
750
- // Update and commit parent directory listing.
751
- listing[fname] = this.addNewSync(tx, node.data, path);
752
- tx.setSync(parent.ino, encodeDirListing(listing));
753
- } catch (e) {
754
- tx.abortSync();
755
- throw e;
756
- }
700
+ node.ino = this.addNewSync(tx, data, path);
701
+ node.size = data.length;
702
+ node.mode = mode | type;
703
+ node.uid = cred.uid;
704
+ node.gid = cred.gid;
705
+ // Update and commit parent directory listing.
706
+ listing[fname] = this.addNewSync(tx, node.data, path);
707
+ tx.setSync(parent.ino, encodeDirListing(listing));
757
708
  tx.commitSync();
758
709
  return node;
759
710
  }
@@ -765,8 +716,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
765
716
  * @todo Update mtime.
766
717
  */
767
718
  private async remove(path: string, isDir: boolean, cred: Cred): Promise<void> {
768
- const tx = this.store.transaction(),
769
- parent: string = dirname(path),
719
+ await using tx = this.store.transaction();
720
+ const parent: string = dirname(path),
770
721
  parentNode = await this.findINode(tx, parent),
771
722
  listing = await this.getDirListing(tx, parentNode, parent),
772
723
  fileName: string = basename(path);
@@ -795,17 +746,12 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
795
746
  throw ErrnoError.With('ENOTDIR', path, 'removeEntry');
796
747
  }
797
748
 
798
- try {
799
- await tx.set(parentNode.ino, encodeDirListing(listing));
749
+ await tx.set(parentNode.ino, encodeDirListing(listing));
800
750
 
801
- if (--fileNode.nlink < 1) {
802
- // remove file
803
- await tx.remove(fileNode.ino);
804
- await tx.remove(fileIno);
805
- }
806
- } catch (e) {
807
- await tx.abort();
808
- throw e;
751
+ if (--fileNode.nlink < 1) {
752
+ // remove file
753
+ await tx.remove(fileNode.ino);
754
+ await tx.remove(fileIno);
809
755
  }
810
756
 
811
757
  // Success.
@@ -819,8 +765,8 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
819
765
  * @todo Update mtime.
820
766
  */
821
767
  protected removeSync(path: string, isDir: boolean, cred: Cred): void {
822
- const tx = this.store.transaction(),
823
- parent: string = dirname(path),
768
+ using tx = this.store.transaction();
769
+ const parent: string = dirname(path),
824
770
  parentNode = this.findINodeSync(tx, parent),
825
771
  listing = this.getDirListingSync(tx, parentNode, parent),
826
772
  fileName: string = basename(path),
@@ -848,19 +794,15 @@ export class StoreFS<T extends Store = Store> extends FileSystem {
848
794
  throw ErrnoError.With('ENOTDIR', path, 'removeEntry');
849
795
  }
850
796
 
851
- try {
852
- // Update directory listing.
853
- tx.setSync(parentNode.ino, encodeDirListing(listing));
797
+ // Update directory listing.
798
+ tx.setSync(parentNode.ino, encodeDirListing(listing));
854
799
 
855
- if (--fileNode.nlink < 1) {
856
- // remove file
857
- tx.removeSync(fileNode.ino);
858
- tx.removeSync(fileIno);
859
- }
860
- } catch (e) {
861
- tx.abortSync();
862
- throw e;
800
+ if (--fileNode.nlink < 1) {
801
+ // remove file
802
+ tx.removeSync(fileNode.ino);
803
+ tx.removeSync(fileIno);
863
804
  }
805
+
864
806
  // Success.
865
807
  tx.commitSync();
866
808
  }
@@ -103,10 +103,13 @@ export class SimpleTransaction extends SyncTransaction<SimpleSyncStore> {
103
103
  }
104
104
 
105
105
  public commitSync(): void {
106
- /* NOP */
106
+ this.done = true;
107
107
  }
108
108
 
109
109
  public abortSync(): void {
110
+ if (!this.done) {
111
+ return;
112
+ }
110
113
  // Rollback old values.
111
114
  for (const key of this.modifiedKeys) {
112
115
  const value = this.originalData.get(key);
@@ -118,6 +121,7 @@ export class SimpleTransaction extends SyncTransaction<SimpleSyncStore> {
118
121
  this.store.set(key, value);
119
122
  }
120
123
  }
124
+ this.done = true;
121
125
  }
122
126
 
123
127
  /**
@@ -38,7 +38,10 @@ export interface Store {
38
38
  export abstract class Transaction<T extends Store = Store> {
39
39
  constructor(protected store: T) {}
40
40
 
41
- protected aborted: boolean = false;
41
+ /**
42
+ * Whether the transaction was commited or aborted
43
+ */
44
+ protected done: boolean = false;
42
45
 
43
46
  /**
44
47
  * Retrieves the data at the given key.
@@ -92,11 +95,11 @@ export abstract class Transaction<T extends Store = Store> {
92
95
  public abstract commit(): Promise<void>;
93
96
 
94
97
  public async [Symbol.asyncDispose]() {
95
- if (this.aborted) {
98
+ if (this.done) {
96
99
  return;
97
100
  }
98
101
 
99
- await this.commit();
102
+ await this.abort();
100
103
  }
101
104
 
102
105
  /**
@@ -105,11 +108,11 @@ export abstract class Transaction<T extends Store = Store> {
105
108
  public abstract commitSync(): void;
106
109
 
107
110
  public [Symbol.dispose]() {
108
- if (this.aborted) {
111
+ if (this.done) {
109
112
  return;
110
113
  }
111
114
 
112
- this.commitSync();
115
+ this.abortSync();
113
116
  }
114
117
 
115
118
  /**
@@ -127,6 +130,7 @@ export abstract class Transaction<T extends Store = Store> {
127
130
  * Transaction that implements asynchronous operations with synchronous ones
128
131
  */
129
132
  export abstract class SyncTransaction<T extends Store = Store> extends Transaction<T> {
133
+ /* eslint-disable @typescript-eslint/require-await */
130
134
  public async get(ino: Ino): Promise<Uint8Array> {
131
135
  return this.getSync(ino);
132
136
  }
@@ -142,6 +146,7 @@ export abstract class SyncTransaction<T extends Store = Store> extends Transacti
142
146
  public async abort(): Promise<void> {
143
147
  return this.abortSync();
144
148
  }
149
+ /* eslint-enable @typescript-eslint/require-await */
145
150
  }
146
151
 
147
152
  /**
package/src/config.ts CHANGED
@@ -64,7 +64,9 @@ export async function resolveMountConfig<T extends Backend>(config: MountConfigu
64
64
  return mount;
65
65
  }
66
66
 
67
- export type ConfigMounts = { [K in AbsolutePath]: Backend };
67
+ export interface ConfigMounts {
68
+ [K: AbsolutePath]: Backend;
69
+ }
68
70
 
69
71
  /**
70
72
  * Configuration
@@ -10,6 +10,7 @@ import { type Dir } from './dir.js';
10
10
  import * as promises from './promises.js';
11
11
  import { fd2file } from './shared.js';
12
12
  import { ReadStream, WriteStream } from './streams.js';
13
+ import { FSWatcher } from './watchers.js';
13
14
 
14
15
  /**
15
16
  * Asynchronous rename. No arguments other than a possible exception are given
@@ -675,7 +676,11 @@ access satisfies Omit<typeof fs.access, '__promisify__'>;
675
676
  */
676
677
  export function watchFile(path: fs.PathLike, listener: (curr: Stats, prev: Stats) => void): void;
677
678
  export function watchFile(path: fs.PathLike, options: { persistent?: boolean; interval?: number }, listener: (curr: Stats, prev: Stats) => void): void;
678
- export function watchFile(path: fs.PathLike, optsListener: any, listener: (curr: Stats, prev: Stats) => void = nop): void {
679
+ export function watchFile(
680
+ path: fs.PathLike,
681
+ optsListener: { persistent?: boolean; interval?: number } | ((curr: Stats, prev: Stats) => void),
682
+ listener: (curr: Stats, prev: Stats) => void = nop
683
+ ): void {
679
684
  throw ErrnoError.With('ENOSYS', path.toString(), 'watchFile');
680
685
  }
681
686
  watchFile satisfies Omit<typeof fs.watchFile, '__promisify__'>;
@@ -688,13 +693,17 @@ export function unwatchFile(path: fs.PathLike, listener: (curr: Stats, prev: Sta
688
693
  }
689
694
  unwatchFile satisfies Omit<typeof fs.unwatchFile, '__promisify__'>;
690
695
 
691
- /**
692
- * @todo Implement
693
- */
694
696
  export function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): fs.FSWatcher;
695
697
  export function watch(path: fs.PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): fs.FSWatcher;
696
- export function watch(path: fs.PathLike, options: any, listener: (event: string, filename: string) => any = nop): fs.FSWatcher {
697
- throw ErrnoError.With('ENOSYS', path.toString(), 'watch');
698
+ export function watch(
699
+ path: fs.PathLike,
700
+ options?: fs.WatchOptions | ((event: string, filename: string) => any),
701
+ listener?: (event: string, filename: string) => any
702
+ ): fs.FSWatcher {
703
+ const watcher = new FSWatcher<string>(typeof options == 'object' ? options : {});
704
+ listener = typeof options == 'function' ? options : listener;
705
+ watcher.on('change', listener || nop);
706
+ return watcher;
698
707
  }
699
708
  watch satisfies Omit<typeof fs.watch, '__promisify__'>;
700
709
 
@@ -57,15 +57,22 @@ export class Dir implements _Dir {
57
57
 
58
58
  protected _entries: Dirent[] = [];
59
59
 
60
- constructor(public readonly path: string) {}
60
+ /**
61
+ * @internal
62
+ */
63
+ public async _loadEntries() {
64
+ this._entries ??= await readdir(this.path, { withFileTypes: true });
65
+ }
66
+
67
+ public constructor(public readonly path: string) {}
61
68
 
62
69
  /**
63
70
  * Asynchronously close the directory's underlying resource handle.
64
71
  * Subsequent reads will result in errors.
65
72
  */
66
- close(): Promise<void>;
67
- close(cb: Callback): void;
68
- close(cb?: Callback): void | Promise<void> {
73
+ public close(): Promise<void>;
74
+ public close(cb: Callback): void;
75
+ public close(cb?: Callback): void | Promise<void> {
69
76
  this.closed = true;
70
77
  if (!cb) {
71
78
  return Promise.resolve();
@@ -77,14 +84,12 @@ export class Dir implements _Dir {
77
84
  * Synchronously close the directory's underlying resource handle.
78
85
  * Subsequent reads will result in errors.
79
86
  */
80
- closeSync(): void {
87
+ public closeSync(): void {
81
88
  this.closed = true;
82
89
  }
83
90
 
84
91
  protected async _read(): Promise<Dirent | null> {
85
- if (!this._entries) {
86
- this._entries = await readdir(this.path, { withFileTypes: true });
87
- }
92
+ await this._loadEntries();
88
93
  if (!this._entries.length) {
89
94
  return null;
90
95
  }
@@ -96,9 +101,9 @@ export class Dir implements _Dir {
96
101
  * After the read is completed, a value is returned that will be resolved with an `Dirent`, or `null` if there are no more directory entries to read.
97
102
  * Directory entries returned by this function are in no particular order as provided by the operating system's underlying directory mechanisms.
98
103
  */
99
- read(): Promise<Dirent | null>;
100
- read(cb: Callback<[Dirent | null]>): void;
101
- read(cb?: Callback<[Dirent | null]>): void | Promise<Dirent | null> {
104
+ public read(): Promise<Dirent | null>;
105
+ public read(cb: Callback<[Dirent | null]>): void;
106
+ public read(cb?: Callback<[Dirent | null]>): void | Promise<Dirent | null> {
102
107
  if (!cb) {
103
108
  return this._read();
104
109
  }
@@ -111,10 +116,8 @@ export class Dir implements _Dir {
111
116
  * If there are no more directory entries to read, null will be returned.
112
117
  * Directory entries returned by this function are in no particular order as provided by the operating system's underlying directory mechanisms.
113
118
  */
114
- readSync(): Dirent | null {
115
- if (!this._entries) {
116
- this._entries = readdirSync(this.path, { withFileTypes: true });
117
- }
119
+ public readSync(): Dirent | null {
120
+ this._entries ??= readdirSync(this.path, { withFileTypes: true });
118
121
  if (!this._entries.length) {
119
122
  return null;
120
123
  }
@@ -124,7 +127,7 @@ export class Dir implements _Dir {
124
127
  /**
125
128
  * Asynchronously iterates over the directory via `readdir(3)` until all entries have been read.
126
129
  */
127
- [Symbol.asyncIterator](): AsyncIterableIterator<Dirent> {
130
+ public [Symbol.asyncIterator](): AsyncIterableIterator<Dirent> {
128
131
  const _this = this;
129
132
 
130
133
  return {