@zenfs/core 1.7.2 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backends/backend.js +3 -4
- package/dist/backends/fetch.d.ts +17 -18
- package/dist/backends/fetch.js +95 -58
- package/dist/backends/index.d.ts +2 -1
- package/dist/backends/index.js +2 -1
- package/dist/backends/memory.d.ts +1 -1
- package/dist/backends/overlay.d.ts +8 -14
- package/dist/backends/overlay.js +38 -31
- package/dist/backends/passthrough.d.ts +8 -3
- package/dist/backends/passthrough.js +148 -4
- package/dist/backends/port/fs.d.ts +15 -49
- package/dist/backends/port/fs.js +28 -116
- package/dist/backends/port/rpc.d.ts +13 -6
- package/dist/backends/port/rpc.js +9 -7
- package/dist/backends/store/file_index.d.ts +38 -0
- package/dist/backends/store/file_index.js +76 -0
- package/dist/backends/store/fs.d.ts +39 -34
- package/dist/backends/store/fs.js +407 -238
- package/dist/backends/store/index_fs.d.ts +34 -0
- package/dist/backends/store/index_fs.js +67 -0
- package/dist/backends/store/inode.d.ts +26 -8
- package/dist/backends/store/inode.js +92 -91
- package/dist/backends/store/simple.d.ts +20 -20
- package/dist/backends/store/simple.js +3 -4
- package/dist/backends/store/store.d.ts +12 -12
- package/dist/backends/store/store.js +4 -6
- package/dist/devices.d.ts +44 -21
- package/dist/devices.js +110 -55
- package/dist/file.d.ts +111 -7
- package/dist/file.js +324 -92
- package/dist/filesystem.d.ts +44 -4
- package/dist/mixins/async.js +12 -6
- package/dist/mixins/mutexed.d.ts +8 -3
- package/dist/mixins/mutexed.js +57 -1
- package/dist/mixins/readonly.d.ts +17 -16
- package/dist/mixins/readonly.js +6 -0
- package/dist/mixins/sync.d.ts +1 -1
- package/dist/stats.d.ts +12 -6
- package/dist/stats.js +14 -6
- package/dist/utils.d.ts +23 -3
- package/dist/utils.js +58 -10
- package/dist/vfs/async.js +1 -1
- package/dist/vfs/constants.d.ts +2 -2
- package/dist/vfs/constants.js +2 -2
- package/dist/vfs/dir.js +3 -1
- package/dist/vfs/index.js +4 -1
- package/dist/vfs/promises.js +33 -13
- package/dist/vfs/shared.js +2 -0
- package/dist/vfs/sync.js +25 -13
- package/dist/vfs/types.d.ts +15 -0
- package/eslint.shared.js +1 -0
- package/package.json +2 -3
- package/readme.md +2 -2
- package/scripts/test.js +73 -11
- package/tests/common/mutex.test.ts +1 -1
- package/tests/fetch/run.sh +16 -0
- package/tests/fetch/server.ts +49 -0
- package/tests/fetch/setup.ts +13 -0
- package/tests/fs/read.test.ts +10 -10
- package/tests/fs/times.test.ts +2 -2
- package/tests/fs/write.test.ts +6 -11
- package/tests/setup/index.ts +38 -0
- package/tests/setup/port.ts +15 -0
- package/dist/backends/file_index.d.ts +0 -63
- package/dist/backends/file_index.js +0 -163
- package/tests/common/async.test.ts +0 -31
- package/tests/setup/cow+fetch.ts +0 -45
- /package/tests/fs/{appendFile.test.ts → append.test.ts} +0 -0
|
@@ -1,7 +1,59 @@
|
|
|
1
|
-
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
2
53
|
import { ErrnoError } from '../error.js';
|
|
3
|
-
import { Stats } from '../stats.js';
|
|
4
54
|
import { File } from '../file.js';
|
|
55
|
+
import { FileSystem } from '../filesystem.js';
|
|
56
|
+
import { Stats } from '../stats.js';
|
|
5
57
|
import { join, resolve } from '../vfs/path.js';
|
|
6
58
|
class PassthroughFile extends File {
|
|
7
59
|
constructor(fs, path, fd) {
|
|
@@ -281,7 +333,23 @@ export class PassthroughFS extends FileSystem {
|
|
|
281
333
|
*/
|
|
282
334
|
async sync(path, data, stats) {
|
|
283
335
|
try {
|
|
284
|
-
|
|
336
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
337
|
+
try {
|
|
338
|
+
const handle = __addDisposableResource(env_1, await this.nodeFS.promises.open(this.path(path), 'w'), true);
|
|
339
|
+
await handle.writeFile(data);
|
|
340
|
+
await handle.chmod(stats.mode);
|
|
341
|
+
await handle.chown(stats.uid, stats.gid);
|
|
342
|
+
await handle.utimes(stats.atimeMs, stats.mtimeMs);
|
|
343
|
+
}
|
|
344
|
+
catch (e_1) {
|
|
345
|
+
env_1.error = e_1;
|
|
346
|
+
env_1.hasError = true;
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
const result_1 = __disposeResources(env_1);
|
|
350
|
+
if (result_1)
|
|
351
|
+
await result_1;
|
|
352
|
+
}
|
|
285
353
|
}
|
|
286
354
|
catch (err) {
|
|
287
355
|
this.error(err, path);
|
|
@@ -292,7 +360,11 @@ export class PassthroughFS extends FileSystem {
|
|
|
292
360
|
*/
|
|
293
361
|
syncSync(path, data, stats) {
|
|
294
362
|
try {
|
|
295
|
-
this.
|
|
363
|
+
const p = this.path(path);
|
|
364
|
+
this.nodeFS.writeFileSync(p, data);
|
|
365
|
+
this.nodeFS.chmodSync(p, stats.mode);
|
|
366
|
+
this.nodeFS.chownSync(p, stats.uid, stats.gid);
|
|
367
|
+
this.nodeFS.utimesSync(p, stats.atimeMs, stats.mtimeMs);
|
|
296
368
|
}
|
|
297
369
|
catch (err) {
|
|
298
370
|
this.error(err, path);
|
|
@@ -320,6 +392,78 @@ export class PassthroughFS extends FileSystem {
|
|
|
320
392
|
this.error(err, target);
|
|
321
393
|
}
|
|
322
394
|
}
|
|
395
|
+
async read(path, buffer, offset, end) {
|
|
396
|
+
try {
|
|
397
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
398
|
+
try {
|
|
399
|
+
const handle = __addDisposableResource(env_2, await this.nodeFS.promises.open(this.path(path), 'r'), true);
|
|
400
|
+
await handle.read({ buffer, offset, length: end - offset });
|
|
401
|
+
}
|
|
402
|
+
catch (e_2) {
|
|
403
|
+
env_2.error = e_2;
|
|
404
|
+
env_2.hasError = true;
|
|
405
|
+
}
|
|
406
|
+
finally {
|
|
407
|
+
const result_2 = __disposeResources(env_2);
|
|
408
|
+
if (result_2)
|
|
409
|
+
await result_2;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
this.error(err, path);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
readSync(path, buffer, offset, end) {
|
|
417
|
+
let fd;
|
|
418
|
+
try {
|
|
419
|
+
fd = this.nodeFS.openSync(this.path(path), 'r');
|
|
420
|
+
this.nodeFS.readSync(fd, buffer, { offset, length: end - offset });
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
this.error(err, path);
|
|
424
|
+
}
|
|
425
|
+
finally {
|
|
426
|
+
if (fd)
|
|
427
|
+
this.nodeFS.closeSync(fd);
|
|
428
|
+
}
|
|
429
|
+
// unreachable
|
|
430
|
+
throw ErrnoError.With('EIO', path, 'read');
|
|
431
|
+
}
|
|
432
|
+
async write(path, buffer, offset) {
|
|
433
|
+
try {
|
|
434
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
435
|
+
try {
|
|
436
|
+
const handle = __addDisposableResource(env_3, await this.nodeFS.promises.open(this.path(path), 'w'), true);
|
|
437
|
+
await handle.write(buffer, offset);
|
|
438
|
+
}
|
|
439
|
+
catch (e_3) {
|
|
440
|
+
env_3.error = e_3;
|
|
441
|
+
env_3.hasError = true;
|
|
442
|
+
}
|
|
443
|
+
finally {
|
|
444
|
+
const result_3 = __disposeResources(env_3);
|
|
445
|
+
if (result_3)
|
|
446
|
+
await result_3;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
this.error(err, path);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
writeSync(path, buffer, offset) {
|
|
454
|
+
let fd;
|
|
455
|
+
try {
|
|
456
|
+
fd = this.nodeFS.openSync(this.path(path), 'w');
|
|
457
|
+
this.nodeFS.writeSync(fd, buffer, offset);
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
this.error(err, path);
|
|
461
|
+
}
|
|
462
|
+
finally {
|
|
463
|
+
if (fd)
|
|
464
|
+
this.nodeFS.closeSync(fd);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
323
467
|
}
|
|
324
468
|
const _Passthrough = {
|
|
325
469
|
name: 'Passthrough',
|
|
@@ -1,56 +1,20 @@
|
|
|
1
|
-
import type { FileReadResult } from 'node:fs/promises';
|
|
2
1
|
import type { ExtractProperties } from 'utilium';
|
|
3
2
|
import type { MountConfiguration } from '../../config.js';
|
|
4
3
|
import type { CreationOptions, FileSystemMetadata } from '../../filesystem.js';
|
|
5
4
|
import type { Backend, FilesystemOf } from '../backend.js';
|
|
6
|
-
import {
|
|
5
|
+
import type { Inode, InodeLike } from '../store/inode.js';
|
|
6
|
+
import type { File } from '../../file.js';
|
|
7
7
|
import { FileSystem } from '../../filesystem.js';
|
|
8
8
|
import { Stats } from '../../stats.js';
|
|
9
9
|
import * as RPC from './rpc.js';
|
|
10
|
-
type FileMethods = Omit<ExtractProperties<File, (...args: any[]) => Promise<any>>, typeof Symbol.asyncDispose>;
|
|
11
|
-
type FileMethod = keyof FileMethods;
|
|
12
|
-
/** @internal */
|
|
13
|
-
export interface FileRequest<TMethod extends FileMethod = FileMethod> extends RPC.Request {
|
|
14
|
-
fd: number;
|
|
15
|
-
scope: 'file';
|
|
16
|
-
method: TMethod;
|
|
17
|
-
args: Parameters<FileMethods[TMethod]>;
|
|
18
|
-
}
|
|
19
|
-
export declare class PortFile extends File {
|
|
20
|
-
fs: PortFS;
|
|
21
|
-
readonly fd: number;
|
|
22
|
-
position: number;
|
|
23
|
-
constructor(fs: PortFS, fd: number, path: string, position: number);
|
|
24
|
-
rpc<const T extends FileMethod>(method: T, ...args: Parameters<FileMethods[T]>): Promise<Awaited<ReturnType<FileMethods[T]>>>;
|
|
25
|
-
protected _throwNoSync(syscall: string): never;
|
|
26
|
-
stat(): Promise<Stats>;
|
|
27
|
-
statSync(): Stats;
|
|
28
|
-
truncate(len: number): Promise<void>;
|
|
29
|
-
truncateSync(): void;
|
|
30
|
-
write(buffer: Uint8Array, offset?: number, length?: number, position?: number): Promise<number>;
|
|
31
|
-
writeSync(): number;
|
|
32
|
-
read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<FileReadResult<TBuffer>>;
|
|
33
|
-
readSync(): number;
|
|
34
|
-
chown(uid: number, gid: number): Promise<void>;
|
|
35
|
-
chownSync(): void;
|
|
36
|
-
chmod(mode: number): Promise<void>;
|
|
37
|
-
chmodSync(): void;
|
|
38
|
-
utimes(atime: Date, mtime: Date): Promise<void>;
|
|
39
|
-
utimesSync(): void;
|
|
40
|
-
_setTypeSync(): void;
|
|
41
|
-
close(): Promise<void>;
|
|
42
|
-
closeSync(): void;
|
|
43
|
-
sync(): Promise<void>;
|
|
44
|
-
syncSync(): void;
|
|
45
|
-
}
|
|
46
10
|
type FSMethods = ExtractProperties<FileSystem, (...args: any[]) => Promise<any> | FileSystemMetadata>;
|
|
47
11
|
type FSMethod = keyof FSMethods;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
12
|
+
export type FSRequest<TMethod extends FSMethod = FSMethod> = RPC.Message & {
|
|
13
|
+
[M in TMethod]: {
|
|
14
|
+
method: M;
|
|
15
|
+
args: Parameters<FSMethods[M]>;
|
|
16
|
+
};
|
|
17
|
+
}[TMethod];
|
|
54
18
|
declare const PortFS_base: import("../../index.js").Mixin<typeof FileSystem, import("../../mixins/async.js").AsyncMixin>;
|
|
55
19
|
/**
|
|
56
20
|
* PortFS lets you access an FS instance that is running in a port, or the other way around.
|
|
@@ -61,7 +25,7 @@ declare const PortFS_base: import("../../index.js").Mixin<typeof FileSystem, imp
|
|
|
61
25
|
export declare class PortFS extends PortFS_base {
|
|
62
26
|
readonly options: RPC.Options;
|
|
63
27
|
readonly port: RPC.Port;
|
|
64
|
-
|
|
28
|
+
/**`
|
|
65
29
|
* @hidden
|
|
66
30
|
*/
|
|
67
31
|
_sync: import("../index.js").StoreFS<import("../memory.js").InMemoryStore>;
|
|
@@ -74,7 +38,7 @@ export declare class PortFS extends PortFS_base {
|
|
|
74
38
|
ready(): Promise<void>;
|
|
75
39
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
76
40
|
stat(path: string): Promise<Stats>;
|
|
77
|
-
sync(path: string, data: Uint8Array, stats: Readonly<
|
|
41
|
+
sync(path: string, data: Uint8Array | undefined, stats: Readonly<InodeLike | Inode>): Promise<void>;
|
|
78
42
|
openFile(path: string, flag: string): Promise<File>;
|
|
79
43
|
createFile(path: string, flag: string, mode: number, options: CreationOptions): Promise<File>;
|
|
80
44
|
unlink(path: string): Promise<void>;
|
|
@@ -83,11 +47,13 @@ export declare class PortFS extends PortFS_base {
|
|
|
83
47
|
readdir(path: string): Promise<string[]>;
|
|
84
48
|
exists(path: string): Promise<boolean>;
|
|
85
49
|
link(srcpath: string, dstpath: string): Promise<void>;
|
|
50
|
+
read(path: string, buffer: Uint8Array, offset: number, length: number): Promise<void>;
|
|
51
|
+
write(path: string, buffer: Uint8Array, offset: number): Promise<void>;
|
|
86
52
|
}
|
|
87
53
|
/** @internal */
|
|
88
|
-
export
|
|
89
|
-
|
|
90
|
-
|
|
54
|
+
export declare function handleRequest(port: RPC.Port, fs: FileSystem & {
|
|
55
|
+
_descriptors?: Map<number, File>;
|
|
56
|
+
}, request: FSRequest): Promise<void>;
|
|
91
57
|
export declare function attachFS(port: RPC.Port, fs: FileSystem): void;
|
|
92
58
|
export declare function detachFS(port: RPC.Port, fs: FileSystem): void;
|
|
93
59
|
declare const _Port: {
|
package/dist/backends/port/fs.js
CHANGED
|
@@ -1,88 +1,11 @@
|
|
|
1
|
+
import { pick } from 'utilium';
|
|
1
2
|
import { resolveMountConfig } from '../../config.js';
|
|
2
3
|
import { Errno, ErrnoError } from '../../error.js';
|
|
3
|
-
import { File } from '../../file.js';
|
|
4
4
|
import { FileSystem } from '../../filesystem.js';
|
|
5
5
|
import { Async } from '../../mixins/async.js';
|
|
6
6
|
import { Stats } from '../../stats.js';
|
|
7
7
|
import { InMemory } from '../memory.js';
|
|
8
8
|
import * as RPC from './rpc.js';
|
|
9
|
-
export class PortFile extends File {
|
|
10
|
-
constructor(fs, fd, path, position) {
|
|
11
|
-
super(fs, path);
|
|
12
|
-
this.fs = fs;
|
|
13
|
-
this.fd = fd;
|
|
14
|
-
this.position = position;
|
|
15
|
-
}
|
|
16
|
-
rpc(method, ...args) {
|
|
17
|
-
return RPC.request({
|
|
18
|
-
scope: 'file',
|
|
19
|
-
fd: this.fd,
|
|
20
|
-
method,
|
|
21
|
-
args,
|
|
22
|
-
}, this.fs.options);
|
|
23
|
-
}
|
|
24
|
-
_throwNoSync(syscall) {
|
|
25
|
-
throw new ErrnoError(Errno.ENOTSUP, 'Synchronous operations not supported on PortFile', this.path, syscall);
|
|
26
|
-
}
|
|
27
|
-
async stat() {
|
|
28
|
-
return new Stats(await this.rpc('stat'));
|
|
29
|
-
}
|
|
30
|
-
statSync() {
|
|
31
|
-
this._throwNoSync('stat');
|
|
32
|
-
}
|
|
33
|
-
truncate(len) {
|
|
34
|
-
return this.rpc('truncate', len);
|
|
35
|
-
}
|
|
36
|
-
truncateSync() {
|
|
37
|
-
this._throwNoSync('truncate');
|
|
38
|
-
}
|
|
39
|
-
write(buffer, offset, length, position) {
|
|
40
|
-
return this.rpc('write', buffer, offset, length, position);
|
|
41
|
-
}
|
|
42
|
-
writeSync() {
|
|
43
|
-
this._throwNoSync('write');
|
|
44
|
-
}
|
|
45
|
-
async read(buffer, offset, length, position) {
|
|
46
|
-
const result = await this.rpc('read', buffer, offset, length, position);
|
|
47
|
-
return result;
|
|
48
|
-
}
|
|
49
|
-
readSync() {
|
|
50
|
-
this._throwNoSync('read');
|
|
51
|
-
}
|
|
52
|
-
chown(uid, gid) {
|
|
53
|
-
return this.rpc('chown', uid, gid);
|
|
54
|
-
}
|
|
55
|
-
chownSync() {
|
|
56
|
-
this._throwNoSync('chown');
|
|
57
|
-
}
|
|
58
|
-
chmod(mode) {
|
|
59
|
-
return this.rpc('chmod', mode);
|
|
60
|
-
}
|
|
61
|
-
chmodSync() {
|
|
62
|
-
this._throwNoSync('chmod');
|
|
63
|
-
}
|
|
64
|
-
utimes(atime, mtime) {
|
|
65
|
-
return this.rpc('utimes', atime, mtime);
|
|
66
|
-
}
|
|
67
|
-
utimesSync() {
|
|
68
|
-
this._throwNoSync('utimes');
|
|
69
|
-
}
|
|
70
|
-
_setTypeSync() {
|
|
71
|
-
this._throwNoSync('_setType');
|
|
72
|
-
}
|
|
73
|
-
close() {
|
|
74
|
-
return this.rpc('close');
|
|
75
|
-
}
|
|
76
|
-
closeSync() {
|
|
77
|
-
this._throwNoSync('close');
|
|
78
|
-
}
|
|
79
|
-
sync() {
|
|
80
|
-
return this.rpc('sync');
|
|
81
|
-
}
|
|
82
|
-
syncSync() {
|
|
83
|
-
this._throwNoSync('sync');
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
9
|
/**
|
|
87
10
|
* PortFS lets you access an FS instance that is running in a port, or the other way around.
|
|
88
11
|
*
|
|
@@ -96,7 +19,7 @@ export class PortFS extends Async(FileSystem) {
|
|
|
96
19
|
constructor(options) {
|
|
97
20
|
super();
|
|
98
21
|
this.options = options;
|
|
99
|
-
|
|
22
|
+
/**`
|
|
100
23
|
* @hidden
|
|
101
24
|
*/
|
|
102
25
|
this._sync = InMemory.create({ name: 'port-tmpfs' });
|
|
@@ -110,11 +33,7 @@ export class PortFS extends Async(FileSystem) {
|
|
|
110
33
|
};
|
|
111
34
|
}
|
|
112
35
|
rpc(method, ...args) {
|
|
113
|
-
return RPC.request({
|
|
114
|
-
scope: 'fs',
|
|
115
|
-
method,
|
|
116
|
-
args,
|
|
117
|
-
}, { ...this.options, fs: this });
|
|
36
|
+
return RPC.request({ method, args }, { ...this.options, fs: this });
|
|
118
37
|
}
|
|
119
38
|
async ready() {
|
|
120
39
|
await this.rpc('ready');
|
|
@@ -127,6 +46,7 @@ export class PortFS extends Async(FileSystem) {
|
|
|
127
46
|
return new Stats(await this.rpc('stat', path));
|
|
128
47
|
}
|
|
129
48
|
sync(path, data, stats) {
|
|
49
|
+
stats = 'toJSON' in stats ? stats.toJSON() : stats;
|
|
130
50
|
return this.rpc('sync', path, data, stats);
|
|
131
51
|
}
|
|
132
52
|
openFile(path, flag) {
|
|
@@ -153,51 +73,43 @@ export class PortFS extends Async(FileSystem) {
|
|
|
153
73
|
link(srcpath, dstpath) {
|
|
154
74
|
return this.rpc('link', srcpath, dstpath);
|
|
155
75
|
}
|
|
76
|
+
async read(path, buffer, offset, length) {
|
|
77
|
+
const _buf = (await this.rpc('read', path, buffer, offset, length));
|
|
78
|
+
buffer.set(_buf);
|
|
79
|
+
}
|
|
80
|
+
write(path, buffer, offset) {
|
|
81
|
+
return this.rpc('write', path, buffer, offset);
|
|
82
|
+
}
|
|
156
83
|
}
|
|
157
|
-
let nextFd = 0;
|
|
158
|
-
const descriptors = new Map();
|
|
159
84
|
/** @internal */
|
|
160
85
|
export async function handleRequest(port, fs, request) {
|
|
161
|
-
if (!RPC.isMessage(request))
|
|
86
|
+
if (!RPC.isMessage(request))
|
|
162
87
|
return;
|
|
163
|
-
}
|
|
164
|
-
const { method, args, id, scope, stack } = request;
|
|
88
|
+
const { method, args, id, stack } = request;
|
|
165
89
|
let value, error = false;
|
|
166
90
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
break;
|
|
180
|
-
case 'file': {
|
|
181
|
-
const { fd } = request;
|
|
182
|
-
if (!descriptors.has(fd)) {
|
|
183
|
-
throw new ErrnoError(Errno.EBADF);
|
|
184
|
-
}
|
|
185
|
-
// @ts-expect-error 2556
|
|
186
|
-
value = await descriptors.get(fd)[method](...args);
|
|
187
|
-
if (method == 'close') {
|
|
188
|
-
descriptors.delete(fd);
|
|
189
|
-
}
|
|
91
|
+
// @ts-expect-error 2556
|
|
92
|
+
value = await fs[method](...args);
|
|
93
|
+
switch (method) {
|
|
94
|
+
case 'openFile':
|
|
95
|
+
case 'createFile': {
|
|
96
|
+
value = {
|
|
97
|
+
path: args[0],
|
|
98
|
+
flag: args[1],
|
|
99
|
+
stats: await fs.stat(args[0]),
|
|
100
|
+
};
|
|
190
101
|
break;
|
|
191
102
|
}
|
|
192
|
-
|
|
193
|
-
|
|
103
|
+
case 'read':
|
|
104
|
+
value = args[1];
|
|
105
|
+
break;
|
|
194
106
|
}
|
|
195
107
|
}
|
|
196
108
|
catch (e) {
|
|
197
|
-
value = e instanceof ErrnoError ? e.toJSON() : e
|
|
109
|
+
value = e instanceof ErrnoError ? e.toJSON() : pick(e, 'message', 'stack');
|
|
198
110
|
error = true;
|
|
199
111
|
}
|
|
200
|
-
port.postMessage({ _zenfs: true,
|
|
112
|
+
port.postMessage({ _zenfs: true, id, error, method, stack, value });
|
|
201
113
|
}
|
|
202
114
|
export function attachFS(port, fs) {
|
|
203
115
|
RPC.attach(port, request => handleRequest(port, fs, request));
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import type { TransferListItem } from 'node:worker_threads';
|
|
2
|
+
import type { WithOptional } from 'utilium';
|
|
1
3
|
import type { ErrnoErrorJSON } from '../../error.js';
|
|
2
4
|
import type { Backend, FilesystemOf } from '../backend.js';
|
|
3
5
|
import type { PortFS } from './fs.js';
|
|
6
|
+
import { type StatsLike } from '../../stats.js';
|
|
4
7
|
type _MessageEvent<T = any> = T | {
|
|
5
8
|
data: T;
|
|
6
9
|
};
|
|
7
10
|
/** @internal */
|
|
8
11
|
export interface Port {
|
|
9
|
-
postMessage(value: unknown): void;
|
|
12
|
+
postMessage(value: unknown, transfer?: TransferListItem[]): void;
|
|
10
13
|
on?(event: 'message', listener: (value: unknown) => void): this;
|
|
11
14
|
off?(event: 'message', listener: (value: unknown) => void): this;
|
|
12
15
|
addEventListener?(type: 'message', listener: (ev: _MessageEvent) => void): void;
|
|
@@ -27,7 +30,6 @@ export interface Options {
|
|
|
27
30
|
*/
|
|
28
31
|
export interface Message {
|
|
29
32
|
_zenfs: true;
|
|
30
|
-
scope: 'fs' | 'file';
|
|
31
33
|
id: string;
|
|
32
34
|
method: string;
|
|
33
35
|
stack: string;
|
|
@@ -37,17 +39,22 @@ export interface Request extends Message {
|
|
|
37
39
|
}
|
|
38
40
|
interface _ResponseWithError extends Message {
|
|
39
41
|
error: true;
|
|
40
|
-
value: ErrnoErrorJSON |
|
|
42
|
+
value: WithOptional<ErrnoErrorJSON, 'code' | 'errno'>;
|
|
41
43
|
}
|
|
42
44
|
interface _ResponseWithValue<T> extends Message {
|
|
43
45
|
error: false;
|
|
44
46
|
value: Awaited<T> extends File ? FileData : Awaited<T>;
|
|
45
47
|
}
|
|
46
|
-
|
|
48
|
+
interface _ResponseRead extends Message {
|
|
49
|
+
error: false;
|
|
50
|
+
method: 'read';
|
|
51
|
+
value: Uint8Array;
|
|
52
|
+
}
|
|
53
|
+
export type Response<T = unknown> = _ResponseWithError | _ResponseWithValue<T> | _ResponseRead;
|
|
47
54
|
export interface FileData {
|
|
48
|
-
fd: number;
|
|
49
55
|
path: string;
|
|
50
|
-
|
|
56
|
+
flag: string;
|
|
57
|
+
stats: StatsLike<number>;
|
|
51
58
|
}
|
|
52
59
|
export { FileData as File };
|
|
53
60
|
export declare function isMessage(arg: unknown): arg is Message;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Errno, ErrnoError } from '../../error.js';
|
|
2
|
-
import {
|
|
2
|
+
import { LazyFile } from '../../file.js';
|
|
3
|
+
import { Stats } from '../../stats.js';
|
|
4
|
+
import { handleRequest } from './fs.js';
|
|
3
5
|
function isFileData(value) {
|
|
4
|
-
return typeof value == 'object' && value != null && '
|
|
6
|
+
return typeof value == 'object' && value != null && 'path' in value && 'flag' in value;
|
|
5
7
|
}
|
|
6
8
|
// general types
|
|
7
9
|
export function isMessage(arg) {
|
|
@@ -18,7 +20,7 @@ export function request(request, { port, timeout = 1000, fs } = {}) {
|
|
|
18
20
|
executors.set(id, { resolve, reject, fs });
|
|
19
21
|
port.postMessage({ ...request, _zenfs: true, id, stack });
|
|
20
22
|
const _ = setTimeout(() => {
|
|
21
|
-
const error = new ErrnoError(Errno.EIO, 'RPC Failed');
|
|
23
|
+
const error = new ErrnoError(Errno.EIO, 'RPC Failed', typeof request.args[0] == 'string' ? request.args[0] : '', request.method);
|
|
22
24
|
error.stack += stack;
|
|
23
25
|
reject(error);
|
|
24
26
|
if (typeof _ == 'object')
|
|
@@ -38,15 +40,15 @@ export function handleResponse(response) {
|
|
|
38
40
|
}
|
|
39
41
|
const { resolve, reject, fs } = executors.get(id);
|
|
40
42
|
if (error) {
|
|
41
|
-
const e =
|
|
43
|
+
const e = ErrnoError.fromJSON({ code: 'EIO', errno: Errno.EIO, ...value });
|
|
42
44
|
e.stack += stack;
|
|
43
45
|
reject(e);
|
|
44
46
|
executors.delete(id);
|
|
45
47
|
return;
|
|
46
48
|
}
|
|
47
49
|
if (isFileData(value)) {
|
|
48
|
-
const {
|
|
49
|
-
const file = new
|
|
50
|
+
const { path, flag, stats } = value;
|
|
51
|
+
const file = new LazyFile(fs, path, flag, new Stats(stats));
|
|
50
52
|
resolve(file);
|
|
51
53
|
executors.delete(id);
|
|
52
54
|
return;
|
|
@@ -78,7 +80,7 @@ export function catchMessages(port) {
|
|
|
78
80
|
return function (fs) {
|
|
79
81
|
detach(port, handler);
|
|
80
82
|
for (const event of events) {
|
|
81
|
-
const request =
|
|
83
|
+
const request = 'data' in event ? event.data : event;
|
|
82
84
|
void handleRequest(port, fs, request);
|
|
83
85
|
}
|
|
84
86
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { InodeLike } from './inode.js';
|
|
2
|
+
import { Inode } from './inode.js';
|
|
3
|
+
/**
|
|
4
|
+
* An Index in JSON form
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export interface IndexData {
|
|
8
|
+
version: number;
|
|
9
|
+
entries: Record<string, InodeLike>;
|
|
10
|
+
}
|
|
11
|
+
export declare const version = 1;
|
|
12
|
+
/**
|
|
13
|
+
* An index of files
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export declare class Index extends Map<string, Readonly<Inode>> {
|
|
17
|
+
protected _directories?: Map<string, Record<string, number>>;
|
|
18
|
+
/**
|
|
19
|
+
* Converts the index to JSON
|
|
20
|
+
*/
|
|
21
|
+
toJSON(): IndexData;
|
|
22
|
+
/**
|
|
23
|
+
* Converts the index to a string
|
|
24
|
+
*/
|
|
25
|
+
toString(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Gets a list of entries for each directory in the index. Memoized.
|
|
28
|
+
*/
|
|
29
|
+
directories(): Map<string, Record<string, number>>;
|
|
30
|
+
/**
|
|
31
|
+
* Loads the index from JSON data
|
|
32
|
+
*/
|
|
33
|
+
fromJSON(json: IndexData): void;
|
|
34
|
+
/**
|
|
35
|
+
* Parses an index from a string
|
|
36
|
+
*/
|
|
37
|
+
static parse(data: string): Index;
|
|
38
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* Note: this file is named file_index.ts because Typescript has special behavior regarding index.ts which can't be disabled. */
|
|
2
|
+
import { isJSON, randomInt } from 'utilium';
|
|
3
|
+
import { Errno, ErrnoError } from '../../error.js';
|
|
4
|
+
import { S_IFDIR, S_IFMT, size_max } from '../../vfs/constants.js';
|
|
5
|
+
import { basename, dirname } from '../../vfs/path.js';
|
|
6
|
+
import { Inode } from './inode.js';
|
|
7
|
+
export const version = 1;
|
|
8
|
+
/**
|
|
9
|
+
* An index of files
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export class Index extends Map {
|
|
13
|
+
/**
|
|
14
|
+
* Converts the index to JSON
|
|
15
|
+
*/
|
|
16
|
+
toJSON() {
|
|
17
|
+
return {
|
|
18
|
+
version,
|
|
19
|
+
entries: Object.fromEntries([...this].map(([k, v]) => [k, v.toJSON()])),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Converts the index to a string
|
|
24
|
+
*/
|
|
25
|
+
toString() {
|
|
26
|
+
return JSON.stringify(this.toJSON());
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Gets a list of entries for each directory in the index. Memoized.
|
|
30
|
+
*/
|
|
31
|
+
directories() {
|
|
32
|
+
if (this._directories)
|
|
33
|
+
return this._directories;
|
|
34
|
+
const dirs = new Map();
|
|
35
|
+
for (const [path, node] of this) {
|
|
36
|
+
if ((node.mode & S_IFMT) != S_IFDIR)
|
|
37
|
+
continue;
|
|
38
|
+
const entries = {};
|
|
39
|
+
for (const entry of this.keys()) {
|
|
40
|
+
if (dirname(entry) == path && entry != path)
|
|
41
|
+
entries[basename(entry)] = this.get(entry).ino;
|
|
42
|
+
}
|
|
43
|
+
dirs.set(path, entries);
|
|
44
|
+
}
|
|
45
|
+
this._directories = dirs;
|
|
46
|
+
return dirs;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Loads the index from JSON data
|
|
50
|
+
*/
|
|
51
|
+
fromJSON(json) {
|
|
52
|
+
var _a;
|
|
53
|
+
if (json.version != version) {
|
|
54
|
+
throw new ErrnoError(Errno.EINVAL, 'Index version mismatch');
|
|
55
|
+
}
|
|
56
|
+
this.clear();
|
|
57
|
+
for (const [path, node] of Object.entries(json.entries)) {
|
|
58
|
+
(_a = node.data) !== null && _a !== void 0 ? _a : (node.data = randomInt(1, size_max));
|
|
59
|
+
if (path == '/')
|
|
60
|
+
node.ino = 0;
|
|
61
|
+
this.set(path, new Inode(node));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parses an index from a string
|
|
66
|
+
*/
|
|
67
|
+
static parse(data) {
|
|
68
|
+
if (!isJSON(data)) {
|
|
69
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid JSON');
|
|
70
|
+
}
|
|
71
|
+
const json = JSON.parse(data);
|
|
72
|
+
const index = new Index();
|
|
73
|
+
index.fromJSON(json);
|
|
74
|
+
return index;
|
|
75
|
+
}
|
|
76
|
+
}
|