@zenfs/core 2.2.3 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backends/backend.js +6 -9
- package/dist/backends/cow.js +4 -4
- package/dist/backends/fetch.js +8 -6
- package/dist/backends/memory.js +4 -2
- package/dist/backends/passthrough.js +2 -0
- package/dist/backends/port.d.ts +16 -89
- package/dist/backends/port.js +35 -171
- package/dist/backends/single_buffer.d.ts +4 -2
- package/dist/backends/single_buffer.js +169 -196
- package/dist/backends/store/fs.js +50 -73
- package/dist/backends/store/map.js +1 -2
- package/dist/backends/store/store.js +23 -27
- package/dist/config.js +2 -3
- package/dist/context.js +2 -2
- package/dist/internal/devices.js +7 -10
- package/dist/internal/file_index.js +3 -8
- package/dist/internal/filesystem.js +19 -12
- package/dist/internal/index_fs.js +3 -4
- package/dist/internal/inode.d.ts +2 -0
- package/dist/internal/inode.js +148 -185
- package/dist/internal/rpc.d.ts +143 -0
- package/dist/internal/rpc.js +251 -0
- package/dist/mixins/async.js +5 -6
- package/dist/mixins/mutexed.js +16 -10
- package/dist/path.js +3 -4
- package/dist/polyfills.js +51 -22
- package/dist/readline.js +32 -30
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +11 -5
- package/dist/vfs/acl.d.ts +2 -0
- package/dist/vfs/acl.js +48 -66
- package/dist/vfs/async.js +4 -4
- package/dist/vfs/dir.js +12 -8
- package/dist/vfs/file.js +22 -18
- package/dist/vfs/ioctl.js +39 -62
- package/dist/vfs/promises.js +48 -39
- package/dist/vfs/shared.js +4 -5
- package/dist/vfs/stats.js +104 -77
- package/dist/vfs/streams.js +11 -8
- package/dist/vfs/sync.js +23 -26
- package/dist/vfs/watchers.js +9 -3
- package/dist/vfs/xattr.js +6 -12
- package/package.json +1 -1
- package/scripts/test.js +14 -7
- package/tests/backend/fetch.test.ts +14 -14
- package/tests/backend/port.test.ts +25 -17
- package/tests/common/handle.test.ts +5 -3
- package/tests/fetch/run.sh +2 -1
- package/tests/fs/scaling.test.ts +32 -0
- package/tests/fs/watch.test.ts +2 -5
- package/tests/setup/single-buffer.ts +1 -1
- package/tests/tsconfig.json +3 -2
- package/types/uint8array.d.ts +64 -0
package/dist/backends/backend.js
CHANGED
|
@@ -32,7 +32,7 @@ export function checkOptions(backend, options) {
|
|
|
32
32
|
}
|
|
33
33
|
// Check for required options.
|
|
34
34
|
for (const [optName, opt] of Object.entries(backend.options)) {
|
|
35
|
-
const value = options
|
|
35
|
+
const value = options?.[optName];
|
|
36
36
|
if (value === undefined || value === null) {
|
|
37
37
|
if (!opt.required) {
|
|
38
38
|
debug('Using default for option: ' + optName);
|
|
@@ -40,14 +40,11 @@ export function checkOptions(backend, options) {
|
|
|
40
40
|
}
|
|
41
41
|
throw err(withErrno('EINVAL', 'Missing required option: ' + optName));
|
|
42
42
|
}
|
|
43
|
-
const isType = (type, _ = value) =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
: type(value)
|
|
49
|
-
: typeof value === type || ((_a = value === null || value === void 0 ? void 0 : value.constructor) === null || _a === void 0 ? void 0 : _a.name) === type;
|
|
50
|
-
};
|
|
43
|
+
const isType = (type, _ = value) => typeof type == 'function'
|
|
44
|
+
? Symbol.hasInstance in type && type.prototype
|
|
45
|
+
? value instanceof type
|
|
46
|
+
: type(value)
|
|
47
|
+
: typeof value === type || value?.constructor?.name === type;
|
|
51
48
|
if (Array.isArray(opt.type) ? opt.type.some(v => isType(v)) : isType(opt.type))
|
|
52
49
|
continue;
|
|
53
50
|
// The type of the value as a string
|
package/dist/backends/cow.js
CHANGED
|
@@ -19,10 +19,7 @@ const journalMagicString = '#journal@v0\n';
|
|
|
19
19
|
* @internal
|
|
20
20
|
*/
|
|
21
21
|
export class Journal extends EventEmitter {
|
|
22
|
-
|
|
23
|
-
super(...arguments);
|
|
24
|
-
this.entries = [];
|
|
25
|
-
}
|
|
22
|
+
entries = [];
|
|
26
23
|
toString() {
|
|
27
24
|
return journalMagicString + this.entries.map(entry => `${entry.op.padEnd(maxOpLength)} ${entry.path}`).join('\n');
|
|
28
25
|
}
|
|
@@ -75,6 +72,9 @@ export class Journal extends EventEmitter {
|
|
|
75
72
|
* @category Internals
|
|
76
73
|
*/
|
|
77
74
|
export class CopyOnWriteFS extends FileSystem {
|
|
75
|
+
readable;
|
|
76
|
+
writable;
|
|
77
|
+
journal;
|
|
78
78
|
async ready() {
|
|
79
79
|
await this.readable.ready();
|
|
80
80
|
await this.writable.ready();
|
package/dist/backends/fetch.js
CHANGED
|
@@ -27,6 +27,13 @@ function parseError(error) {
|
|
|
27
27
|
* @internal
|
|
28
28
|
*/
|
|
29
29
|
export class FetchFS extends IndexFS {
|
|
30
|
+
baseUrl;
|
|
31
|
+
requestInit;
|
|
32
|
+
remoteWrite;
|
|
33
|
+
/**
|
|
34
|
+
* @internal @hidden
|
|
35
|
+
*/
|
|
36
|
+
_asyncDone = Promise.resolve();
|
|
30
37
|
_async(p) {
|
|
31
38
|
this._asyncDone = this._asyncDone.then(() => p);
|
|
32
39
|
}
|
|
@@ -35,10 +42,6 @@ export class FetchFS extends IndexFS {
|
|
|
35
42
|
this.baseUrl = baseUrl;
|
|
36
43
|
this.requestInit = requestInit;
|
|
37
44
|
this.remoteWrite = remoteWrite;
|
|
38
|
-
/**
|
|
39
|
-
* @internal @hidden
|
|
40
|
-
*/
|
|
41
|
-
this._asyncDone = Promise.resolve();
|
|
42
45
|
}
|
|
43
46
|
async remove(path) {
|
|
44
47
|
await requests.remove(this.baseUrl + path, { warn, cacheOnly: !this.remoteWrite }, this.requestInit);
|
|
@@ -102,13 +105,12 @@ const _Fetch = {
|
|
|
102
105
|
return typeof globalThis.fetch == 'function';
|
|
103
106
|
},
|
|
104
107
|
async create(options) {
|
|
105
|
-
var _a;
|
|
106
108
|
const url = new URL(options.baseUrl);
|
|
107
109
|
url.pathname = normalizePath(url.pathname);
|
|
108
110
|
let baseUrl = url.toString();
|
|
109
111
|
if (baseUrl.at(-1) == '/')
|
|
110
112
|
baseUrl = baseUrl.slice(0, -1);
|
|
111
|
-
|
|
113
|
+
options.index ??= 'index.json';
|
|
112
114
|
const index = new Index();
|
|
113
115
|
if (typeof options.index != 'string') {
|
|
114
116
|
index.fromJSON(options.index);
|
package/dist/backends/memory.js
CHANGED
|
@@ -6,12 +6,14 @@ import { SyncMapTransaction } from './store/map.js';
|
|
|
6
6
|
* @category Stores and Transactions
|
|
7
7
|
*/
|
|
8
8
|
export class InMemoryStore extends Map {
|
|
9
|
+
maxSize;
|
|
10
|
+
label;
|
|
11
|
+
flags = [];
|
|
12
|
+
name = 'tmpfs';
|
|
9
13
|
constructor(maxSize = size_max, label) {
|
|
10
14
|
super();
|
|
11
15
|
this.maxSize = maxSize;
|
|
12
16
|
this.label = label;
|
|
13
|
-
this.flags = [];
|
|
14
|
-
this.name = 'tmpfs';
|
|
15
17
|
}
|
|
16
18
|
async sync() { }
|
|
17
19
|
transaction() {
|
|
@@ -55,6 +55,8 @@ import { FileSystem } from '../internal/filesystem.js';
|
|
|
55
55
|
import { isDirectory } from '../internal/inode.js';
|
|
56
56
|
import { resolve } from '../path.js';
|
|
57
57
|
export class PassthroughFS extends FileSystem {
|
|
58
|
+
nodeFS;
|
|
59
|
+
prefix;
|
|
58
60
|
constructor(nodeFS, prefix) {
|
|
59
61
|
super(0x6e6f6465, 'nodefs');
|
|
60
62
|
this.nodeFS = nodeFS;
|
package/dist/backends/port.d.ts
CHANGED
|
@@ -1,94 +1,25 @@
|
|
|
1
|
-
import type { TransferListItem } from 'node:worker_threads';
|
|
2
1
|
import type { MountConfiguration } from '../config.js';
|
|
3
|
-
import type { CreationOptions
|
|
2
|
+
import type { CreationOptions } from '../internal/filesystem.js';
|
|
4
3
|
import type { InodeLike } from '../internal/inode.js';
|
|
5
4
|
import type { Backend, FilesystemOf } from './backend.js';
|
|
6
5
|
import { FileSystem } from '../internal/filesystem.js';
|
|
7
6
|
import { Inode } from '../internal/inode.js';
|
|
7
|
+
import * as RPC from '../internal/rpc.js';
|
|
8
8
|
import '../polyfills.js';
|
|
9
|
-
|
|
10
|
-
data: T;
|
|
11
|
-
};
|
|
12
|
-
/** @internal */
|
|
13
|
-
export interface RPCPort {
|
|
14
|
-
postMessage(value: unknown, transfer?: TransferListItem[]): void;
|
|
15
|
-
on?(event: 'message' | 'online', listener: (value: unknown) => void): this;
|
|
16
|
-
off?(event: 'message', listener: (value: unknown) => void): this;
|
|
17
|
-
addEventListener?(type: 'message', listener: (ev: _MessageEvent) => void): void;
|
|
18
|
-
removeEventListener?(type: 'message', listener: (ev: _MessageEvent) => void): void;
|
|
19
|
-
}
|
|
9
|
+
export { RPC };
|
|
20
10
|
/**
|
|
21
|
-
* The options for the Port backend
|
|
22
11
|
* @category Backends and Configuration
|
|
23
12
|
*/
|
|
24
13
|
export interface PortOptions {
|
|
25
14
|
/**
|
|
26
15
|
* The target port that you want to connect to, or the current port if in a port context.
|
|
27
16
|
*/
|
|
28
|
-
port:
|
|
17
|
+
port: RPC.Channel;
|
|
29
18
|
/**
|
|
30
19
|
* How long to wait for a request to complete
|
|
31
20
|
*/
|
|
32
21
|
timeout?: number;
|
|
33
22
|
}
|
|
34
|
-
/**
|
|
35
|
-
* The API for remote procedure calls
|
|
36
|
-
* @category Internals
|
|
37
|
-
* @internal
|
|
38
|
-
*/
|
|
39
|
-
export interface RPCMethods {
|
|
40
|
-
usage(): UsageInfo;
|
|
41
|
-
ready(): void;
|
|
42
|
-
rename(oldPath: string, newPath: string): void;
|
|
43
|
-
createFile(path: string, options: CreationOptions): Uint8Array;
|
|
44
|
-
unlink(path: string): void;
|
|
45
|
-
rmdir(path: string): void;
|
|
46
|
-
mkdir(path: string, options: CreationOptions): Uint8Array;
|
|
47
|
-
readdir(path: string): string[];
|
|
48
|
-
touch(path: string, metadata: Uint8Array): void;
|
|
49
|
-
exists(path: string): boolean;
|
|
50
|
-
link(target: string, link: string): void;
|
|
51
|
-
sync(): void;
|
|
52
|
-
read(path: string, buffer: Uint8Array, start: number, end: number): Uint8Array;
|
|
53
|
-
write(path: string, buffer: Uint8Array, offset: number): void;
|
|
54
|
-
stat(path: string): Uint8Array;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* The methods that can be called on the RPC port
|
|
58
|
-
* @category Internals
|
|
59
|
-
* @internal
|
|
60
|
-
*/
|
|
61
|
-
export type RPCMethod = keyof RPCMethods;
|
|
62
|
-
/**
|
|
63
|
-
* An RPC message
|
|
64
|
-
* @category Internals
|
|
65
|
-
* @internal
|
|
66
|
-
*/
|
|
67
|
-
export interface RPCMessage {
|
|
68
|
-
_zenfs: true;
|
|
69
|
-
id: string;
|
|
70
|
-
method: RPCMethod;
|
|
71
|
-
stack: string;
|
|
72
|
-
}
|
|
73
|
-
interface RPCRequest<TMethod extends RPCMethod = RPCMethod> extends RPCMessage {
|
|
74
|
-
method: TMethod;
|
|
75
|
-
args: Parameters<RPCMethods[TMethod]>;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* An RPC executor
|
|
79
|
-
* @internal @hidden
|
|
80
|
-
*/
|
|
81
|
-
interface RPCExecutor extends PromiseWithResolvers<any> {
|
|
82
|
-
fs: PortFS;
|
|
83
|
-
timeout: ReturnType<typeof setTimeout>;
|
|
84
|
-
}
|
|
85
|
-
export declare function attach<T extends RPCMessage>(port: RPCPort, handler: (message: T) => unknown): void;
|
|
86
|
-
export declare function detach<T extends RPCMessage>(port: RPCPort, handler: (message: T) => unknown): void;
|
|
87
|
-
export declare function catchMessages<T extends Backend>(port: RPCPort): (fs: FilesystemOf<T>) => Promise<void>;
|
|
88
|
-
/**
|
|
89
|
-
* @internal
|
|
90
|
-
*/
|
|
91
|
-
export declare function waitOnline(port: RPCPort): Promise<void>;
|
|
92
23
|
declare const PortFS_base: import("../index.js").Mixin<typeof FileSystem, import("../mixins/async.js").AsyncMixin>;
|
|
93
24
|
/**
|
|
94
25
|
* PortFS lets you access an FS instance that is running in a port, or the other way around.
|
|
@@ -98,14 +29,15 @@ declare const PortFS_base: import("../index.js").Mixin<typeof FileSystem, import
|
|
|
98
29
|
* @category Internals
|
|
99
30
|
* @internal
|
|
100
31
|
*/
|
|
101
|
-
export declare class PortFS extends PortFS_base {
|
|
102
|
-
readonly
|
|
103
|
-
readonly
|
|
32
|
+
export declare class PortFS<T extends RPC.Channel = RPC.Channel> extends PortFS_base {
|
|
33
|
+
readonly channel: T;
|
|
34
|
+
readonly timeout: number;
|
|
35
|
+
readonly port: RPC.Port<T>;
|
|
104
36
|
/**
|
|
105
37
|
* A map of outstanding RPC requests
|
|
106
38
|
* @internal @hidden
|
|
107
39
|
*/
|
|
108
|
-
readonly _executors: Map<string,
|
|
40
|
+
readonly _executors: Map<string, RPC.Executor>;
|
|
109
41
|
/**
|
|
110
42
|
* @hidden
|
|
111
43
|
*/
|
|
@@ -113,8 +45,8 @@ export declare class PortFS extends PortFS_base {
|
|
|
113
45
|
/**
|
|
114
46
|
* Constructs a new PortFS instance that connects with the FS running on `options.port`.
|
|
115
47
|
*/
|
|
116
|
-
constructor(
|
|
117
|
-
protected rpc<const T extends
|
|
48
|
+
constructor(channel: T, timeout?: number);
|
|
49
|
+
protected rpc<const T extends RPC.Method>(method: T, ...args: Parameters<RPC.Methods[T]>): Promise<Awaited<ReturnType<RPC.Methods[T]>>>;
|
|
118
50
|
ready(): Promise<void>;
|
|
119
51
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
120
52
|
stat(path: string): Promise<Inode>;
|
|
@@ -130,17 +62,13 @@ export declare class PortFS extends PortFS_base {
|
|
|
130
62
|
read(path: string, buffer: Uint8Array, start: number, end: number): Promise<void>;
|
|
131
63
|
write(path: string, buffer: Uint8Array, offset: number): Promise<void>;
|
|
132
64
|
}
|
|
133
|
-
|
|
134
|
-
export declare function
|
|
135
|
-
_descriptors?: Map<number, File>;
|
|
136
|
-
}, request: RPCRequest): Promise<void>;
|
|
137
|
-
export declare function attachFS(port: RPCPort, fs: FileSystem): void;
|
|
138
|
-
export declare function detachFS(port: RPCPort, fs: FileSystem): void;
|
|
65
|
+
export declare function attachFS(channel: RPC.Channel | RPC.Port, fs: FileSystem): void;
|
|
66
|
+
export declare function detachFS(channel: RPC.Channel | RPC.Port, fs: FileSystem): void;
|
|
139
67
|
declare const _Port: {
|
|
140
68
|
name: string;
|
|
141
69
|
options: {
|
|
142
70
|
port: {
|
|
143
|
-
type:
|
|
71
|
+
type: string[];
|
|
144
72
|
required: true;
|
|
145
73
|
};
|
|
146
74
|
timeout: {
|
|
@@ -148,7 +76,7 @@ declare const _Port: {
|
|
|
148
76
|
required: false;
|
|
149
77
|
};
|
|
150
78
|
};
|
|
151
|
-
create(
|
|
79
|
+
create(opt: PortOptions): PortFS<RPC.Channel>;
|
|
152
80
|
};
|
|
153
81
|
type _Port = typeof _Port;
|
|
154
82
|
/**
|
|
@@ -216,5 +144,4 @@ export declare const Port: Port;
|
|
|
216
144
|
/**
|
|
217
145
|
* @category Backends and Configuration
|
|
218
146
|
*/
|
|
219
|
-
export declare function resolveRemoteMount<T extends Backend>(
|
|
220
|
-
export {};
|
|
147
|
+
export declare function resolveRemoteMount<T extends Backend>(channel: RPC.Channel | RPC.Port, config: MountConfiguration<T>, _depth?: number): Promise<FilesystemOf<T>>;
|
package/dist/backends/port.js
CHANGED
|
@@ -1,114 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { err, info } from 'kerium/log';
|
|
3
|
-
import { pick } from 'utilium';
|
|
1
|
+
import { info } from 'kerium/log';
|
|
4
2
|
import { resolveMountConfig } from '../config.js';
|
|
5
3
|
import { FileSystem } from '../internal/filesystem.js';
|
|
6
4
|
import { Inode } from '../internal/inode.js';
|
|
5
|
+
import * as RPC from '../internal/rpc.js';
|
|
7
6
|
import { Async } from '../mixins/async.js';
|
|
8
7
|
import '../polyfills.js';
|
|
9
|
-
import { _fnOpt } from './backend.js';
|
|
10
8
|
import { InMemory } from './memory.js';
|
|
11
|
-
|
|
12
|
-
return typeof arg == 'object' && arg != null && '_zenfs' in arg && !!arg._zenfs;
|
|
13
|
-
}
|
|
14
|
-
function disposeExecutors(id) {
|
|
15
|
-
const executor = executors.get(id);
|
|
16
|
-
if (!executor)
|
|
17
|
-
return;
|
|
18
|
-
if (executor.timeout) {
|
|
19
|
-
clearTimeout(executor.timeout);
|
|
20
|
-
if (typeof executor.timeout == 'object')
|
|
21
|
-
executor.timeout.unref();
|
|
22
|
-
}
|
|
23
|
-
executor.fs._executors.delete(id);
|
|
24
|
-
executors.delete(id);
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* A map of *all* outstanding RPC requests
|
|
28
|
-
*/
|
|
29
|
-
const executors = new Map();
|
|
30
|
-
function request(request, { port, timeout: ms = 1000, fs }) {
|
|
31
|
-
const stack = '\n' + new Error().stack.slice('Error:'.length);
|
|
32
|
-
if (!port)
|
|
33
|
-
throw err(withErrno('EINVAL', 'Can not make an RPC request without a port'));
|
|
34
|
-
const { resolve, reject, promise } = Promise.withResolvers();
|
|
35
|
-
const id = Math.random().toString(16).slice(10);
|
|
36
|
-
const timeout = setTimeout(() => {
|
|
37
|
-
const error = err(withErrno('EIO', 'RPC Failed'));
|
|
38
|
-
error.stack += stack;
|
|
39
|
-
disposeExecutors(id);
|
|
40
|
-
reject(error);
|
|
41
|
-
}, ms);
|
|
42
|
-
const executor = { resolve, reject, promise, fs, timeout };
|
|
43
|
-
fs._executors.set(id, executor);
|
|
44
|
-
executors.set(id, executor);
|
|
45
|
-
port.postMessage({ ...request, _zenfs: true, id, stack });
|
|
46
|
-
return promise;
|
|
47
|
-
}
|
|
48
|
-
// Why Typescript, WHY does the type need to be asserted even when the method is explicitly checked?
|
|
49
|
-
function __requestMethod(req) { }
|
|
50
|
-
function __responseMethod(res, ...t) {
|
|
51
|
-
return t.includes(res.method);
|
|
52
|
-
}
|
|
53
|
-
function handleResponse(response) {
|
|
54
|
-
if (!isRPCMessage(response))
|
|
55
|
-
return;
|
|
56
|
-
if (!executors.has(response.id)) {
|
|
57
|
-
const error = err(withErrno('EIO', 'Invalid RPC id: ' + response.id));
|
|
58
|
-
error.stack += response.stack;
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
const { resolve, reject } = executors.get(response.id);
|
|
62
|
-
if (response.error) {
|
|
63
|
-
const e = Exception.fromJSON({ code: 'EIO', errno: Errno.EIO, ...response.error });
|
|
64
|
-
e.stack += response.stack;
|
|
65
|
-
disposeExecutors(response.id);
|
|
66
|
-
reject(e);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
disposeExecutors(response.id);
|
|
70
|
-
resolve(__responseMethod(response, 'stat', 'createFile', 'mkdir') ? new Inode(response.value) : response.value);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
export function attach(port, handler) {
|
|
74
|
-
if (!port)
|
|
75
|
-
throw err(withErrno('EINVAL', 'Cannot attach to non-existent port'));
|
|
76
|
-
info('Attached handler to port: ' + handler.name);
|
|
77
|
-
port['on' in port ? 'on' : 'addEventListener']('message', (message) => {
|
|
78
|
-
handler(typeof message == 'object' && message !== null && 'data' in message ? message.data : message);
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
export function detach(port, handler) {
|
|
82
|
-
if (!port)
|
|
83
|
-
throw err(withErrno('EINVAL', 'Cannot detach from non-existent port'));
|
|
84
|
-
info('Detached handler from port: ' + handler.name);
|
|
85
|
-
port['off' in port ? 'off' : 'removeEventListener']('message', (message) => {
|
|
86
|
-
handler(typeof message == 'object' && message !== null && 'data' in message ? message.data : message);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
export function catchMessages(port) {
|
|
90
|
-
const events = [];
|
|
91
|
-
const handler = events.push.bind(events);
|
|
92
|
-
attach(port, handler);
|
|
93
|
-
return async function (fs) {
|
|
94
|
-
detach(port, handler);
|
|
95
|
-
for (const event of events) {
|
|
96
|
-
const request = 'data' in event ? event.data : event;
|
|
97
|
-
await handleRequest(port, fs, request);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* @internal
|
|
103
|
-
*/
|
|
104
|
-
export async function waitOnline(port) {
|
|
105
|
-
if (!('on' in port))
|
|
106
|
-
return; // Only need to wait in Node.js
|
|
107
|
-
const online = Promise.withResolvers();
|
|
108
|
-
setTimeout(online.reject, 500);
|
|
109
|
-
port.on('online', online.resolve);
|
|
110
|
-
await online.promise;
|
|
111
|
-
}
|
|
9
|
+
export { RPC };
|
|
112
10
|
/**
|
|
113
11
|
* PortFS lets you access an FS instance that is running in a port, or the other way around.
|
|
114
12
|
*
|
|
@@ -118,27 +16,32 @@ export async function waitOnline(port) {
|
|
|
118
16
|
* @internal
|
|
119
17
|
*/
|
|
120
18
|
export class PortFS extends Async(FileSystem) {
|
|
19
|
+
channel;
|
|
20
|
+
timeout;
|
|
21
|
+
port;
|
|
22
|
+
/**
|
|
23
|
+
* A map of outstanding RPC requests
|
|
24
|
+
* @internal @hidden
|
|
25
|
+
*/
|
|
26
|
+
_executors = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* @hidden
|
|
29
|
+
*/
|
|
30
|
+
_sync = InMemory.create({ label: 'tmpfs:port' });
|
|
121
31
|
/**
|
|
122
32
|
* Constructs a new PortFS instance that connects with the FS running on `options.port`.
|
|
123
33
|
*/
|
|
124
|
-
constructor(
|
|
34
|
+
constructor(channel, timeout = 250) {
|
|
125
35
|
super(0x706f7274, 'portfs');
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
*/
|
|
131
|
-
this._executors = new Map();
|
|
132
|
-
/**
|
|
133
|
-
* @hidden
|
|
134
|
-
*/
|
|
135
|
-
this._sync = InMemory.create({ label: 'tmpfs:port' });
|
|
136
|
-
this.port = options.port;
|
|
137
|
-
attach(this.port, handleResponse);
|
|
36
|
+
this.channel = channel;
|
|
37
|
+
this.timeout = timeout;
|
|
38
|
+
this.port = RPC.from(channel);
|
|
39
|
+
RPC.attach(this.port, RPC.handleResponse);
|
|
138
40
|
}
|
|
139
41
|
rpc(method, ...args) {
|
|
140
|
-
return request({ method, args }, {
|
|
141
|
-
|
|
42
|
+
return RPC.request({ method, args }, {
|
|
43
|
+
port: this.port,
|
|
44
|
+
timeout: this.timeout,
|
|
142
45
|
fs: this,
|
|
143
46
|
});
|
|
144
47
|
}
|
|
@@ -197,65 +100,25 @@ export class PortFS extends Async(FileSystem) {
|
|
|
197
100
|
return this.rpc('write', path, buffer, offset);
|
|
198
101
|
}
|
|
199
102
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return;
|
|
204
|
-
let value, error;
|
|
205
|
-
const transferList = [];
|
|
206
|
-
try {
|
|
207
|
-
switch (request.method) {
|
|
208
|
-
case 'read': {
|
|
209
|
-
__requestMethod(request);
|
|
210
|
-
const [path, buffer, start, end] = request.args;
|
|
211
|
-
await fs.read(path, buffer, start, end);
|
|
212
|
-
value = buffer;
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
case 'stat':
|
|
216
|
-
case 'createFile':
|
|
217
|
-
case 'mkdir': {
|
|
218
|
-
__requestMethod(request);
|
|
219
|
-
// @ts-expect-error 2556
|
|
220
|
-
const md = await fs[request.method](...request.args);
|
|
221
|
-
const inode = md instanceof Inode ? md : new Inode(md);
|
|
222
|
-
value = new Uint8Array(inode.buffer, inode.byteOffset, inode.byteLength);
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
case 'touch': {
|
|
226
|
-
__requestMethod(request);
|
|
227
|
-
const [path, metadata] = request.args;
|
|
228
|
-
await fs.touch(path, new Inode(metadata));
|
|
229
|
-
value = undefined;
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
default:
|
|
233
|
-
// @ts-expect-error 2556
|
|
234
|
-
value = (await fs[request.method](...request.args));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
catch (e) {
|
|
238
|
-
error = e instanceof Exception ? e.toJSON() : pick(e, 'message', 'stack');
|
|
239
|
-
}
|
|
240
|
-
port.postMessage({ _zenfs: true, ...pick(request, 'id', 'method', 'stack'), error, value }, transferList);
|
|
241
|
-
}
|
|
242
|
-
export function attachFS(port, fs) {
|
|
243
|
-
attach(port, request => handleRequest(port, fs, request));
|
|
103
|
+
export function attachFS(channel, fs) {
|
|
104
|
+
const port = RPC.from(channel);
|
|
105
|
+
RPC.attach(port, request => RPC.handleRequest(port, fs, request));
|
|
244
106
|
}
|
|
245
|
-
export function detachFS(
|
|
246
|
-
|
|
107
|
+
export function detachFS(channel, fs) {
|
|
108
|
+
const port = RPC.from(channel);
|
|
109
|
+
RPC.detach(port, request => RPC.handleRequest(port, fs, request));
|
|
247
110
|
}
|
|
248
111
|
const _Port = {
|
|
249
112
|
name: 'Port',
|
|
250
113
|
options: {
|
|
251
114
|
port: {
|
|
252
|
-
type:
|
|
115
|
+
type: ['Worker', 'MessagePort', 'WebSocket'],
|
|
253
116
|
required: true,
|
|
254
117
|
},
|
|
255
118
|
timeout: { type: 'number', required: false },
|
|
256
119
|
},
|
|
257
|
-
create(
|
|
258
|
-
return new PortFS(
|
|
120
|
+
create(opt) {
|
|
121
|
+
return new PortFS(opt.port, opt.timeout);
|
|
259
122
|
},
|
|
260
123
|
};
|
|
261
124
|
/**
|
|
@@ -318,8 +181,9 @@ export const Port = _Port;
|
|
|
318
181
|
/**
|
|
319
182
|
* @category Backends and Configuration
|
|
320
183
|
*/
|
|
321
|
-
export async function resolveRemoteMount(
|
|
322
|
-
const
|
|
184
|
+
export async function resolveRemoteMount(channel, config, _depth = 0) {
|
|
185
|
+
const port = RPC.from(channel);
|
|
186
|
+
const stopAndReplay = RPC.catchMessages(port);
|
|
323
187
|
const fs = await resolveMountConfig(config, _depth);
|
|
324
188
|
attachFS(port, fs);
|
|
325
189
|
await stopAndReplay(fs);
|
|
@@ -23,6 +23,7 @@ declare class MetadataEntry extends BufferView {
|
|
|
23
23
|
* This is done since IDs are not guaranteed to be sequential.
|
|
24
24
|
*/
|
|
25
25
|
export declare class MetadataBlock extends Int32Array<ArrayBufferLike> {
|
|
26
|
+
static readonly name = "MetadataBlock";
|
|
26
27
|
readonly ['constructor']: typeof MetadataBlock;
|
|
27
28
|
/**
|
|
28
29
|
* The crc32c checksum for the metadata block.
|
|
@@ -53,9 +54,10 @@ export declare class MetadataBlock extends Int32Array<ArrayBufferLike> {
|
|
|
53
54
|
/**
|
|
54
55
|
* The super block structure for a single-buffer file system
|
|
55
56
|
*/
|
|
56
|
-
export declare class SuperBlock extends
|
|
57
|
+
export declare class SuperBlock extends BigUint64Array<ArrayBufferLike> {
|
|
58
|
+
static readonly name = "SuperBlock";
|
|
57
59
|
readonly ['constructor']: typeof SuperBlock;
|
|
58
|
-
constructor(...args: ConstructorParameters<typeof
|
|
60
|
+
constructor(...args: ConstructorParameters<typeof BigUint64Array<ArrayBufferLike>>);
|
|
59
61
|
/**
|
|
60
62
|
* The crc32c checksum for the super block.
|
|
61
63
|
* @privateRemarks Keep this first!
|