@zenfs/core 0.9.6 → 0.9.7

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 (56) hide show
  1. package/dist/ApiError.d.ts +4 -3
  2. package/dist/ApiError.js +1 -1
  3. package/dist/backends/AsyncStore.d.ts +3 -2
  4. package/dist/backends/AsyncStore.js +12 -5
  5. package/dist/backends/InMemory.d.ts +1 -1
  6. package/dist/backends/Index.d.ts +7 -10
  7. package/dist/backends/Index.js +7 -5
  8. package/dist/backends/Overlay.js +1 -1
  9. package/dist/backends/SyncStore.d.ts +6 -6
  10. package/dist/backends/SyncStore.js +4 -4
  11. package/dist/backends/backend.d.ts +5 -4
  12. package/dist/backends/backend.js +2 -2
  13. package/dist/browser.min.js +4 -4
  14. package/dist/browser.min.js.map +3 -3
  15. package/dist/config.d.ts +1 -1
  16. package/dist/config.js +2 -2
  17. package/dist/emulation/async.d.ts +76 -77
  18. package/dist/emulation/async.js +42 -42
  19. package/dist/emulation/dir.js +6 -5
  20. package/dist/emulation/promises.d.ts +106 -102
  21. package/dist/emulation/promises.js +61 -65
  22. package/dist/emulation/shared.d.ts +1 -7
  23. package/dist/emulation/shared.js +1 -1
  24. package/dist/emulation/streams.js +3 -2
  25. package/dist/emulation/sync.d.ts +71 -64
  26. package/dist/emulation/sync.js +39 -40
  27. package/dist/file.d.ts +4 -4
  28. package/dist/file.js +7 -5
  29. package/dist/filesystem.d.ts +1 -1
  30. package/dist/filesystem.js +3 -0
  31. package/dist/mutex.js +2 -2
  32. package/dist/stats.d.ts +7 -7
  33. package/dist/stats.js +50 -10
  34. package/dist/utils.d.ts +5 -5
  35. package/dist/utils.js +4 -3
  36. package/package.json +3 -3
  37. package/readme.md +2 -2
  38. package/src/ApiError.ts +3 -1
  39. package/src/backends/AsyncStore.ts +14 -8
  40. package/src/backends/Index.ts +14 -10
  41. package/src/backends/Overlay.ts +3 -3
  42. package/src/backends/SyncStore.ts +8 -8
  43. package/src/backends/backend.ts +7 -5
  44. package/src/config.ts +5 -5
  45. package/src/emulation/async.ts +188 -196
  46. package/src/emulation/dir.ts +6 -6
  47. package/src/emulation/promises.ts +181 -173
  48. package/src/emulation/shared.ts +2 -9
  49. package/src/emulation/streams.ts +9 -8
  50. package/src/emulation/sync.ts +159 -159
  51. package/src/file.ts +11 -9
  52. package/src/filesystem.ts +11 -7
  53. package/src/mutex.ts +3 -3
  54. package/src/stats.ts +32 -23
  55. package/src/utils.ts +10 -9
  56. package/tsconfig.json +2 -1
package/dist/file.d.ts CHANGED
@@ -49,11 +49,11 @@ export declare abstract class File {
49
49
  /**
50
50
  * Get the current file position.
51
51
  */
52
- abstract position?: number;
52
+ abstract position: number;
53
53
  /**
54
54
  * The path to the file
55
55
  */
56
- abstract readonly path?: string;
56
+ abstract readonly path: string;
57
57
  /**
58
58
  * Asynchronous `stat`.
59
59
  */
@@ -99,7 +99,7 @@ export declare abstract class File {
99
99
  * the current position.
100
100
  * @returns Promise resolving to the new length of the buffer
101
101
  */
102
- abstract write(buffer: Uint8Array, offset?: number, length?: number, position?: number): Promise<number>;
102
+ abstract write(buffer: Uint8Array, offset?: number, length?: number, position?: number | null): Promise<number>;
103
103
  /**
104
104
  * Write buffer to the file.
105
105
  * Note that it is unsafe to use fs.writeSync multiple times on the same file
@@ -112,7 +112,7 @@ export declare abstract class File {
112
112
  * data should be written. If position is null, the data will be written at
113
113
  * the current position.
114
114
  */
115
- abstract writeSync(buffer: Uint8Array, offset?: number, length?: number, position?: number): number;
115
+ abstract writeSync(buffer: Uint8Array, offset?: number, length?: number, position?: number | null): number;
116
116
  /**
117
117
  * Read data from the file.
118
118
  * @param buffer The buffer that the data will be
package/dist/file.js CHANGED
@@ -15,7 +15,7 @@ export var ActionType;
15
15
  ActionType[ActionType["TRUNCATE"] = 2] = "TRUNCATE";
16
16
  // Indicates that the code should create the file.
17
17
  ActionType[ActionType["CREATE"] = 3] = "CREATE";
18
- })(ActionType = ActionType || (ActionType = {}));
18
+ })(ActionType || (ActionType = {}));
19
19
  const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+'];
20
20
  export function parseFlag(flag) {
21
21
  if (typeof flag === 'number') {
@@ -263,7 +263,7 @@ export class PreloadFile extends File {
263
263
  * Asynchronous truncate.
264
264
  * @param len
265
265
  */
266
- truncate(len) {
266
+ async truncate(len) {
267
267
  this.truncateSync(len);
268
268
  if (isSynchronous(this.flag)) {
269
269
  return this.sync();
@@ -308,7 +308,9 @@ export class PreloadFile extends File {
308
308
  * the current position.
309
309
  */
310
310
  async write(buffer, offset = 0, length = this.stats.size, position = 0) {
311
- return this.writeSync(buffer, offset, length, position);
311
+ const bytesWritten = this.writeSync(buffer, offset, length, position);
312
+ await this.sync();
313
+ return bytesWritten;
312
314
  }
313
315
  /**
314
316
  * Write buffer to the file.
@@ -380,7 +382,7 @@ export class PreloadFile extends File {
380
382
  * position.
381
383
  * @returns number of bytes written
382
384
  */
383
- readSync(buffer, offset = 0, length = this.stats.size, position = 0) {
385
+ readSync(buffer, offset = 0, length = this.stats.size, position) {
384
386
  if (!isReadable(this.flag)) {
385
387
  throw new ApiError(ErrorCode.EPERM, 'File not opened with a readable mode.');
386
388
  }
@@ -396,7 +398,7 @@ export class PreloadFile extends File {
396
398
  // No copy/read. Return immediatly for better performance
397
399
  return bytesRead;
398
400
  }
399
- new Uint8Array(buffer.buffer).set(this._buffer.slice(position, end), offset);
401
+ new Uint8Array(buffer.buffer, 0, length).set(this._buffer.slice(position, end), offset);
400
402
  return bytesRead;
401
403
  }
402
404
  /**
@@ -1,7 +1,7 @@
1
1
  import { type Cred } from './cred.js';
2
2
  import { type File } from './file.js';
3
3
  import type { Stats } from './stats.js';
4
- export type FileContents = Uint8Array | string;
4
+ export type FileContents = ArrayBufferView | string;
5
5
  /**
6
6
  * Metadata about a FileSystem
7
7
  */
@@ -58,6 +58,7 @@ export class FileSystem {
58
58
  /**
59
59
  * Implements the asynchronous API in terms of the synchronous API.
60
60
  */
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
62
  export function Sync(FS) {
62
63
  class _SyncFileSystem extends FS {
63
64
  async exists(path, cred) {
@@ -108,6 +109,7 @@ export function Sync(FS) {
108
109
  * the synchronous store, if desired.
109
110
  *
110
111
  */
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
113
  export function Async(FS) {
112
114
  class _AsyncFileSystem extends FS {
113
115
  constructor() {
@@ -241,6 +243,7 @@ export function Async(FS) {
241
243
  /**
242
244
  * Implements the non-readonly methods to throw `EROFS`
243
245
  */
246
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
244
247
  export function Readonly(FS) {
245
248
  class _ReadonlyFileSystem extends FS {
246
249
  metadata() {
package/dist/mutex.js CHANGED
@@ -12,7 +12,7 @@ export class Mutex {
12
12
  this._locks.get(path).push(resolve);
13
13
  }
14
14
  else {
15
- this._locks.set(path, []);
15
+ this._locks.set(path, [resolve]);
16
16
  }
17
17
  });
18
18
  }
@@ -20,7 +20,7 @@ export class Mutex {
20
20
  if (!this._locks.has(path)) {
21
21
  throw new Error('unlock of a non-locked mutex');
22
22
  }
23
- const next = this._locks.get(path).shift();
23
+ const next = this._locks.get(path)?.shift();
24
24
  /*
25
25
  don't unlock - we want to queue up next for the
26
26
  end of the current task execution, but we don't
package/dist/stats.d.ts CHANGED
@@ -5,9 +5,9 @@ import { Cred } from './cred.js';
5
5
  * Indicates the type of the given file. Applied to 'mode'.
6
6
  */
7
7
  export declare enum FileType {
8
- FILE,
9
- DIRECTORY,
10
- SYMLINK
8
+ FILE = 32768,
9
+ DIRECTORY = 16384,
10
+ SYMLINK = 40960
11
11
  }
12
12
  /**
13
13
  *
@@ -173,6 +173,10 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
173
173
  * @internal
174
174
  */
175
175
  chown(uid: number | bigint, gid: number | bigint): void;
176
+ get atimeNs(): bigint;
177
+ get mtimeNs(): bigint;
178
+ get ctimeNs(): bigint;
179
+ get birthtimeNs(): bigint;
176
180
  }
177
181
  /**
178
182
  * Implementation of Node's `Stats`.
@@ -195,10 +199,6 @@ export declare class Stats extends StatsCommon<number> implements Node.Stats, St
195
199
  */
196
200
  export declare class BigIntStats extends StatsCommon<bigint> implements Node.BigIntStats, StatsLike {
197
201
  protected _isBigint: boolean;
198
- atimeNs: bigint;
199
- mtimeNs: bigint;
200
- ctimeNs: bigint;
201
- birthtimeNs: bigint;
202
202
  /**
203
203
  * Clone a stats object.
204
204
  * @deprecated use `new BigIntStats(stats)`
package/dist/stats.js CHANGED
@@ -4,10 +4,10 @@ import { S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IRWXG, S_IRWXO, S_IRWXU } from './
4
4
  */
5
5
  export var FileType;
6
6
  (function (FileType) {
7
- FileType[FileType["FILE"] = S_IFREG] = "FILE";
8
- FileType[FileType["DIRECTORY"] = S_IFDIR] = "DIRECTORY";
9
- FileType[FileType["SYMLINK"] = S_IFLNK] = "SYMLINK";
10
- })(FileType = FileType || (FileType = {}));
7
+ FileType[FileType["FILE"] = 32768] = "FILE";
8
+ FileType[FileType["DIRECTORY"] = 16384] = "DIRECTORY";
9
+ FileType[FileType["SYMLINK"] = 40960] = "SYMLINK";
10
+ })(FileType || (FileType = {}));
11
11
  /**
12
12
  * Provides information about a particular entry in the file system.
13
13
  * Common code used by both Stats and BigIntStats.
@@ -78,10 +78,6 @@ export class StatsCommon {
78
78
  * group ID of owner
79
79
  */
80
80
  this.gid = this._convert(0);
81
- /**
82
- * Some file systems stash data on stats objects.
83
- */
84
- this.fileData = null;
85
81
  const currentTime = Date.now();
86
82
  const resolveT = (val, _default) => (typeof val == this._typename ? val : this._convert(typeof val == this._typename_inverse ? val : _default));
87
83
  this.atimeMs = resolveT(atimeMs, currentTime);
@@ -90,8 +86,8 @@ export class StatsCommon {
90
86
  this.birthtimeMs = resolveT(birthtimeMs, currentTime);
91
87
  this.uid = resolveT(uid, 0);
92
88
  this.gid = resolveT(gid, 0);
93
- this.size = this._convert(size);
94
- this.ino = this._convert(ino);
89
+ this.size = resolveT(size, 0);
90
+ this.ino = resolveT(ino, 0);
95
91
  const itemType = Number(mode) & S_IFMT || FileType.FILE;
96
92
  if (mode) {
97
93
  this.mode = this._convert(mode);
@@ -197,6 +193,18 @@ export class StatsCommon {
197
193
  this.gid = this._convert(gid);
198
194
  }
199
195
  }
196
+ get atimeNs() {
197
+ return BigInt(this.atimeMs);
198
+ }
199
+ get mtimeNs() {
200
+ return BigInt(this.mtimeMs);
201
+ }
202
+ get ctimeNs() {
203
+ return BigInt(this.ctimeMs);
204
+ }
205
+ get birthtimeNs() {
206
+ return BigInt(this.birthtimeMs);
207
+ }
200
208
  }
201
209
  /**
202
210
  * Implementation of Node's `Stats`.
@@ -237,6 +245,38 @@ export class BigIntStats extends StatsCommon {
237
245
  }
238
246
  }
239
247
  export class StatsFs {
248
+ constructor() {
249
+ /** Type of file system. */
250
+ this.type = 0;
251
+ /** Optimal transfer block size. */
252
+ this.bsize = 0;
253
+ /** Total data blocks in file system. */
254
+ this.blocks = 0;
255
+ /** Free blocks in file system. */
256
+ this.bfree = 0;
257
+ /** Available blocks for unprivileged users */
258
+ this.bavail = 0;
259
+ /** Total file nodes in file system. */
260
+ this.files = 0;
261
+ /** Free file nodes in file system. */
262
+ this.ffree = 0;
263
+ }
240
264
  }
241
265
  export class BigIntStatsFs {
266
+ constructor() {
267
+ /** Type of file system. */
268
+ this.type = 0n;
269
+ /** Optimal transfer block size. */
270
+ this.bsize = 0n;
271
+ /** Total data blocks in file system. */
272
+ this.blocks = 0n;
273
+ /** Free blocks in file system. */
274
+ this.bfree = 0n;
275
+ /** Available blocks for unprivileged users */
276
+ this.bavail = 0n;
277
+ /** Total file nodes in file system. */
278
+ this.files = 0n;
279
+ /** Free file nodes in file system. */
280
+ this.ffree = 0n;
281
+ }
242
282
  }
package/dist/utils.d.ts CHANGED
@@ -43,7 +43,7 @@ export declare function decodeDirListing(data: Uint8Array): Record<string, bigin
43
43
  */
44
44
  export declare function encodeDirListing(data: Record<string, bigint>): Uint8Array;
45
45
  export type Callback<Args extends unknown[] = []> = (e?: ApiError, ...args: OptionalTuple<Args>) => unknown;
46
- import type { EncodingOption, OpenMode, WriteFileOptions } from 'node:fs';
46
+ import type { EncodingOption, OpenMode, PathLike, WriteFileOptions } from 'node:fs';
47
47
  /**
48
48
  * converts Date or number to a integer UNIX timestamp
49
49
  * Grabbed from NodeJS sources (lib/fs.js)
@@ -65,7 +65,7 @@ export declare function normalizeTime(time: string | number | Date): Date;
65
65
  * Normalizes a path
66
66
  * @internal
67
67
  */
68
- export declare function normalizePath(p: string): string;
68
+ export declare function normalizePath(p: PathLike): string;
69
69
  /**
70
70
  * Normalizes options
71
71
  * @param options options to normalize
@@ -74,10 +74,10 @@ export declare function normalizePath(p: string): string;
74
74
  * @param mode default mode
75
75
  * @internal
76
76
  */
77
- export declare function normalizeOptions(options?: WriteFileOptions | (EncodingOption & {
77
+ export declare function normalizeOptions(options: WriteFileOptions | (EncodingOption & {
78
78
  flag?: OpenMode;
79
- }), encoding?: BufferEncoding, flag?: string, mode?: number): {
80
- encoding: BufferEncoding;
79
+ }) | undefined, encoding: BufferEncoding | null | undefined, flag: string, mode?: number): {
80
+ encoding?: BufferEncoding | null;
81
81
  flag: string;
82
82
  mode: number;
83
83
  };
package/dist/utils.js CHANGED
@@ -86,7 +86,7 @@ export function levenshtein(a, b) {
86
86
  /**
87
87
  * @hidden
88
88
  */
89
- export const setImmediate = typeof globalThis.setImmediate == 'function' ? globalThis.setImmediate : cb => setTimeout(cb, 0);
89
+ export const setImmediate = typeof globalThis.setImmediate == 'function' ? globalThis.setImmediate : (cb) => setTimeout(cb, 0);
90
90
  /**
91
91
  * Encodes a string into a buffer
92
92
  * @internal
@@ -188,6 +188,7 @@ export function normalizeTime(time) {
188
188
  * @internal
189
189
  */
190
190
  export function normalizePath(p) {
191
+ p = p.toString();
191
192
  // Node doesn't allow null characters in paths.
192
193
  if (p.includes('\x00')) {
193
194
  throw new ApiError(ErrorCode.EINVAL, 'Path must be a string without null bytes.');
@@ -208,13 +209,13 @@ export function normalizePath(p) {
208
209
  export function normalizeOptions(options, encoding = 'utf8', flag, mode = 0) {
209
210
  if (typeof options != 'object' || options === null) {
210
211
  return {
211
- encoding: typeof options == 'string' ? options : encoding,
212
+ encoding: typeof options == 'string' ? options : encoding ?? null,
212
213
  flag,
213
214
  mode,
214
215
  };
215
216
  }
216
217
  return {
217
- encoding: typeof options?.encoding == 'string' ? options.encoding : encoding,
218
+ encoding: typeof options?.encoding == 'string' ? options.encoding : encoding ?? null,
218
219
  flag: typeof options?.flag == 'string' ? options.flag : flag,
219
220
  mode: normalizeMode('mode' in options ? options?.mode : null, mode),
220
221
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
4
4
  "description": "A filesystem in your browser",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.ts",
@@ -65,7 +65,7 @@
65
65
  "jest": "^29.5.0",
66
66
  "prettier": "^3.2.5",
67
67
  "ts-jest": "^29.1.0",
68
- "typedoc": "^0.25.1",
69
- "typescript": "^4.9.5"
68
+ "typedoc": "^0.25.13",
69
+ "typescript": "^5.4.0"
70
70
  }
71
71
  }
package/readme.md CHANGED
@@ -4,7 +4,7 @@ ZenFS is a file system that emulates the [NodeJS filesystem API](http://nodejs.o
4
4
 
5
5
  It works using a system of backends, which are used by ZenFS to store and retrieve data. ZenFS can also integrate with other tools.
6
6
 
7
- ZenFS is a fork of [BrowserFS](https://github.com/jvilk/BrowserFS).
7
+ ZenFS is a fork of [BrowserFS](https://github.com/jvilk/BrowserFS). If you are using ZenFS in a research paper, you may want to [cite BrowserFS](https://github.com/jvilk/BrowserFS?tab=readme-ov-file#citing).
8
8
 
9
9
  ## Backends
10
10
 
@@ -13,7 +13,7 @@ ZenFS is modular and extensible. The core includes two built-in backends:
13
13
  - `InMemory`: Stores files in-memory. This is cleared when the runtime ends (e.g. a user navigating away from a web page or a Node process exiting)
14
14
  - `Overlay`: Use read-only file system as read-write by overlaying a writable file system on top of it. ([copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write))
15
15
 
16
- ZenFS supports a number of other backends. Many are provided as separate packages under `@zenfs`. More backends can be defined by separate libraries by extending the `FileSystem` class and/or providing a `Backend` object.
16
+ ZenFS supports a number of other backends. Many are provided as separate packages under `@zenfs`. More backends can be defined by separate libraries by extending the `FileSystem` class and providing a `Backend` object.
17
17
 
18
18
  For more information, see the [docs](https://zen-fs.github.io/core).
19
19
 
package/src/ApiError.ts CHANGED
@@ -255,12 +255,14 @@ export class ApiError extends Error implements NodeJS.ErrnoException {
255
255
  return err;
256
256
  }
257
257
 
258
- public static With(code: keyof typeof ErrorCode, path: string, syscall?: string): ApiError {
258
+ public static With(code: keyof typeof ErrorCode, path?: string, syscall?: string): ApiError {
259
259
  return new ApiError(ErrorCode[code], errorMessages[ErrorCode[code]], path, syscall);
260
260
  }
261
261
 
262
262
  public code: keyof typeof ErrorCode;
263
263
 
264
+ public declare stack: string;
265
+
264
266
  /**
265
267
  * Represents a ZenFS error. Passed back to applications after a failed
266
268
  * call to the ZenFS API.
@@ -34,7 +34,7 @@ class LRUCache<K, V> {
34
34
  this.cache.push({ key, value });
35
35
  }
36
36
 
37
- public get(key: K): V | null {
37
+ public get(key: K): V | void {
38
38
  const node = this.cache.find(n => n.key === key);
39
39
  if (!node) {
40
40
  return;
@@ -131,21 +131,27 @@ export interface AsyncStoreOptions {
131
131
  * @internal
132
132
  */
133
133
  export class AsyncStoreFS extends Async(FileSystem) {
134
- protected store: AsyncStore;
134
+ protected _store?: AsyncStore;
135
+ protected get store(): AsyncStore {
136
+ if (!this._store) {
137
+ throw new ReferenceError('AsyncStoreFS not attached to a store');
138
+ }
139
+ return this._store;
140
+ }
135
141
  protected _cache?: LRUCache<string, Ino>;
136
142
  private _initialized: boolean = false;
137
- _sync: FileSystem;
143
+ _sync: FileSystem = InMemory.create({ name: 'test' });
138
144
 
139
145
  public async ready(): Promise<this> {
140
146
  if (this._initialized) {
141
147
  return this;
142
148
  }
143
149
  this._initialized = true;
144
- if (this._options.lruCacheSize > 0) {
150
+ if (this._options.lruCacheSize) {
145
151
  this._cache = new LRUCache(this._options.lruCacheSize);
146
152
  }
147
- this.store = await this._options.store;
148
- this._sync = this._options.sync || InMemory.create({ name: 'test' });
153
+ this._store = await this._options.store;
154
+ this._sync = this._options.sync || this._sync;
149
155
  await this.makeRootDirectory();
150
156
  await super.ready();
151
157
  return this;
@@ -182,8 +188,8 @@ export class AsyncStoreFS extends Async(FileSystem) {
182
188
  const c = this._cache;
183
189
  if (this._cache) {
184
190
  // Clear and disable cache during renaming process.
185
- this._cache = null;
186
- c.reset();
191
+ delete this._cache;
192
+ c?.reset();
187
193
  }
188
194
 
189
195
  try {
@@ -86,7 +86,10 @@ export class FileIndex<TData> {
86
86
  for (const dir of this._index.values()) {
87
87
  for (const file of dir.listing) {
88
88
  const item = dir.get(file);
89
- if (!item?.isFile()) {
89
+ if (!item) {
90
+ continue;
91
+ }
92
+ if (!item.isFile()) {
90
93
  continue;
91
94
  }
92
95
 
@@ -123,11 +126,12 @@ export class FileIndex<TData> {
123
126
 
124
127
  const dirpath = dirname(path);
125
128
 
129
+ const hasParent = this._index.has(dirpath);
130
+
131
+ const parent = hasParent ? this._index.get(dirpath)! : new IndexDirInode<TData>();
132
+
126
133
  // Try to add to its parent directory first.
127
- let parent = this._index.get(dirpath);
128
- if (!parent && path != '/') {
129
- // Create parent.
130
- parent = new IndexDirInode<TData>();
134
+ if (!hasParent && path != '/') {
131
135
  if (!this.add(dirpath, parent)) {
132
136
  return false;
133
137
  }
@@ -184,7 +188,7 @@ export class FileIndex<TData> {
184
188
  * @return The removed item,
185
189
  * or null if it did not exist.
186
190
  */
187
- public remove(path: string): IndexInode<TData> | null {
191
+ public remove(path: string): IndexInode<TData> | void {
188
192
  const dirpath = dirname(path);
189
193
 
190
194
  // Try to remove it from its parent directory first.
@@ -217,7 +221,7 @@ export class FileIndex<TData> {
217
221
  * Retrieves the directory listing of the given path.
218
222
  * @return An array of files in the given path, or 'null' if it does not exist.
219
223
  */
220
- public ls(path: string): string[] | null {
224
+ public ls(path: string): string[] | void {
221
225
  return this._index.get(path)?.listing;
222
226
  }
223
227
 
@@ -225,7 +229,7 @@ export class FileIndex<TData> {
225
229
  * Returns the inode of the given item.
226
230
  * @return Returns null if the item does not exist.
227
231
  */
228
- public get(path: string): IndexInode<TData> | null {
232
+ public get(path: string): IndexInode<TData> | void | null {
229
233
  const dirpath = dirname(path);
230
234
 
231
235
  // Retrieve from its parent directory.
@@ -316,7 +320,7 @@ export class IndexDirInode<TData> extends IndexInode<TData> {
316
320
  * Returns the inode for the indicated item, or null if it does not exist.
317
321
  * @param path Name of item in this directory.
318
322
  */
319
- public get(path: string): IndexInode<TData> | null {
323
+ public get(path: string): IndexInode<TData> | void {
320
324
  return this._listing.get(path);
321
325
  }
322
326
  /**
@@ -340,7 +344,7 @@ export class IndexDirInode<TData> extends IndexInode<TData> {
340
344
  * @return Returns the item
341
345
  * removed, or null if the item did not exist.
342
346
  */
343
- public remove(p: string): IndexInode<TData> | null {
347
+ public remove(p: string): IndexInode<TData> | void {
344
348
  const item = this._listing.get(p);
345
349
  if (!item) {
346
350
  return;
@@ -109,7 +109,7 @@ export class UnlockedOverlayFS extends FileSystem {
109
109
  const { buffer } = await file.read(new Uint8Array(size));
110
110
  this._deleteLog = decode(buffer);
111
111
  } catch (err) {
112
- if (err.errno !== ErrorCode.ENOENT) {
112
+ if ((err as ApiError).errno !== ErrorCode.ENOENT) {
113
113
  throw err;
114
114
  }
115
115
  }
@@ -394,7 +394,7 @@ export class UnlockedOverlayFS extends FileSystem {
394
394
  this.updateLog('', cred);
395
395
  }
396
396
  } catch (e) {
397
- this._deleteLogError = e;
397
+ this._deleteLogError = e as ApiError;
398
398
  } finally {
399
399
  this._deleteLogUpdatePending = false;
400
400
  }
@@ -423,7 +423,7 @@ export class UnlockedOverlayFS extends FileSystem {
423
423
  }
424
424
 
425
425
  const error = this._deleteLogError;
426
- this._deleteLogError = null;
426
+ delete this._deleteLogError;
427
427
  throw error;
428
428
  }
429
429
 
@@ -37,7 +37,7 @@ export interface SyncTransaction {
37
37
  * @param ino The key to look under for data.
38
38
  * @return The data stored under the key, or undefined if not present.
39
39
  */
40
- get(ino: Ino): Uint8Array | undefined;
40
+ get(ino: Ino): Uint8Array | void;
41
41
 
42
42
  /**
43
43
  * Adds the data to the store under the given key.
@@ -81,7 +81,7 @@ export class SimpleSyncTransaction implements SyncTransaction {
81
81
  * Stores data in the keys we modify prior to modifying them.
82
82
  * Allows us to roll back commits.
83
83
  */
84
- protected originalData: Map<Ino, Uint8Array> = new Map();
84
+ protected originalData: Map<Ino, Uint8Array | void> = new Map();
85
85
  /**
86
86
  * List of keys modified in this transaction, if any.
87
87
  */
@@ -89,7 +89,7 @@ export class SimpleSyncTransaction implements SyncTransaction {
89
89
 
90
90
  constructor(protected store: SimpleSyncStore) {}
91
91
 
92
- public get(ino: Ino): Uint8Array | undefined {
92
+ public get(ino: Ino): Uint8Array | void {
93
93
  const val = this.store.get(ino);
94
94
  this.stashOldValue(ino, val);
95
95
  return val;
@@ -129,7 +129,7 @@ export class SimpleSyncTransaction implements SyncTransaction {
129
129
  * prevent needless `get` requests if the program modifies the data later
130
130
  * on during the transaction.
131
131
  */
132
- protected stashOldValue(ino: Ino, value: Uint8Array | undefined) {
132
+ protected stashOldValue(ino: Ino, value?: Uint8Array) {
133
133
  // Keep only the earliest value in the transaction.
134
134
  if (!this.originalData.has(ino)) {
135
135
  this.originalData.set(ino, value);
@@ -280,7 +280,7 @@ export class SyncStoreFS extends Sync(FileSystem) {
280
280
  if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
281
281
  throw ApiError.With('EACCES', p, 'openFile');
282
282
  }
283
- if (data === null) {
283
+ if (!data) {
284
284
  throw ApiError.With('ENOENT', p, 'openFile');
285
285
  }
286
286
  return new PreloadFile(this, p, flag, node.toStats(), data);
@@ -439,13 +439,13 @@ export class SyncStoreFS extends Sync(FileSystem) {
439
439
  /**
440
440
  * Given the ID of a node, retrieves the corresponding Inode.
441
441
  * @param tx The transaction to use.
442
- * @param p The corresponding path to the file (used for error messages).
442
+ * @param path The corresponding path to the file (used for error messages).
443
443
  * @param id The ID to look up.
444
444
  */
445
- protected getINode(tx: SyncTransaction, id: Ino, p?: string): Inode {
445
+ protected getINode(tx: SyncTransaction, id: Ino, path?: string): Inode {
446
446
  const data = tx.get(id);
447
447
  if (!data) {
448
- throw ApiError.With('ENOENT', p, 'getINode');
448
+ throw ApiError.With('ENOENT', path, 'getINode');
449
449
  }
450
450
  const inode = new Inode(data.buffer);
451
451
  return inode;
@@ -66,6 +66,8 @@ export interface Backend<FS extends FileSystem = FileSystem, TOptions extends ob
66
66
  isAvailable(): boolean | Promise<boolean>;
67
67
  }
68
68
 
69
+ type OptionsOf<T extends Backend> = T extends Backend<FileSystem, infer TOptions> ? TOptions : never;
70
+
69
71
  /**
70
72
  * @internal
71
73
  */
@@ -77,7 +79,7 @@ export function isBackend(arg: unknown): arg is Backend {
77
79
  * Checks that the given options object is valid for the file system options.
78
80
  * @internal
79
81
  */
80
- export async function checkOptions<T extends Backend>(backend: T, opts: object): Promise<void> {
82
+ export async function checkOptions<T extends Backend>(backend: T, opts: Partial<OptionsOf<T>> & Record<string, unknown>): Promise<void> {
81
83
  if (typeof opts != 'object' || opts === null) {
82
84
  throw new ApiError(ErrorCode.EINVAL, 'Invalid options');
83
85
  }
@@ -126,8 +128,8 @@ export async function checkOptions<T extends Backend>(backend: T, opts: object):
126
128
  }
127
129
  }
128
130
 
129
- export function createBackend<B extends Backend>(backend: B, options?: object): Promise<ReturnType<B['create']>> {
130
- checkOptions(backend, options);
131
+ export async function createBackend<B extends Backend>(backend: B, options: Partial<OptionsOf<B>> = {}): Promise<ReturnType<B['create']>> {
132
+ await checkOptions(backend, options);
131
133
  const fs = <ReturnType<B['create']>>backend.create(options);
132
134
  return fs.ready();
133
135
  }
@@ -140,8 +142,8 @@ export function createBackend<B extends Backend>(backend: B, options?: object):
140
142
  *
141
143
  * The option object for each file system corresponds to that file system's option object passed to its `Create()` method.
142
144
  */
143
- export type BackendConfiguration<FS extends FileSystem = FileSystem, TOptions extends object = object> = TOptions & {
144
- backend: Backend<FS, TOptions>;
145
+ export type BackendConfiguration<T extends Backend = Backend> = OptionsOf<T> & {
146
+ backend: T;
145
147
  };
146
148
 
147
149
  /**
package/src/config.ts CHANGED
@@ -8,7 +8,7 @@ import { FileSystem } from './filesystem.js';
8
8
  /**
9
9
  * Configuration for a specific mount point
10
10
  */
11
- export type MountConfiguration<FS extends FileSystem = FileSystem, TOptions extends object = object> = FS | BackendConfiguration<FS, TOptions> | Backend<FS, TOptions>;
11
+ export type MountConfiguration<FS extends FileSystem = FileSystem, TOptions extends object = object> = FS | BackendConfiguration<Backend<FS, TOptions>> | Backend<FS, TOptions>;
12
12
 
13
13
  function isMountConfig(arg: unknown): arg is MountConfiguration {
14
14
  return isBackendConfig(arg) || isBackend(arg) || arg instanceof FileSystem;
@@ -32,7 +32,7 @@ export async function resolveMountConfig<FS extends FileSystem, TOptions extends
32
32
  }
33
33
 
34
34
  if (isBackend(config)) {
35
- config = <BackendConfiguration<FS, TOptions>>{ backend: config };
35
+ config = <BackendConfiguration<Backend<FS, TOptions>>>{ backend: config };
36
36
  }
37
37
 
38
38
  for (const [key, value] of Object.entries(config)) {
@@ -48,7 +48,7 @@ export async function resolveMountConfig<FS extends FileSystem, TOptions extends
48
48
  throw new ApiError(ErrorCode.EINVAL, 'Invalid configuration, too deep and possibly infinite');
49
49
  }
50
50
 
51
- config[key] = await resolveMountConfig(value, ++_depth);
51
+ (<Record<string, FileSystem>>config)[key] = await resolveMountConfig(value, ++_depth);
52
52
  }
53
53
 
54
54
  const { backend } = config;
@@ -81,8 +81,8 @@ export type Configuration = MountConfiguration | MappingConfiguration;
81
81
  * @see Configuration for more info on the configuration object.
82
82
  */
83
83
  export async function configure(config: Configuration): Promise<void> {
84
- const uid = 'uid' in config ? +config.uid || 0 : 0;
85
- const gid = 'gid' in config ? +config.gid || 0 : 0;
84
+ const uid = 'uid' in config ? config.uid || 0 : 0;
85
+ const gid = 'gid' in config ? config.gid || 0 : 0;
86
86
 
87
87
  if (isMountConfig(config)) {
88
88
  // single FS