@zenfs/core 0.16.4 → 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 +25 -2
  25. package/dist/emulation/sync.js +0 -1
  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 +28 -6
  59. package/src/emulation/sync.ts +1 -2
  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
@@ -1,11 +1,109 @@
1
- import type { Cred } from '../../cred.js';
2
- import { ErrnoError, Errno } from '../../error.js';
3
- import { NoSyncFile, isWriteable, flagToMode } from '../../file.js';
4
- import { Readonly, FileSystem } from '../../filesystem.js';
5
- import type { Stats } from '../../stats.js';
6
- import { decode } from '../../utils.js';
7
- import type { IndexData } from './index.js';
8
- import { Index } from './index.js';
1
+ /* Note: this file is named file_index.ts because Typescript has special behavior regarding index.ts which can't be disabled. */
2
+
3
+ import { isJSON } from 'utilium';
4
+ import type { Cred } from '../cred.js';
5
+ import { basename, dirname } from '../emulation/path.js';
6
+ import { Errno, ErrnoError } from '../error.js';
7
+ import { NoSyncFile, flagToMode, isWriteable } from '../file.js';
8
+ import { FileSystem } from '../filesystem.js';
9
+ import { Readonly } from '../mixins/readonly.js';
10
+ import type { StatsLike } from '../stats.js';
11
+ import { Stats } from '../stats.js';
12
+ import { decode, encode } from '../utils.js';
13
+
14
+ /**
15
+ * An Index in JSON form
16
+ * @internal
17
+ */
18
+ export interface IndexData {
19
+ version: 1;
20
+ entries: Record<string, StatsLike<number>>;
21
+ }
22
+
23
+ export const version = 1;
24
+
25
+ /**
26
+ * An index of files
27
+ * @internal
28
+ */
29
+ export class Index extends Map<string, Stats> {
30
+ /**
31
+ * Convience method
32
+ */
33
+ public files(): Map<string, Stats> {
34
+ const files = new Map<string, Stats>();
35
+ for (const [path, stats] of this) {
36
+ if (stats.isFile()) {
37
+ files.set(path, stats);
38
+ }
39
+ }
40
+ return files;
41
+ }
42
+
43
+ /**
44
+ * Converts the index to JSON
45
+ */
46
+ public toJSON(): IndexData {
47
+ return {
48
+ version,
49
+ entries: Object.fromEntries(this),
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Converts the index to a string
55
+ */
56
+ public toString(): string {
57
+ return JSON.stringify(this.toJSON());
58
+ }
59
+
60
+ /**
61
+ * Returns the files in the directory `dir`.
62
+ * This is expensive so it is only called once per directory.
63
+ */
64
+ protected dirEntries(dir: string): string[] {
65
+ const entries = [];
66
+ for (const entry of this.keys()) {
67
+ if (dirname(entry) == dir) {
68
+ entries.push(basename(entry));
69
+ }
70
+ }
71
+ return entries;
72
+ }
73
+
74
+ /**
75
+ * Loads the index from JSON data
76
+ */
77
+ public fromJSON(json: IndexData): void {
78
+ if (json.version != version) {
79
+ throw new ErrnoError(Errno.EINVAL, 'Index version mismatch');
80
+ }
81
+
82
+ this.clear();
83
+
84
+ for (const [path, data] of Object.entries(json.entries)) {
85
+ const stats = new Stats(data);
86
+ if (stats.isDirectory()) {
87
+ stats.fileData = encode(JSON.stringify(this.dirEntries(path)));
88
+ }
89
+ this.set(path, stats);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Parses an index from a string
95
+ */
96
+ public static parse(data: string): Index {
97
+ if (!isJSON(data)) {
98
+ throw new ErrnoError(Errno.EINVAL, 'Invalid JSON');
99
+ }
100
+
101
+ const json = JSON.parse(data) as IndexData;
102
+ const index = new Index();
103
+ index.fromJSON(json);
104
+ return index;
105
+ }
106
+ }
9
107
 
10
108
  export abstract class IndexFS extends Readonly(FileSystem) {
11
109
  protected index: Index = new Index();
@@ -26,7 +26,7 @@ export class InMemoryStore extends Map<Ino, Uint8Array> implements SimpleSyncSto
26
26
  * A simple in-memory file system backed by an InMemoryStore.
27
27
  * Files are not persisted across page loads.
28
28
  */
29
- export const InMemory = {
29
+ export const _InMemory = {
30
30
  name: 'InMemory',
31
31
  isAvailable(): boolean {
32
32
  return true;
@@ -44,3 +44,7 @@ export const InMemory = {
44
44
  return fs;
45
45
  },
46
46
  } as const satisfies Backend<StoreFS<InMemoryStore>, { name?: string }>;
47
+ type _inmemory = typeof _InMemory;
48
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
49
+ interface InMemory extends _inmemory {}
50
+ export const InMemory: InMemory = _InMemory;
@@ -4,7 +4,7 @@ import { ErrnoError, Errno } from '../error.js';
4
4
  import type { File } from '../file.js';
5
5
  import { PreloadFile, parseFlag } from '../file.js';
6
6
  import { Stats } from '../stats.js';
7
- import { LockedFS } from './locked.js';
7
+ import { Mutexed } from '../mixins/mutexed.js';
8
8
  import { dirname } from '../emulation/path.js';
9
9
  import type { Cred } from '../cred.js';
10
10
  import { rootCred } from '../cred.js';
@@ -33,19 +33,19 @@ export interface OverlayOptions {
33
33
  * OverlayFS makes a read-only filesystem writable by storing writes on a second, writable file system.
34
34
  * Deletes are persisted via metadata stored on the writable file system.
35
35
  *
36
- * This class contains no locking whatsoever. It is wrapped in a LockedFS to prevent races.
36
+ * This class contains no locking whatsoever. It is mutexed to prevent races.
37
37
  *
38
38
  * @internal
39
39
  */
40
- export class UnlockedOverlayFS extends FileSystem {
40
+ export class UnmutexedOverlayFS extends FileSystem {
41
41
  async ready(): Promise<void> {
42
- await this._readable.ready();
43
- await this._writable.ready();
42
+ await this.readable.ready();
43
+ await this.writable.ready();
44
44
  await this._ready;
45
45
  }
46
46
 
47
- private _writable: FileSystem;
48
- private _readable: FileSystem;
47
+ public readonly writable: FileSystem;
48
+ public readonly readable: FileSystem;
49
49
  private _isInitialized: boolean = false;
50
50
  private _deletedFiles: Set<string> = new Set();
51
51
  private _deleteLog: string = '';
@@ -61,9 +61,9 @@ export class UnlockedOverlayFS extends FileSystem {
61
61
 
62
62
  constructor({ writable, readable }: OverlayOptions) {
63
63
  super();
64
- this._writable = writable;
65
- this._readable = readable;
66
- if (this._writable.metadata().readonly) {
64
+ this.writable = writable;
65
+ this.readable = readable;
66
+ if (this.writable.metadata().readonly) {
67
67
  throw new ErrnoError(Errno.EINVAL, 'Writable file system must be writable.');
68
68
  }
69
69
  this._ready = this._initialize();
@@ -76,26 +76,19 @@ export class UnlockedOverlayFS extends FileSystem {
76
76
  };
77
77
  }
78
78
 
79
- public getOverlayedFileSystems(): OverlayOptions {
80
- return {
81
- readable: this._readable,
82
- writable: this._writable,
83
- };
84
- }
85
-
86
79
  public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
87
80
  const cred = stats.cred(0, 0);
88
81
  await this.createParentDirectories(path, cred);
89
- if (!(await this._writable.exists(path, cred))) {
90
- await this._writable.createFile(path, 'w', 0o644, cred);
82
+ if (!(await this.writable.exists(path, cred))) {
83
+ await this.writable.createFile(path, 'w', 0o644, cred);
91
84
  }
92
- await this._writable.sync(path, data, stats);
85
+ await this.writable.sync(path, data, stats);
93
86
  }
94
87
 
95
88
  public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void {
96
89
  const cred = stats.cred(0, 0);
97
90
  this.createParentDirectoriesSync(path, cred);
98
- this._writable.syncSync(path, data, stats);
91
+ this.writable.syncSync(path, data, stats);
99
92
  }
100
93
 
101
94
  /**
@@ -109,7 +102,7 @@ export class UnlockedOverlayFS extends FileSystem {
109
102
 
110
103
  // Read deletion log, process into metadata.
111
104
  try {
112
- const file = await this._writable.openFile(deletionLogPath, parseFlag('r'), rootCred);
105
+ const file = await this.writable.openFile(deletionLogPath, parseFlag('r'), rootCred);
113
106
  const { size } = await file.stat();
114
107
  const { buffer } = await file.read(new Uint8Array(size));
115
108
  this._deleteLog = decode(buffer);
@@ -138,7 +131,7 @@ export class UnlockedOverlayFS extends FileSystem {
138
131
  this.checkPath(newPath);
139
132
 
140
133
  try {
141
- await this._writable.rename(oldPath, newPath, cred);
134
+ await this.writable.rename(oldPath, newPath, cred);
142
135
  } catch (e) {
143
136
  if (this._deletedFiles.has(oldPath)) {
144
137
  throw ErrnoError.With('ENOENT', oldPath, 'rename');
@@ -152,7 +145,7 @@ export class UnlockedOverlayFS extends FileSystem {
152
145
  this.checkPath(newPath);
153
146
 
154
147
  try {
155
- this._writable.renameSync(oldPath, newPath, cred);
148
+ this.writable.renameSync(oldPath, newPath, cred);
156
149
  } catch (e) {
157
150
  if (this._deletedFiles.has(oldPath)) {
158
151
  throw ErrnoError.With('ENOENT', oldPath, 'rename');
@@ -163,12 +156,12 @@ export class UnlockedOverlayFS extends FileSystem {
163
156
  public async stat(path: string, cred: Cred): Promise<Stats> {
164
157
  this.checkInitialized();
165
158
  try {
166
- return await this._writable.stat(path, cred);
159
+ return await this.writable.stat(path, cred);
167
160
  } catch (e) {
168
161
  if (this._deletedFiles.has(path)) {
169
162
  throw ErrnoError.With('ENOENT', path, 'stat');
170
163
  }
171
- const oldStat = new Stats(await this._readable.stat(path, cred));
164
+ const oldStat = new Stats(await this.readable.stat(path, cred));
172
165
  // Make the oldStat's mode writable. Preserve the topmost part of the mode, which specifies the type
173
166
  oldStat.mode |= 0o222;
174
167
  return oldStat;
@@ -178,12 +171,12 @@ export class UnlockedOverlayFS extends FileSystem {
178
171
  public statSync(path: string, cred: Cred): Stats {
179
172
  this.checkInitialized();
180
173
  try {
181
- return this._writable.statSync(path, cred);
174
+ return this.writable.statSync(path, cred);
182
175
  } catch (e) {
183
176
  if (this._deletedFiles.has(path)) {
184
177
  throw ErrnoError.With('ENOENT', path, 'stat');
185
178
  }
186
- const oldStat = new Stats(this._readable.statSync(path, cred));
179
+ const oldStat = new Stats(this.readable.statSync(path, cred));
187
180
  // Make the oldStat's mode writable. Preserve the topmost part of the mode, which specifies the type.
188
181
  oldStat.mode |= 0o222;
189
182
  return oldStat;
@@ -191,22 +184,22 @@ export class UnlockedOverlayFS extends FileSystem {
191
184
  }
192
185
 
193
186
  public async openFile(path: string, flag: string, cred: Cred): Promise<File> {
194
- if (await this._writable.exists(path, cred)) {
195
- return this._writable.openFile(path, flag, cred);
187
+ if (await this.writable.exists(path, cred)) {
188
+ return this.writable.openFile(path, flag, cred);
196
189
  }
197
190
  // Create an OverlayFile.
198
- const file = await this._readable.openFile(path, parseFlag('r'), cred);
191
+ const file = await this.readable.openFile(path, parseFlag('r'), cred);
199
192
  const stats = new Stats(await file.stat());
200
193
  const { buffer } = await file.read(new Uint8Array(stats.size));
201
194
  return new PreloadFile(this, path, flag, stats, buffer);
202
195
  }
203
196
 
204
197
  public openFileSync(path: string, flag: string, cred: Cred): File {
205
- if (this._writable.existsSync(path, cred)) {
206
- return this._writable.openFileSync(path, flag, cred);
198
+ if (this.writable.existsSync(path, cred)) {
199
+ return this.writable.openFileSync(path, flag, cred);
207
200
  }
208
201
  // Create an OverlayFile.
209
- const file = this._readable.openFileSync(path, parseFlag('r'), cred);
202
+ const file = this.readable.openFileSync(path, parseFlag('r'), cred);
210
203
  const stats = new Stats(file.statSync());
211
204
  const data = new Uint8Array(stats.size);
212
205
  file.readSync(data);
@@ -215,24 +208,24 @@ export class UnlockedOverlayFS extends FileSystem {
215
208
 
216
209
  public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> {
217
210
  this.checkInitialized();
218
- await this._writable.createFile(path, flag, mode, cred);
211
+ await this.writable.createFile(path, flag, mode, cred);
219
212
  return this.openFile(path, flag, cred);
220
213
  }
221
214
 
222
215
  public createFileSync(path: string, flag: string, mode: number, cred: Cred): File {
223
216
  this.checkInitialized();
224
- this._writable.createFileSync(path, flag, mode, cred);
217
+ this.writable.createFileSync(path, flag, mode, cred);
225
218
  return this.openFileSync(path, flag, cred);
226
219
  }
227
220
 
228
221
  public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> {
229
222
  this.checkInitialized();
230
- await this._writable.link(srcpath, dstpath, cred);
223
+ await this.writable.link(srcpath, dstpath, cred);
231
224
  }
232
225
 
233
226
  public linkSync(srcpath: string, dstpath: string, cred: Cred): void {
234
227
  this.checkInitialized();
235
- this._writable.linkSync(srcpath, dstpath, cred);
228
+ this.writable.linkSync(srcpath, dstpath, cred);
236
229
  }
237
230
 
238
231
  public async unlink(path: string, cred: Cred): Promise<void> {
@@ -242,8 +235,8 @@ export class UnlockedOverlayFS extends FileSystem {
242
235
  throw ErrnoError.With('ENOENT', path, 'unlink');
243
236
  }
244
237
 
245
- if (await this._writable.exists(path, cred)) {
246
- await this._writable.unlink(path, cred);
238
+ if (await this.writable.exists(path, cred)) {
239
+ await this.writable.unlink(path, cred);
247
240
  }
248
241
 
249
242
  // if it still exists add to the delete log
@@ -259,8 +252,8 @@ export class UnlockedOverlayFS extends FileSystem {
259
252
  throw ErrnoError.With('ENOENT', path, 'unlink');
260
253
  }
261
254
 
262
- if (this._writable.existsSync(path, cred)) {
263
- this._writable.unlinkSync(path, cred);
255
+ if (this.writable.existsSync(path, cred)) {
256
+ this.writable.unlinkSync(path, cred);
264
257
  }
265
258
 
266
259
  // if it still exists add to the delete log
@@ -274,8 +267,8 @@ export class UnlockedOverlayFS extends FileSystem {
274
267
  if (!(await this.exists(path, cred))) {
275
268
  throw ErrnoError.With('ENOENT', path, 'rmdir');
276
269
  }
277
- if (await this._writable.exists(path, cred)) {
278
- await this._writable.rmdir(path, cred);
270
+ if (await this.writable.exists(path, cred)) {
271
+ await this.writable.rmdir(path, cred);
279
272
  }
280
273
  if (await this.exists(path, cred)) {
281
274
  // Check if directory is empty.
@@ -292,8 +285,8 @@ export class UnlockedOverlayFS extends FileSystem {
292
285
  if (!this.existsSync(path, cred)) {
293
286
  throw ErrnoError.With('ENOENT', path, 'rmdir');
294
287
  }
295
- if (this._writable.existsSync(path, cred)) {
296
- this._writable.rmdirSync(path, cred);
288
+ if (this.writable.existsSync(path, cred)) {
289
+ this.writable.rmdirSync(path, cred);
297
290
  }
298
291
  if (this.existsSync(path, cred)) {
299
292
  // Check if directory is empty.
@@ -312,7 +305,7 @@ export class UnlockedOverlayFS extends FileSystem {
312
305
  }
313
306
  // The below will throw should any of the parent directories fail to exist on _writable.
314
307
  await this.createParentDirectories(path, cred);
315
- await this._writable.mkdir(path, mode, cred);
308
+ await this.writable.mkdir(path, mode, cred);
316
309
  }
317
310
 
318
311
  public mkdirSync(path: string, mode: number, cred: Cred): void {
@@ -322,7 +315,7 @@ export class UnlockedOverlayFS extends FileSystem {
322
315
  }
323
316
  // The below will throw should any of the parent directories fail to exist on _writable.
324
317
  this.createParentDirectoriesSync(path, cred);
325
- this._writable.mkdirSync(path, mode, cred);
318
+ this.writable.mkdirSync(path, mode, cred);
326
319
  }
327
320
 
328
321
  public async readdir(path: string, cred: Cred): Promise<string[]> {
@@ -335,12 +328,12 @@ export class UnlockedOverlayFS extends FileSystem {
335
328
  // Readdir in both, check delete log on RO file system's listing, merge, return.
336
329
  const contents: string[] = [];
337
330
  try {
338
- contents.push(...(await this._writable.readdir(path, cred)));
331
+ contents.push(...(await this.writable.readdir(path, cred)));
339
332
  } catch (e) {
340
333
  // NOP.
341
334
  }
342
335
  try {
343
- contents.push(...(await this._readable.readdir(path, cred)).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`)));
336
+ contents.push(...(await this.readable.readdir(path, cred)).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`)));
344
337
  } catch (e) {
345
338
  // NOP.
346
339
  }
@@ -362,12 +355,12 @@ export class UnlockedOverlayFS extends FileSystem {
362
355
  // Readdir in both, check delete log on RO file system's listing, merge, return.
363
356
  let contents: string[] = [];
364
357
  try {
365
- contents = contents.concat(this._writable.readdirSync(path, cred));
358
+ contents = contents.concat(this.writable.readdirSync(path, cred));
366
359
  } catch (e) {
367
360
  // NOP.
368
361
  }
369
362
  try {
370
- contents = contents.concat(this._readable.readdirSync(path, cred).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`)));
363
+ contents = contents.concat(this.readable.readdirSync(path, cred).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`)));
371
364
  } catch (e) {
372
365
  // NOP.
373
366
  }
@@ -391,7 +384,7 @@ export class UnlockedOverlayFS extends FileSystem {
391
384
  return;
392
385
  }
393
386
  this._deleteLogUpdatePending = true;
394
- const log = await this._writable.openFile(deletionLogPath, parseFlag('w'), cred);
387
+ const log = await this.writable.openFile(deletionLogPath, parseFlag('w'), cred);
395
388
  try {
396
389
  await log.write(encode(this._deleteLog));
397
390
  if (this._deleteLogUpdateNeeded) {
@@ -445,21 +438,21 @@ export class UnlockedOverlayFS extends FileSystem {
445
438
  private createParentDirectoriesSync(path: string, cred: Cred): void {
446
439
  let parent = dirname(path),
447
440
  toCreate: string[] = [];
448
- while (!this._writable.existsSync(parent, cred)) {
441
+ while (!this.writable.existsSync(parent, cred)) {
449
442
  toCreate.push(parent);
450
443
  parent = dirname(parent);
451
444
  }
452
445
  toCreate = toCreate.reverse();
453
446
 
454
447
  for (const p of toCreate) {
455
- this._writable.mkdirSync(p, this.statSync(p, cred).mode, cred);
448
+ this.writable.mkdirSync(p, this.statSync(p, cred).mode, cred);
456
449
  }
457
450
  }
458
451
 
459
452
  private async createParentDirectories(path: string, cred: Cred): Promise<void> {
460
453
  let parent = dirname(path),
461
454
  toCreate: string[] = [];
462
- while (!(await this._writable.exists(parent, cred))) {
455
+ while (!(await this.writable.exists(parent, cred))) {
463
456
  toCreate.push(parent);
464
457
  parent = dirname(parent);
465
458
  }
@@ -467,7 +460,7 @@ export class UnlockedOverlayFS extends FileSystem {
467
460
 
468
461
  for (const p of toCreate) {
469
462
  const stats = await this.stat(p, cred);
470
- await this._writable.mkdir(p, stats.mode, cred);
463
+ await this.writable.mkdir(p, stats.mode, cred);
471
464
  }
472
465
  }
473
466
 
@@ -480,7 +473,7 @@ export class UnlockedOverlayFS extends FileSystem {
480
473
  if (!this.existsSync(path, cred)) {
481
474
  throw ErrnoError.With('ENOENT', path, 'operateOnWriteable');
482
475
  }
483
- if (!this._writable.existsSync(path, cred)) {
476
+ if (!this.writable.existsSync(path, cred)) {
484
477
  // File is on readable storage. Copy to writable storage before
485
478
  // changing its mode.
486
479
  this.copyToWritableSync(path, cred);
@@ -492,7 +485,7 @@ export class UnlockedOverlayFS extends FileSystem {
492
485
  throw ErrnoError.With('ENOENT', path, 'operateOnWritable');
493
486
  }
494
487
 
495
- if (!(await this._writable.exists(path, cred))) {
488
+ if (!(await this.writable.exists(path, cred))) {
496
489
  return this.copyToWritable(path, cred);
497
490
  }
498
491
  }
@@ -504,15 +497,15 @@ export class UnlockedOverlayFS extends FileSystem {
504
497
  private copyToWritableSync(path: string, cred: Cred): void {
505
498
  const stats = this.statSync(path, cred);
506
499
  if (stats.isDirectory()) {
507
- this._writable.mkdirSync(path, stats.mode, cred);
500
+ this.writable.mkdirSync(path, stats.mode, cred);
508
501
  return;
509
502
  }
510
503
 
511
504
  const data = new Uint8Array(stats.size);
512
- const readable = this._readable.openFileSync(path, parseFlag('r'), cred);
505
+ const readable = this.readable.openFileSync(path, parseFlag('r'), cred);
513
506
  readable.readSync(data);
514
507
  readable.closeSync();
515
- const writable = this._writable.openFileSync(path, parseFlag('w'), cred);
508
+ const writable = this.writable.openFileSync(path, parseFlag('w'), cred);
516
509
  writable.writeSync(data);
517
510
  writable.closeSync();
518
511
  }
@@ -520,15 +513,15 @@ export class UnlockedOverlayFS extends FileSystem {
520
513
  private async copyToWritable(path: string, cred: Cred): Promise<void> {
521
514
  const stats = await this.stat(path, cred);
522
515
  if (stats.isDirectory()) {
523
- await this._writable.mkdir(path, stats.mode, cred);
516
+ await this.writable.mkdir(path, stats.mode, cred);
524
517
  return;
525
518
  }
526
519
 
527
520
  const data = new Uint8Array(stats.size);
528
- const readable = await this._readable.openFile(path, parseFlag('r'), cred);
521
+ const readable = await this.readable.openFile(path, parseFlag('r'), cred);
529
522
  await readable.read(data);
530
523
  await readable.close();
531
- const writable = await this._writable.openFile(path, parseFlag('w'), cred);
524
+ const writable = await this.writable.openFile(path, parseFlag('w'), cred);
532
525
  await writable.write(data);
533
526
  await writable.close();
534
527
  }
@@ -540,32 +533,9 @@ export class UnlockedOverlayFS extends FileSystem {
540
533
  * file system.
541
534
  * @internal
542
535
  */
543
- export class OverlayFS extends LockedFS<UnlockedOverlayFS> {
544
- /**
545
- * @param options The options to initialize the OverlayFS with
546
- */
547
- constructor(options: OverlayOptions) {
548
- super(new UnlockedOverlayFS(options));
549
- }
550
-
551
- public getOverlayedFileSystems(): OverlayOptions {
552
- return super.fs.getOverlayedFileSystems();
553
- }
554
-
555
- public getDeletionLog(): string {
556
- return super.fs.getDeletionLog();
557
- }
558
-
559
- public resDeletionLog(): string {
560
- return super.fs.getDeletionLog();
561
- }
562
-
563
- public unwrap(): UnlockedOverlayFS {
564
- return super.fs;
565
- }
566
- }
536
+ export class OverlayFS extends Mutexed(UnmutexedOverlayFS) {}
567
537
 
568
- export const Overlay = {
538
+ const _Overlay = {
569
539
  name: 'Overlay',
570
540
 
571
541
  options: {
@@ -589,3 +559,7 @@ export const Overlay = {
589
559
  return new OverlayFS(options);
590
560
  },
591
561
  } as const satisfies Backend<OverlayFS, OverlayOptions>;
562
+ type _overlay = typeof _Overlay;
563
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
564
+ interface Overlay extends _overlay {}
565
+ export const Overlay: Overlay = _Overlay;
@@ -4,7 +4,8 @@ import type { ExtractProperties } from 'utilium';
4
4
  import type { Cred } from '../../cred.js';
5
5
  import { Errno, ErrnoError } from '../../error.js';
6
6
  import { File } from '../../file.js';
7
- import { Async, FileSystem, type FileSystemMetadata } from '../../filesystem.js';
7
+ import { FileSystem, type FileSystemMetadata } from '../../filesystem.js';
8
+ import { Async } from '../../mixins/async.js';
8
9
  import { Stats, type FileType } from '../../stats.js';
9
10
  import { InMemory } from '../memory.js';
10
11
  import type { Backend, FilesystemOf } from '../backend.js';
@@ -285,7 +286,7 @@ export function detachFS(port: RPC.Port, fs: FileSystem): void {
285
286
  RPC.detach<FileOrFSRequest>(port, request => handleRequest(port, fs, request));
286
287
  }
287
288
 
288
- export const Port = {
289
+ export const _Port = {
289
290
  name: 'Port',
290
291
 
291
292
  options: {
@@ -315,6 +316,10 @@ export const Port = {
315
316
  return new PortFS(options);
316
317
  },
317
318
  } satisfies Backend<PortFS, RPC.Options>;
319
+ type _port = typeof _Port;
320
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
321
+ interface Port extends _port {}
322
+ export const Port: Port = _Port;
318
323
 
319
324
  export async function resolveRemoteMount<T extends Backend>(port: RPC.Port, config: MountConfiguration<T>, _depth = 0): Promise<FilesystemOf<T>> {
320
325
  const stopAndReplay = RPC.catchMessages(port);
@@ -1,3 +1,3 @@
1
- # IndexFS
1
+ ## IndexFS
2
2
 
3
3
  The `IndexFS` class is a base class for other backends that uses an `Index` to store stats for files and directories. The `Index` class inherits from `Map` and stores information about files and directories in a file system.