@zenfs/core 2.2.3 → 2.3.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 +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 +2 -2
- package/dist/backends/single_buffer.js +165 -198
- 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.js +144 -187
- 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.js +44 -68
- 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 +2 -2
- package/scripts/test.js +16 -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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { ExceptionJSON } from 'kerium';
|
|
2
|
+
import type { TransferListItem } from 'node:worker_threads';
|
|
3
|
+
import type { WithOptional } from 'utilium';
|
|
4
|
+
import type { Backend, FilesystemOf } from '../backends/backend.js';
|
|
5
|
+
import type { PortFS } from '../backends/port.js';
|
|
6
|
+
import type { CreationOptions, FileSystem, UsageInfo } from '../internal/filesystem.js';
|
|
7
|
+
import '../polyfills.js';
|
|
8
|
+
export interface WebMessagePort {
|
|
9
|
+
postMessage(value: unknown, transfer?: TransferListItem[]): void;
|
|
10
|
+
addEventListener(type: 'message', listener: (ev: {
|
|
11
|
+
data: any;
|
|
12
|
+
}) => void): void;
|
|
13
|
+
removeEventListener(type: 'message', listener: (ev: {
|
|
14
|
+
data: any;
|
|
15
|
+
}) => void): void;
|
|
16
|
+
}
|
|
17
|
+
export interface NodeMessagePort {
|
|
18
|
+
postMessage(value: unknown, transfer?: TransferListItem[]): void;
|
|
19
|
+
on(type: 'message', listener: (ev: any) => void): void;
|
|
20
|
+
off(type: 'message', listener: (ev: any) => void): void;
|
|
21
|
+
}
|
|
22
|
+
export type Channel = NodeMessagePort | WebMessagePort | WebSocket;
|
|
23
|
+
/** @internal */
|
|
24
|
+
export interface Port<T extends Channel = Channel> {
|
|
25
|
+
readonly channel: T;
|
|
26
|
+
/** Send a request */
|
|
27
|
+
send<M extends Message>(message: M, transfer?: TransferListItem[]): void;
|
|
28
|
+
/** Add a response handler */
|
|
29
|
+
addHandler<M extends Message>(handler: (message: M) => void): void;
|
|
30
|
+
/** Remove a response handler */
|
|
31
|
+
removeHandler<M extends Message>(handler: (message: M) => void): void;
|
|
32
|
+
/** Remove all handlers */
|
|
33
|
+
disconnect?(): void;
|
|
34
|
+
}
|
|
35
|
+
export declare function isPort<T extends Channel>(port: unknown): port is Port<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new RPC port from a `Worker` or `MessagePort` that extends `EventTarget`
|
|
38
|
+
*/
|
|
39
|
+
export declare function fromWeb<T extends WebMessagePort>(port: T): Port<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Creates a new RPC port from a Node.js `Worker` or `MessagePort`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function fromNode<T extends NodeMessagePort>(port: T): Port<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Creates a new RPC port from a WebSocket.
|
|
46
|
+
* @experimental
|
|
47
|
+
*/
|
|
48
|
+
export declare function fromWebSocket<T extends WebSocket>(ws: T): Port<T>;
|
|
49
|
+
export declare function from<T extends Channel>(port: T | Port<T>): Port<T>;
|
|
50
|
+
/**
|
|
51
|
+
* The options for the RPC options
|
|
52
|
+
* @category Backends and Configuration
|
|
53
|
+
*/
|
|
54
|
+
export interface Options {
|
|
55
|
+
/**
|
|
56
|
+
* The target port that you want to connect to, or the current port if in a port context.
|
|
57
|
+
*/
|
|
58
|
+
port: Port;
|
|
59
|
+
/**
|
|
60
|
+
* How long to wait for a request to complete
|
|
61
|
+
*/
|
|
62
|
+
timeout?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The API for remote procedure calls
|
|
66
|
+
* @category Internals
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export interface Methods {
|
|
70
|
+
usage(): UsageInfo;
|
|
71
|
+
ready(): void;
|
|
72
|
+
rename(oldPath: string, newPath: string): void;
|
|
73
|
+
createFile(path: string, options: CreationOptions): Uint8Array;
|
|
74
|
+
unlink(path: string): void;
|
|
75
|
+
rmdir(path: string): void;
|
|
76
|
+
mkdir(path: string, options: CreationOptions): Uint8Array;
|
|
77
|
+
readdir(path: string): string[];
|
|
78
|
+
touch(path: string, metadata: Uint8Array): void;
|
|
79
|
+
exists(path: string): boolean;
|
|
80
|
+
link(target: string, link: string): void;
|
|
81
|
+
sync(): void;
|
|
82
|
+
read(path: string, buffer: Uint8Array, start: number, end: number): Uint8Array;
|
|
83
|
+
write(path: string, buffer: Uint8Array, offset: number): void;
|
|
84
|
+
stat(path: string): Uint8Array;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The methods that can be called on the RPC port
|
|
88
|
+
* @category Internals
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
export type Method = keyof Methods;
|
|
92
|
+
/**
|
|
93
|
+
* An RPC message
|
|
94
|
+
* @category Internals
|
|
95
|
+
* @internal
|
|
96
|
+
*/
|
|
97
|
+
export interface Message {
|
|
98
|
+
_zenfs: true;
|
|
99
|
+
id: string;
|
|
100
|
+
method: Method;
|
|
101
|
+
stack: string;
|
|
102
|
+
}
|
|
103
|
+
export interface Request<TMethod extends Method = Method> extends Message {
|
|
104
|
+
method: TMethod;
|
|
105
|
+
args: Parameters<Methods[TMethod]>;
|
|
106
|
+
}
|
|
107
|
+
export interface Response<TMethod extends Method = Method> extends Message {
|
|
108
|
+
error?: WithOptional<ExceptionJSON, 'code' | 'errno'>;
|
|
109
|
+
method: TMethod;
|
|
110
|
+
value: ReturnType<Methods[TMethod]>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Encode a RPC message as a string using JSON.
|
|
114
|
+
* This is only done when structured cloning is not available.
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
export declare function encodeMessage(message: Message): string;
|
|
118
|
+
/**
|
|
119
|
+
* Decode a RPC message from a string using JSON.
|
|
120
|
+
* This is only done when structured cloning is not available.
|
|
121
|
+
* @internal
|
|
122
|
+
*/
|
|
123
|
+
export declare function decodeMessage<T extends Message>(message: string): T;
|
|
124
|
+
export declare function isMessage(arg: unknown): arg is Message;
|
|
125
|
+
/**
|
|
126
|
+
* An RPC executor
|
|
127
|
+
* @internal @hidden
|
|
128
|
+
*/
|
|
129
|
+
export interface Executor extends PromiseWithResolvers<any> {
|
|
130
|
+
fs: PortFS;
|
|
131
|
+
timeout: ReturnType<typeof setTimeout>;
|
|
132
|
+
}
|
|
133
|
+
export declare function request<const TRequest extends Request, TValue>(request: Omit<TRequest, 'id' | 'stack' | '_zenfs'>, { port, timeout: ms, fs }: Partial<Options> & {
|
|
134
|
+
fs: PortFS;
|
|
135
|
+
}): Promise<TValue>;
|
|
136
|
+
export declare function handleResponse<const TMethod extends Method>(response: Response<TMethod>): void;
|
|
137
|
+
export declare function attach<T extends Message>(port: Port, handler: (message: T) => unknown): void;
|
|
138
|
+
export declare function detach<T extends Message>(port: Port, handler: (message: T) => unknown): void;
|
|
139
|
+
export declare function catchMessages<T extends Backend>(port: Port): (fs: FilesystemOf<T>) => Promise<void>;
|
|
140
|
+
/** @internal */
|
|
141
|
+
export declare function handleRequest(port: Port, fs: FileSystem & {
|
|
142
|
+
_descriptors?: Map<number, File>;
|
|
143
|
+
}, request: Request): Promise<void>;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { Errno, Exception, withErrno } from 'kerium';
|
|
2
|
+
import { err, info, warn } from 'kerium/log';
|
|
3
|
+
import { isJSON, pick } from 'utilium';
|
|
4
|
+
import { Inode } from '../internal/inode.js';
|
|
5
|
+
import '../polyfills.js';
|
|
6
|
+
export function isPort(port) {
|
|
7
|
+
return port != null && typeof port == 'object' && 'channel' in port && 'send' in port && 'addHandler' in port && 'removeHandler' in port;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new RPC port from a `Worker` or `MessagePort` that extends `EventTarget`
|
|
11
|
+
*/
|
|
12
|
+
export function fromWeb(port) {
|
|
13
|
+
return {
|
|
14
|
+
channel: port,
|
|
15
|
+
send: port.postMessage.bind(port),
|
|
16
|
+
addHandler(handler) {
|
|
17
|
+
port.addEventListener('message', (event) => handler(event.data));
|
|
18
|
+
},
|
|
19
|
+
removeHandler(handler) {
|
|
20
|
+
port.removeEventListener('message', (event) => handler(event.data));
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a new RPC port from a Node.js `Worker` or `MessagePort`.
|
|
26
|
+
*/
|
|
27
|
+
export function fromNode(port) {
|
|
28
|
+
return {
|
|
29
|
+
channel: port,
|
|
30
|
+
send: port.postMessage.bind(port),
|
|
31
|
+
addHandler: port.on.bind(port, 'message'),
|
|
32
|
+
removeHandler: port.off.bind(port, 'message'),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates a new RPC port from a WebSocket.
|
|
37
|
+
* @experimental
|
|
38
|
+
*/
|
|
39
|
+
export function fromWebSocket(ws) {
|
|
40
|
+
return {
|
|
41
|
+
channel: ws,
|
|
42
|
+
send(message) {
|
|
43
|
+
ws.send(encodeMessage(message));
|
|
44
|
+
},
|
|
45
|
+
addHandler(handler) {
|
|
46
|
+
ws.addEventListener('message', event => {
|
|
47
|
+
handler(decodeMessage(event.data));
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
removeHandler(handler) {
|
|
51
|
+
ws.removeEventListener('message', event => {
|
|
52
|
+
handler(decodeMessage(event.data));
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function from(port) {
|
|
58
|
+
if (isPort(port))
|
|
59
|
+
return port;
|
|
60
|
+
if (port instanceof WebSocket)
|
|
61
|
+
return fromWebSocket(port);
|
|
62
|
+
if ('on' in port)
|
|
63
|
+
return fromNode(port);
|
|
64
|
+
if ('addEventListener' in port)
|
|
65
|
+
return fromWeb(port);
|
|
66
|
+
throw err(withErrno('EINVAL', 'Invalid port type'));
|
|
67
|
+
}
|
|
68
|
+
/*
|
|
69
|
+
|
|
70
|
+
Notes on encoding:
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Buffer prefix ($):
|
|
74
|
+
|
|
75
|
+
Used to mark when a Uint8Array is encoded into JSON using base64.
|
|
76
|
+
These are encoded "special" since by default it becomes {"0":n,"1":n,...}
|
|
77
|
+
It shouldn't be possible to forge this since all paths start with / and inodes are serialized as buffers
|
|
78
|
+
|
|
79
|
+
Message prefix and version (Z...):
|
|
80
|
+
|
|
81
|
+
Used to indicate that this is a ZenFS message, rather than some 3rd party message.
|
|
82
|
+
Immediately following the message prefix is a plain-text version.
|
|
83
|
+
This is used in case the encoding changes in the future, so a client and server with mismatched versions can detect it.
|
|
84
|
+
*/
|
|
85
|
+
const encodingVersion = 1;
|
|
86
|
+
/**
|
|
87
|
+
* Encode a RPC message as a string using JSON.
|
|
88
|
+
* This is only done when structured cloning is not available.
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
export function encodeMessage(message) {
|
|
92
|
+
return `Z${encodingVersion}${JSON.stringify(message, (key, value) => {
|
|
93
|
+
if (key == '_zenfs')
|
|
94
|
+
return; // encoded differently
|
|
95
|
+
return value instanceof Uint8Array ? '$' + value.toBase64() : value;
|
|
96
|
+
})}`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Decode a RPC message from a string using JSON.
|
|
100
|
+
* This is only done when structured cloning is not available.
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
export function decodeMessage(message) {
|
|
104
|
+
if (!message.startsWith('Z'))
|
|
105
|
+
return {}; // ignore not-ZenFS messages
|
|
106
|
+
message = message.slice(1);
|
|
107
|
+
const v = parseInt(message); // hack so we don't have to figure out how long the version is
|
|
108
|
+
if (isNaN(v)) {
|
|
109
|
+
warn('Ignoring encoded message with missing version');
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
message = message.slice(v.toString().length);
|
|
113
|
+
if (!isJSON(message)) {
|
|
114
|
+
warn('Ignoring encoded message with invalid JSON');
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
if (v != encodingVersion)
|
|
118
|
+
throw err(withErrno('EPROTONOSUPPORT', `Version mismatch in RPC message encoding (got ${v}, expected ${encodingVersion})`));
|
|
119
|
+
return {
|
|
120
|
+
...JSON.parse(message, (key, value) => (typeof value == 'string' && value.startsWith('$') ? Uint8Array.fromBase64(value.slice(1)) : value)),
|
|
121
|
+
_zenfs: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export function isMessage(arg) {
|
|
125
|
+
return typeof arg == 'object' && arg != null && '_zenfs' in arg && !!arg._zenfs;
|
|
126
|
+
}
|
|
127
|
+
function disposeExecutors(id) {
|
|
128
|
+
const executor = executors.get(id);
|
|
129
|
+
if (!executor)
|
|
130
|
+
return;
|
|
131
|
+
if (executor.timeout) {
|
|
132
|
+
clearTimeout(executor.timeout);
|
|
133
|
+
if (typeof executor.timeout == 'object')
|
|
134
|
+
executor.timeout.unref();
|
|
135
|
+
}
|
|
136
|
+
executor.fs._executors.delete(id);
|
|
137
|
+
executors.delete(id);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A map of *all* outstanding RPC requests
|
|
141
|
+
*/
|
|
142
|
+
const executors = new Map();
|
|
143
|
+
export function request(request, { port, timeout: ms = 1000, fs }) {
|
|
144
|
+
const stack = '\n' + new Error().stack.slice('Error:'.length);
|
|
145
|
+
if (!port)
|
|
146
|
+
throw err(withErrno('EINVAL', 'Can not make an RPC request without a port'));
|
|
147
|
+
const { resolve, reject, promise } = Promise.withResolvers();
|
|
148
|
+
const id = Math.random().toString(16).slice(10);
|
|
149
|
+
const timeout = setTimeout(() => {
|
|
150
|
+
const error = err(withErrno('EIO', 'RPC Failed'));
|
|
151
|
+
error.stack += stack;
|
|
152
|
+
disposeExecutors(id);
|
|
153
|
+
reject(error);
|
|
154
|
+
}, ms);
|
|
155
|
+
const executor = { resolve, reject, promise, fs, timeout };
|
|
156
|
+
fs._executors.set(id, executor);
|
|
157
|
+
executors.set(id, executor);
|
|
158
|
+
port.send({ ...request, _zenfs: true, id, stack });
|
|
159
|
+
return promise;
|
|
160
|
+
}
|
|
161
|
+
// Why Typescript, WHY does the type need to be asserted even when the method is explicitly checked?
|
|
162
|
+
function __requestMethod(req) { }
|
|
163
|
+
function __responseMethod(res, ...t) {
|
|
164
|
+
return t.includes(res.method);
|
|
165
|
+
}
|
|
166
|
+
export function handleResponse(response) {
|
|
167
|
+
if (!isMessage(response))
|
|
168
|
+
return;
|
|
169
|
+
if (!executors.has(response.id)) {
|
|
170
|
+
const error = err(withErrno('EIO', 'Invalid RPC id: ' + response.id));
|
|
171
|
+
error.stack += response.stack;
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
const { resolve, reject } = executors.get(response.id);
|
|
175
|
+
if (response.error) {
|
|
176
|
+
const e = Exception.fromJSON({ code: 'EIO', errno: Errno.EIO, ...response.error });
|
|
177
|
+
e.stack += response.stack;
|
|
178
|
+
disposeExecutors(response.id);
|
|
179
|
+
reject(e);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
disposeExecutors(response.id);
|
|
183
|
+
resolve(__responseMethod(response, 'stat', 'createFile', 'mkdir') ? new Inode(response.value) : response.value);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
export function attach(port, handler) {
|
|
187
|
+
if (!port)
|
|
188
|
+
throw err(withErrno('EINVAL', 'Cannot attach to non-existent port'));
|
|
189
|
+
info('Attached handler to port: ' + handler.name);
|
|
190
|
+
port.addHandler(handler);
|
|
191
|
+
}
|
|
192
|
+
export function detach(port, handler) {
|
|
193
|
+
if (!port)
|
|
194
|
+
throw err(withErrno('EINVAL', 'Cannot detach from non-existent port'));
|
|
195
|
+
info('Detached handler from port: ' + handler.name);
|
|
196
|
+
port.removeHandler(handler);
|
|
197
|
+
}
|
|
198
|
+
export function catchMessages(port) {
|
|
199
|
+
const events = [];
|
|
200
|
+
const handler = events.push.bind(events);
|
|
201
|
+
attach(port, handler);
|
|
202
|
+
return async function (fs) {
|
|
203
|
+
detach(port, handler);
|
|
204
|
+
for (const event of events) {
|
|
205
|
+
const request = 'data' in event ? event.data : event;
|
|
206
|
+
await handleRequest(port, fs, request);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/** @internal */
|
|
211
|
+
export async function handleRequest(port, fs, request) {
|
|
212
|
+
if (!isMessage(request))
|
|
213
|
+
return;
|
|
214
|
+
let value, error;
|
|
215
|
+
const transferList = [];
|
|
216
|
+
try {
|
|
217
|
+
switch (request.method) {
|
|
218
|
+
case 'read': {
|
|
219
|
+
__requestMethod(request);
|
|
220
|
+
const [path, buffer, start, end] = request.args;
|
|
221
|
+
await fs.read(path, buffer, start, end);
|
|
222
|
+
value = buffer;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
case 'stat':
|
|
226
|
+
case 'createFile':
|
|
227
|
+
case 'mkdir': {
|
|
228
|
+
__requestMethod(request);
|
|
229
|
+
// @ts-expect-error 2556
|
|
230
|
+
const md = await fs[request.method](...request.args);
|
|
231
|
+
const inode = md instanceof Inode ? md : new Inode(md);
|
|
232
|
+
value = new Uint8Array(inode.buffer, inode.byteOffset, inode.byteLength);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case 'touch': {
|
|
236
|
+
__requestMethod(request);
|
|
237
|
+
const [path, metadata] = request.args;
|
|
238
|
+
await fs.touch(path, new Inode(metadata));
|
|
239
|
+
value = undefined;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
default:
|
|
243
|
+
// @ts-expect-error 2556
|
|
244
|
+
value = (await fs[request.method](...request.args));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
error = e instanceof Exception ? e.toJSON() : pick(e, 'message', 'stack');
|
|
249
|
+
}
|
|
250
|
+
port.send({ _zenfs: true, ...pick(request, 'id', 'method', 'stack'), error, value }, transferList);
|
|
251
|
+
}
|
package/dist/mixins/async.js
CHANGED
|
@@ -28,15 +28,15 @@ export function Async(FS) {
|
|
|
28
28
|
queueDone() {
|
|
29
29
|
return this.sync();
|
|
30
30
|
}
|
|
31
|
+
_promise = Promise.resolve();
|
|
31
32
|
_async(promise) {
|
|
32
33
|
this._promise = this._promise.then(() => promise);
|
|
33
34
|
}
|
|
35
|
+
_isInitialized = false;
|
|
36
|
+
/** Tracks how many updates to the sync. cache we skipped during initialization */
|
|
37
|
+
_skippedCacheUpdates = 0;
|
|
34
38
|
constructor(...args) {
|
|
35
39
|
super(...args);
|
|
36
|
-
this._promise = Promise.resolve();
|
|
37
|
-
this._isInitialized = false;
|
|
38
|
-
/** Tracks how many updates to the sync. cache we skipped during initialization */
|
|
39
|
-
this._skippedCacheUpdates = 0;
|
|
40
40
|
this._patchAsync();
|
|
41
41
|
}
|
|
42
42
|
async ready() {
|
|
@@ -191,7 +191,6 @@ export function Async(FS) {
|
|
|
191
191
|
// TS does not narrow the union based on the key
|
|
192
192
|
const originalMethod = this[key].bind(this);
|
|
193
193
|
this[key] = async (...args) => {
|
|
194
|
-
var _a, _b;
|
|
195
194
|
const result = await originalMethod(...args);
|
|
196
195
|
const stack = new Error().stack.split('\n').slice(2).join('\n');
|
|
197
196
|
// From the async queue
|
|
@@ -206,7 +205,7 @@ export function Async(FS) {
|
|
|
206
205
|
}
|
|
207
206
|
try {
|
|
208
207
|
// @ts-expect-error 2556 - The type of `args` is not narrowed
|
|
209
|
-
|
|
208
|
+
this._sync?.[`${key}Sync`]?.(...args);
|
|
210
209
|
}
|
|
211
210
|
catch (e) {
|
|
212
211
|
const stack = e.stack.split('\n').slice(3).join('\n');
|
package/dist/mixins/mutexed.js
CHANGED
|
@@ -58,17 +58,17 @@ import '../polyfills.js';
|
|
|
58
58
|
* @internal
|
|
59
59
|
*/
|
|
60
60
|
export class MutexLock {
|
|
61
|
+
previous;
|
|
62
|
+
current = Promise.withResolvers();
|
|
63
|
+
_isLocked = true;
|
|
61
64
|
get isLocked() {
|
|
62
65
|
return this._isLocked;
|
|
63
66
|
}
|
|
64
67
|
constructor(previous) {
|
|
65
68
|
this.previous = previous;
|
|
66
|
-
this.current = Promise.withResolvers();
|
|
67
|
-
this._isLocked = true;
|
|
68
69
|
}
|
|
69
70
|
async done() {
|
|
70
|
-
|
|
71
|
-
await ((_a = this.previous) === null || _a === void 0 ? void 0 : _a.done());
|
|
71
|
+
await this.previous?.done();
|
|
72
72
|
await this.current.promise;
|
|
73
73
|
}
|
|
74
74
|
unlock() {
|
|
@@ -84,6 +84,10 @@ export class MutexLock {
|
|
|
84
84
|
* @category Internals
|
|
85
85
|
*/
|
|
86
86
|
export class _MutexedFS {
|
|
87
|
+
/**
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
_fs;
|
|
87
91
|
get type() {
|
|
88
92
|
return this._fs.type;
|
|
89
93
|
}
|
|
@@ -114,6 +118,10 @@ export class _MutexedFS {
|
|
|
114
118
|
usage() {
|
|
115
119
|
return this._fs.usage();
|
|
116
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* The current locks
|
|
123
|
+
*/
|
|
124
|
+
currentLock;
|
|
117
125
|
/**
|
|
118
126
|
* Adds a lock for a path
|
|
119
127
|
*/
|
|
@@ -134,11 +142,11 @@ export class _MutexedFS {
|
|
|
134
142
|
setTimeout(() => {
|
|
135
143
|
if (lock.isLocked) {
|
|
136
144
|
const error = withErrno('EDEADLK');
|
|
137
|
-
error.stack += stack
|
|
145
|
+
error.stack += stack?.slice('Error'.length);
|
|
138
146
|
throw err(error);
|
|
139
147
|
}
|
|
140
148
|
}, 5000);
|
|
141
|
-
await
|
|
149
|
+
await previous?.done();
|
|
142
150
|
return lock;
|
|
143
151
|
}
|
|
144
152
|
/**
|
|
@@ -147,8 +155,7 @@ export class _MutexedFS {
|
|
|
147
155
|
* @internal
|
|
148
156
|
*/
|
|
149
157
|
lockSync() {
|
|
150
|
-
|
|
151
|
-
if ((_a = this.currentLock) === null || _a === void 0 ? void 0 : _a.isLocked) {
|
|
158
|
+
if (this.currentLock?.isLocked) {
|
|
152
159
|
throw err(withErrno('EBUSY'));
|
|
153
160
|
}
|
|
154
161
|
return this.addLock();
|
|
@@ -158,8 +165,7 @@ export class _MutexedFS {
|
|
|
158
165
|
* @internal
|
|
159
166
|
*/
|
|
160
167
|
get isLocked() {
|
|
161
|
-
|
|
162
|
-
return !!((_a = this.currentLock) === null || _a === void 0 ? void 0 : _a.isLocked);
|
|
168
|
+
return !!this.currentLock?.isLocked;
|
|
163
169
|
}
|
|
164
170
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
165
171
|
async rename(oldPath, newPath) {
|
package/dist/path.js
CHANGED
|
@@ -101,10 +101,9 @@ export function formatExt(ext) {
|
|
|
101
101
|
return ext ? `${ext[0] === '.' ? '' : '.'}${ext}` : '';
|
|
102
102
|
}
|
|
103
103
|
export function resolve(...parts) {
|
|
104
|
-
var _a;
|
|
105
104
|
let resolved = '';
|
|
106
|
-
for (const part of [...parts.reverse(),
|
|
107
|
-
if (!
|
|
105
|
+
for (const part of [...parts.reverse(), this?.pwd ?? defaultContext.pwd]) {
|
|
106
|
+
if (!part?.length)
|
|
108
107
|
continue;
|
|
109
108
|
resolved = `${part}/${resolved}`;
|
|
110
109
|
if (part.startsWith('/')) {
|
|
@@ -144,7 +143,7 @@ export function join(...parts) {
|
|
|
144
143
|
if (!parts.length)
|
|
145
144
|
return '.';
|
|
146
145
|
const joined = parts.join('/');
|
|
147
|
-
if (!
|
|
146
|
+
if (!joined?.length)
|
|
148
147
|
return '.';
|
|
149
148
|
return normalize(joined);
|
|
150
149
|
}
|
package/dist/polyfills.js
CHANGED
|
@@ -1,29 +1,58 @@
|
|
|
1
1
|
/* node:coverage disable */
|
|
2
2
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
3
|
-
var _a, _b, _c, _d;
|
|
4
|
-
var _e;
|
|
5
3
|
import { warn } from 'kerium/log';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
Promise.withResolvers ??=
|
|
5
|
+
(warn('Using a polyfill of Promise.withResolvers'),
|
|
6
|
+
function () {
|
|
7
|
+
let _resolve,
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
_reject;
|
|
10
|
+
const promise = new Promise((resolve, reject) => {
|
|
11
|
+
_resolve = resolve;
|
|
12
|
+
_reject = reject;
|
|
13
|
+
});
|
|
14
|
+
return { promise, resolve: _resolve, reject: _reject };
|
|
14
15
|
});
|
|
15
|
-
return { promise, resolve: _resolve, reject: _reject };
|
|
16
|
-
}));
|
|
17
16
|
// @ts-expect-error 2540
|
|
18
|
-
|
|
17
|
+
Symbol['dispose'] ??= (warn('Using a polyfill of Symbol.dispose'), Symbol('Symbol.dispose'));
|
|
19
18
|
// @ts-expect-error 2540
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
Symbol['asyncDispose'] ??= (warn('Using a polyfill of Symbol.asyncDispose'), Symbol('Symbol.asyncDispose'));
|
|
20
|
+
globalThis.crypto.randomUUID ??=
|
|
21
|
+
(warn('Using a polyfill of crypto.randomUUID'),
|
|
22
|
+
function randomUUID() {
|
|
23
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
24
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
25
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
26
|
+
const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');
|
|
27
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
28
|
+
});
|
|
29
|
+
Uint8Array.prototype.toBase64 ??=
|
|
30
|
+
(warn('Using a polyfill of Uint8Array.prototype.toBase64'),
|
|
31
|
+
function toBase64() {
|
|
32
|
+
return btoa(String.fromCharCode(...this));
|
|
33
|
+
});
|
|
34
|
+
Uint8Array.fromBase64 ??=
|
|
35
|
+
(warn('Using a polyfill of Uint8Array.fromBase64'),
|
|
36
|
+
function fromBase64(base64) {
|
|
37
|
+
const binaryString = atob(base64);
|
|
38
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
39
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
40
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
41
|
+
}
|
|
42
|
+
return bytes;
|
|
43
|
+
});
|
|
44
|
+
Uint8Array.prototype.toHex ??=
|
|
45
|
+
(warn('Using a polyfill of Uint8Array.prototype.toHex'),
|
|
46
|
+
function toHex() {
|
|
47
|
+
return [...this].map(b => b.toString(16).padStart(2, '0')).join('');
|
|
48
|
+
});
|
|
49
|
+
Uint8Array.fromHex ??=
|
|
50
|
+
(warn('Using a polyfill of Uint8Array.fromHex'),
|
|
51
|
+
function fromHex(hex) {
|
|
52
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
53
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
54
|
+
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
55
|
+
}
|
|
56
|
+
return bytes;
|
|
57
|
+
});
|
|
29
58
|
/* node:coverage enable */
|