@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.
- package/dist/ApiError.d.ts +4 -3
- package/dist/ApiError.js +1 -1
- package/dist/backends/AsyncStore.d.ts +3 -2
- package/dist/backends/AsyncStore.js +12 -5
- package/dist/backends/InMemory.d.ts +1 -1
- package/dist/backends/Index.d.ts +7 -10
- package/dist/backends/Index.js +7 -5
- package/dist/backends/Overlay.js +1 -1
- package/dist/backends/SyncStore.d.ts +6 -6
- package/dist/backends/SyncStore.js +4 -4
- package/dist/backends/backend.d.ts +5 -4
- package/dist/backends/backend.js +2 -2
- package/dist/browser.min.js +4 -4
- package/dist/browser.min.js.map +3 -3
- package/dist/config.d.ts +1 -1
- package/dist/config.js +2 -2
- package/dist/emulation/async.d.ts +76 -77
- package/dist/emulation/async.js +42 -42
- package/dist/emulation/dir.js +6 -5
- package/dist/emulation/promises.d.ts +106 -102
- package/dist/emulation/promises.js +61 -65
- package/dist/emulation/shared.d.ts +1 -7
- package/dist/emulation/shared.js +1 -1
- package/dist/emulation/streams.js +3 -2
- package/dist/emulation/sync.d.ts +71 -64
- package/dist/emulation/sync.js +39 -40
- package/dist/file.d.ts +4 -4
- package/dist/file.js +7 -5
- package/dist/filesystem.d.ts +1 -1
- package/dist/filesystem.js +3 -0
- package/dist/mutex.js +2 -2
- package/dist/stats.d.ts +7 -7
- package/dist/stats.js +50 -10
- package/dist/utils.d.ts +5 -5
- package/dist/utils.js +4 -3
- package/package.json +3 -3
- package/readme.md +2 -2
- package/src/ApiError.ts +3 -1
- package/src/backends/AsyncStore.ts +14 -8
- package/src/backends/Index.ts +14 -10
- package/src/backends/Overlay.ts +3 -3
- package/src/backends/SyncStore.ts +8 -8
- package/src/backends/backend.ts +7 -5
- package/src/config.ts +5 -5
- package/src/emulation/async.ts +188 -196
- package/src/emulation/dir.ts +6 -6
- package/src/emulation/promises.ts +181 -173
- package/src/emulation/shared.ts +2 -9
- package/src/emulation/streams.ts +9 -8
- package/src/emulation/sync.ts +159 -159
- package/src/file.ts +11 -9
- package/src/filesystem.ts +11 -7
- package/src/mutex.ts +3 -3
- package/src/stats.ts +32 -23
- package/src/utils.ts +10 -9
- 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
|
|
52
|
+
abstract position: number;
|
|
53
53
|
/**
|
|
54
54
|
* The path to the file
|
|
55
55
|
*/
|
|
56
|
-
abstract readonly path
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
/**
|
package/dist/filesystem.d.ts
CHANGED
|
@@ -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 =
|
|
4
|
+
export type FileContents = ArrayBufferView | string;
|
|
5
5
|
/**
|
|
6
6
|
* Metadata about a FileSystem
|
|
7
7
|
*/
|
package/dist/filesystem.js
CHANGED
|
@@ -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)
|
|
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"] =
|
|
8
|
-
FileType[FileType["DIRECTORY"] =
|
|
9
|
-
FileType[FileType["SYMLINK"] =
|
|
10
|
-
})(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 =
|
|
94
|
-
this.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:
|
|
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
|
|
77
|
+
export declare function normalizeOptions(options: WriteFileOptions | (EncodingOption & {
|
|
78
78
|
flag?: OpenMode;
|
|
79
|
-
}), encoding
|
|
80
|
-
encoding
|
|
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.
|
|
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.
|
|
69
|
-
"typescript": "^4.
|
|
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
|
|
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
|
|
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 |
|
|
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
|
|
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
|
|
150
|
+
if (this._options.lruCacheSize) {
|
|
145
151
|
this._cache = new LRUCache(this._options.lruCacheSize);
|
|
146
152
|
}
|
|
147
|
-
this.
|
|
148
|
-
this._sync = this._options.sync ||
|
|
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
|
|
186
|
-
c
|
|
191
|
+
delete this._cache;
|
|
192
|
+
c?.reset();
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
try {
|
package/src/backends/Index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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> |
|
|
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[] |
|
|
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> |
|
|
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> |
|
|
347
|
+
public remove(p: string): IndexInode<TData> | void {
|
|
344
348
|
const item = this._listing.get(p);
|
|
345
349
|
if (!item) {
|
|
346
350
|
return;
|
package/src/backends/Overlay.ts
CHANGED
|
@@ -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
|
|
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 |
|
|
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 |
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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',
|
|
448
|
+
throw ApiError.With('ENOENT', path, 'getINode');
|
|
449
449
|
}
|
|
450
450
|
const inode = new Inode(data.buffer);
|
|
451
451
|
return inode;
|
package/src/backends/backend.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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<
|
|
144
|
-
backend:
|
|
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
|
|
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
|
|
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 ?
|
|
85
|
-
const gid = 'gid' in config ?
|
|
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
|