@zenfs/core 2.3.9 → 2.3.11
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 +8 -1
- package/dist/backends/port.d.ts +0 -5
- package/dist/backends/port.js +1 -8
- package/dist/config.d.ts +1 -0
- package/dist/config.js +5 -1
- package/dist/internal/index_fs.d.ts +2 -0
- package/dist/internal/index_fs.js +46 -11
- package/dist/internal/rpc.js +7 -6
- package/dist/mixins/async.d.ts +1 -3
- package/dist/mixins/async.js +36 -26
- package/dist/mixins/mutexed.d.ts +1 -1
- package/dist/mixins/mutexed.js +2 -2
- package/package.json +2 -2
- package/tests/backend/port.test.ts +3 -3
- package/tests/common/mutex.test.ts +3 -3
- package/tests/common.ts +5 -0
- package/tests/fs/read.test.ts +4 -1
- package/tests/fs/scaling.test.ts +2 -0
- package/tests/setup/port.ts +4 -2
package/dist/backends/backend.js
CHANGED
|
@@ -21,6 +21,13 @@ export function _fnOpt(name, fn) {
|
|
|
21
21
|
Object.defineProperty(fn, 'name', { value: name });
|
|
22
22
|
return fn;
|
|
23
23
|
}
|
|
24
|
+
function _isClass(func) {
|
|
25
|
+
if (!(func && func.constructor === Function) || func.prototype === undefined)
|
|
26
|
+
return false;
|
|
27
|
+
if (Function.prototype !== Object.getPrototypeOf(func))
|
|
28
|
+
return true;
|
|
29
|
+
return Object.getOwnPropertyNames(func.prototype).length > 1;
|
|
30
|
+
}
|
|
24
31
|
/**
|
|
25
32
|
* Checks that `options` object is valid for the file system options.
|
|
26
33
|
* @category Backends and Configuration
|
|
@@ -41,7 +48,7 @@ export function checkOptions(backend, options) {
|
|
|
41
48
|
throw err(withErrno('EINVAL', 'Missing required option: ' + optName));
|
|
42
49
|
}
|
|
43
50
|
const isType = (type, _ = value) => typeof type == 'function'
|
|
44
|
-
?
|
|
51
|
+
? _isClass(type)
|
|
45
52
|
? value instanceof type
|
|
46
53
|
: type(value)
|
|
47
54
|
: typeof value === type || value?.constructor?.name === type;
|
package/dist/backends/port.d.ts
CHANGED
|
@@ -33,11 +33,6 @@ export declare class PortFS<T extends RPC.Channel = RPC.Channel> extends PortFS_
|
|
|
33
33
|
readonly channel: T;
|
|
34
34
|
readonly timeout: number;
|
|
35
35
|
readonly port: RPC.Port<T>;
|
|
36
|
-
/**
|
|
37
|
-
* A map of outstanding RPC requests
|
|
38
|
-
* @internal @hidden
|
|
39
|
-
*/
|
|
40
|
-
readonly _executors: Map<string, RPC.Executor>;
|
|
41
36
|
/**
|
|
42
37
|
* @hidden
|
|
43
38
|
*/
|
package/dist/backends/port.js
CHANGED
|
@@ -19,11 +19,6 @@ export class PortFS extends Async(FileSystem) {
|
|
|
19
19
|
channel;
|
|
20
20
|
timeout;
|
|
21
21
|
port;
|
|
22
|
-
/**
|
|
23
|
-
* A map of outstanding RPC requests
|
|
24
|
-
* @internal @hidden
|
|
25
|
-
*/
|
|
26
|
-
_executors = new Map();
|
|
27
22
|
/**
|
|
28
23
|
* @hidden
|
|
29
24
|
*/
|
|
@@ -61,10 +56,8 @@ export class PortFS extends Async(FileSystem) {
|
|
|
61
56
|
await this.rpc('touch', path, new Uint8Array(inode.buffer, inode.byteOffset, inode.byteLength));
|
|
62
57
|
}
|
|
63
58
|
async sync() {
|
|
59
|
+
await super.sync();
|
|
64
60
|
await this.rpc('sync');
|
|
65
|
-
for (const executor of this._executors.values()) {
|
|
66
|
-
await executor.promise.catch(() => { });
|
|
67
|
-
}
|
|
68
61
|
}
|
|
69
62
|
async createFile(path, options) {
|
|
70
63
|
if (options instanceof Inode)
|
package/dist/config.d.ts
CHANGED
|
@@ -87,3 +87,4 @@ export declare function addDevice(driver: DeviceDriver, options?: object): Devic
|
|
|
87
87
|
* @see Configuration
|
|
88
88
|
*/
|
|
89
89
|
export declare function configure<T extends ConfigMounts>(configuration: Partial<Configuration<T>>): Promise<void>;
|
|
90
|
+
export declare function sync(): Promise<void>;
|
package/dist/config.js
CHANGED
|
@@ -65,7 +65,7 @@ export async function resolveMountConfig(configuration, _depth = 0) {
|
|
|
65
65
|
* @category Backends and Configuration
|
|
66
66
|
*/
|
|
67
67
|
export async function configureSingle(configuration) {
|
|
68
|
-
if (!
|
|
68
|
+
if (!isMountConfig(configuration)) {
|
|
69
69
|
throw new TypeError('Invalid single mount point configuration');
|
|
70
70
|
}
|
|
71
71
|
const resolved = await resolveMountConfig(configuration);
|
|
@@ -137,3 +137,7 @@ export async function configure(configuration) {
|
|
|
137
137
|
await mount('/dev', devfs);
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
+
export async function sync() {
|
|
141
|
+
for (const fs of mounts.values())
|
|
142
|
+
await fs.sync();
|
|
143
|
+
}
|
|
@@ -30,6 +30,8 @@ export declare abstract class IndexFS extends FileSystem {
|
|
|
30
30
|
protected create(path: string, options: CreationOptions): Inode;
|
|
31
31
|
createFile(path: string, options: CreationOptions): Promise<InodeLike>;
|
|
32
32
|
createFileSync(path: string, options: CreationOptions): InodeLike;
|
|
33
|
+
protected _mkdir?(path: string, options: CreationOptions): Promise<void>;
|
|
34
|
+
protected _mkdirSync?(path: string, options: CreationOptions): void;
|
|
33
35
|
mkdir(path: string, options: CreationOptions): Promise<InodeLike>;
|
|
34
36
|
mkdirSync(path: string, options: CreationOptions): InodeLike;
|
|
35
37
|
link(target: string, link: string): Promise<void>;
|
|
@@ -38,31 +38,56 @@ export class IndexFS extends FileSystem {
|
|
|
38
38
|
to = to.slice(0, -1);
|
|
39
39
|
toRename.push({ from, to, inode });
|
|
40
40
|
}
|
|
41
|
+
toRename.sort((a, b) => b.from.length - a.from.length);
|
|
41
42
|
return toRename;
|
|
42
43
|
}
|
|
43
44
|
async rename(oldPath, newPath) {
|
|
44
45
|
if (oldPath == newPath)
|
|
45
46
|
return;
|
|
46
|
-
|
|
47
|
+
const toRename = this.pathsForRename(oldPath, newPath);
|
|
48
|
+
const contents = new Map();
|
|
49
|
+
for (const { from, to, inode } of toRename) {
|
|
47
50
|
const data = new Uint8Array(inode.size);
|
|
48
51
|
await this.read(from, data, 0, inode.size);
|
|
52
|
+
contents.set(to, data);
|
|
49
53
|
this.index.delete(from);
|
|
54
|
+
await this.remove(from);
|
|
55
|
+
if (this.index.has(to))
|
|
56
|
+
await this.remove(to);
|
|
57
|
+
}
|
|
58
|
+
toRename.reverse();
|
|
59
|
+
for (const { to, inode } of toRename) {
|
|
60
|
+
const data = contents.get(to);
|
|
50
61
|
this.index.set(to, inode);
|
|
51
|
-
|
|
62
|
+
if ((inode.mode & S_IFMT) == S_IFDIR)
|
|
63
|
+
await this._mkdir?.(to, inode);
|
|
64
|
+
else
|
|
65
|
+
await this.write(to, data, 0);
|
|
52
66
|
}
|
|
53
|
-
await this.remove(oldPath);
|
|
54
67
|
}
|
|
55
68
|
renameSync(oldPath, newPath) {
|
|
56
69
|
if (oldPath == newPath)
|
|
57
70
|
return;
|
|
58
|
-
|
|
71
|
+
const toRename = this.pathsForRename(oldPath, newPath);
|
|
72
|
+
const contents = new Map();
|
|
73
|
+
for (const { from, to, inode } of toRename) {
|
|
59
74
|
const data = new Uint8Array(inode.size);
|
|
60
75
|
this.readSync(from, data, 0, inode.size);
|
|
76
|
+
contents.set(to, data);
|
|
61
77
|
this.index.delete(from);
|
|
78
|
+
this.removeSync(from);
|
|
79
|
+
if (this.index.has(to))
|
|
80
|
+
this.removeSync(to);
|
|
81
|
+
}
|
|
82
|
+
toRename.reverse();
|
|
83
|
+
for (const { to, inode } of toRename) {
|
|
84
|
+
const data = contents.get(to);
|
|
62
85
|
this.index.set(to, inode);
|
|
63
|
-
|
|
86
|
+
if ((inode.mode & S_IFMT) == S_IFDIR)
|
|
87
|
+
this._mkdirSync?.(to, inode);
|
|
88
|
+
else
|
|
89
|
+
this.writeSync(to, data, 0);
|
|
64
90
|
}
|
|
65
|
-
this.removeSync(oldPath);
|
|
66
91
|
}
|
|
67
92
|
async stat(path) {
|
|
68
93
|
const inode = this.index.get(path);
|
|
@@ -93,9 +118,8 @@ export class IndexFS extends FileSystem {
|
|
|
93
118
|
throw withErrno('ENOTDIR');
|
|
94
119
|
if (isDir && isUnlink)
|
|
95
120
|
throw withErrno('EISDIR');
|
|
96
|
-
if (isDir
|
|
97
|
-
|
|
98
|
-
this.index.delete(path);
|
|
121
|
+
if (!isDir)
|
|
122
|
+
this.index.delete(path);
|
|
99
123
|
}
|
|
100
124
|
async unlink(path) {
|
|
101
125
|
this._remove(path, true);
|
|
@@ -107,10 +131,17 @@ export class IndexFS extends FileSystem {
|
|
|
107
131
|
}
|
|
108
132
|
async rmdir(path) {
|
|
109
133
|
this._remove(path, false);
|
|
134
|
+
const entries = await this.readdir(path);
|
|
135
|
+
if (entries.length)
|
|
136
|
+
throw withErrno('ENOTEMPTY');
|
|
137
|
+
this.index.delete(path);
|
|
110
138
|
await this.remove(path);
|
|
111
139
|
}
|
|
112
140
|
rmdirSync(path) {
|
|
113
141
|
this._remove(path, false);
|
|
142
|
+
if (this.readdirSync(path).length)
|
|
143
|
+
throw withErrno('ENOTEMPTY');
|
|
144
|
+
this.index.delete(path);
|
|
114
145
|
this.removeSync(path);
|
|
115
146
|
}
|
|
116
147
|
create(path, options) {
|
|
@@ -142,11 +173,15 @@ export class IndexFS extends FileSystem {
|
|
|
142
173
|
}
|
|
143
174
|
async mkdir(path, options) {
|
|
144
175
|
options.mode |= S_IFDIR;
|
|
145
|
-
|
|
176
|
+
const inode = this.create(path, options);
|
|
177
|
+
await this._mkdir?.(path, options);
|
|
178
|
+
return inode;
|
|
146
179
|
}
|
|
147
180
|
mkdirSync(path, options) {
|
|
148
181
|
options.mode |= S_IFDIR;
|
|
149
|
-
|
|
182
|
+
const inode = this.create(path, options);
|
|
183
|
+
this._mkdirSync?.(path, options);
|
|
184
|
+
return inode;
|
|
150
185
|
}
|
|
151
186
|
link(target, link) {
|
|
152
187
|
throw withErrno('ENOSYS');
|
package/dist/internal/rpc.js
CHANGED
|
@@ -10,14 +10,17 @@ export function isPort(port) {
|
|
|
10
10
|
* Creates a new RPC port from a `Worker` or `MessagePort` that extends `EventTarget`
|
|
11
11
|
*/
|
|
12
12
|
export function fromWeb(port) {
|
|
13
|
+
const _handlers = new Map();
|
|
13
14
|
return {
|
|
14
15
|
channel: port,
|
|
15
16
|
send: port.postMessage.bind(port),
|
|
16
17
|
addHandler(handler) {
|
|
17
|
-
|
|
18
|
+
const _handler = (event) => handler(event.data);
|
|
19
|
+
_handlers.set(handler, _handler);
|
|
20
|
+
port.addEventListener('message', _handler);
|
|
18
21
|
},
|
|
19
22
|
removeHandler(handler) {
|
|
20
|
-
port.removeEventListener('message', (
|
|
23
|
+
port.removeEventListener('message', _handlers.get(handler));
|
|
21
24
|
},
|
|
22
25
|
};
|
|
23
26
|
}
|
|
@@ -133,7 +136,6 @@ function disposeExecutors(id) {
|
|
|
133
136
|
if (typeof executor.timeout == 'object')
|
|
134
137
|
executor.timeout.unref();
|
|
135
138
|
}
|
|
136
|
-
executor.fs._executors.delete(id);
|
|
137
139
|
executors.delete(id);
|
|
138
140
|
}
|
|
139
141
|
/**
|
|
@@ -145,15 +147,14 @@ export function request(request, { port, timeout: ms = 1000, fs }) {
|
|
|
145
147
|
if (!port)
|
|
146
148
|
throw err(withErrno('EINVAL', 'Can not make an RPC request without a port'));
|
|
147
149
|
const { resolve, reject, promise } = Promise.withResolvers();
|
|
148
|
-
const id = Math.random().toString(16).slice(
|
|
150
|
+
const id = Math.random().toString(16).slice(5);
|
|
149
151
|
const timeout = setTimeout(() => {
|
|
150
|
-
const error = err(withErrno('
|
|
152
|
+
const error = err(withErrno('ETIMEDOUT', 'RPC request timed out'));
|
|
151
153
|
error.stack += stack;
|
|
152
154
|
disposeExecutors(id);
|
|
153
155
|
reject(error);
|
|
154
156
|
}, ms);
|
|
155
157
|
const executor = { resolve, reject, promise, fs, timeout };
|
|
156
|
-
fs._executors.set(id, executor);
|
|
157
158
|
executors.set(id, executor);
|
|
158
159
|
port.send({ ...request, _zenfs: true, id, stack });
|
|
159
160
|
return promise;
|
package/dist/mixins/async.d.ts
CHANGED
|
@@ -20,10 +20,8 @@ export interface AsyncMixin extends Pick<FileSystem, Exclude<_SyncFSKeys, 'exist
|
|
|
20
20
|
* @deprecated Use {@link sync | `sync`} instead
|
|
21
21
|
*/
|
|
22
22
|
queueDone(): Promise<void>;
|
|
23
|
-
/**
|
|
24
|
-
* @deprecated Use {@link sync | `sync`} instead
|
|
25
|
-
*/
|
|
26
23
|
ready(): Promise<void>;
|
|
24
|
+
sync(): Promise<void>;
|
|
27
25
|
}
|
|
28
26
|
/**
|
|
29
27
|
* Async() implements synchronous methods on an asynchronous file system
|
package/dist/mixins/async.js
CHANGED
|
@@ -29,8 +29,9 @@ export function Async(FS) {
|
|
|
29
29
|
return this.sync();
|
|
30
30
|
}
|
|
31
31
|
_promise = Promise.resolve();
|
|
32
|
-
_async(
|
|
33
|
-
|
|
32
|
+
_async(thunk) {
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
34
|
+
this._promise = this._promise.finally(() => thunk());
|
|
34
35
|
}
|
|
35
36
|
_isInitialized = false;
|
|
36
37
|
/** Tracks how many updates to the sync. cache we skipped during initialization */
|
|
@@ -41,9 +42,9 @@ export function Async(FS) {
|
|
|
41
42
|
}
|
|
42
43
|
async ready() {
|
|
43
44
|
await super.ready();
|
|
44
|
-
await this._promise;
|
|
45
45
|
if (this._isInitialized || this.attributes.has('no_async_preload'))
|
|
46
46
|
return;
|
|
47
|
+
await this._promise;
|
|
47
48
|
this.checkSync();
|
|
48
49
|
await this._sync.ready();
|
|
49
50
|
// optimization: for 2 storeFS', we copy at a lower abstraction level.
|
|
@@ -79,7 +80,7 @@ export function Async(FS) {
|
|
|
79
80
|
renameSync(oldPath, newPath) {
|
|
80
81
|
this.checkSync();
|
|
81
82
|
this._sync.renameSync(oldPath, newPath);
|
|
82
|
-
this._async(this.rename(oldPath, newPath));
|
|
83
|
+
this._async(() => this.rename(oldPath, newPath));
|
|
83
84
|
}
|
|
84
85
|
statSync(path) {
|
|
85
86
|
this.checkSync();
|
|
@@ -88,27 +89,29 @@ export function Async(FS) {
|
|
|
88
89
|
touchSync(path, metadata) {
|
|
89
90
|
this.checkSync();
|
|
90
91
|
this._sync.touchSync(path, metadata);
|
|
91
|
-
this._async(this.touch(path, metadata));
|
|
92
|
+
this._async(() => this.touch(path, metadata));
|
|
92
93
|
}
|
|
93
94
|
createFileSync(path, options) {
|
|
94
95
|
this.checkSync();
|
|
95
|
-
this.
|
|
96
|
-
|
|
96
|
+
const result = this._sync.createFileSync(path, options);
|
|
97
|
+
this._async(() => this.createFile(path, options));
|
|
98
|
+
return result;
|
|
97
99
|
}
|
|
98
100
|
unlinkSync(path) {
|
|
99
101
|
this.checkSync();
|
|
100
|
-
this._async(this.unlink(path));
|
|
101
102
|
this._sync.unlinkSync(path);
|
|
103
|
+
this._async(() => this.unlink(path));
|
|
102
104
|
}
|
|
103
105
|
rmdirSync(path) {
|
|
104
106
|
this.checkSync();
|
|
105
107
|
this._sync.rmdirSync(path);
|
|
106
|
-
this._async(this.rmdir(path));
|
|
108
|
+
this._async(() => this.rmdir(path));
|
|
107
109
|
}
|
|
108
110
|
mkdirSync(path, options) {
|
|
109
111
|
this.checkSync();
|
|
110
|
-
this.
|
|
111
|
-
|
|
112
|
+
const result = this._sync.mkdirSync(path, options);
|
|
113
|
+
this._async(() => this.mkdir(path, options));
|
|
114
|
+
return result;
|
|
112
115
|
}
|
|
113
116
|
readdirSync(path) {
|
|
114
117
|
this.checkSync();
|
|
@@ -117,12 +120,12 @@ export function Async(FS) {
|
|
|
117
120
|
linkSync(srcpath, dstpath) {
|
|
118
121
|
this.checkSync();
|
|
119
122
|
this._sync.linkSync(srcpath, dstpath);
|
|
120
|
-
this._async(this.link(srcpath, dstpath));
|
|
123
|
+
this._async(() => this.link(srcpath, dstpath));
|
|
121
124
|
}
|
|
122
125
|
async sync() {
|
|
123
126
|
if (!this.attributes.has('no_async_preload') && this._sync)
|
|
124
127
|
this._sync.syncSync();
|
|
125
|
-
await this._promise;
|
|
128
|
+
await this._promise.catch(() => { });
|
|
126
129
|
}
|
|
127
130
|
syncSync() {
|
|
128
131
|
this.checkSync();
|
|
@@ -139,7 +142,7 @@ export function Async(FS) {
|
|
|
139
142
|
writeSync(path, buffer, offset) {
|
|
140
143
|
this.checkSync();
|
|
141
144
|
this._sync.writeSync(path, buffer, offset);
|
|
142
|
-
this._async(this.write(path, buffer, offset));
|
|
145
|
+
this._async(() => this.write(path, buffer, offset));
|
|
143
146
|
}
|
|
144
147
|
streamWrite(path, options) {
|
|
145
148
|
this.checkSync();
|
|
@@ -186,18 +189,27 @@ export function Async(FS) {
|
|
|
186
189
|
* Patch all async methods to also call their synchronous counterparts unless called from themselves (either sync or async)
|
|
187
190
|
*/
|
|
188
191
|
_patchAsync() {
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
const noPatch = ['read', 'readdir', 'stat', 'exists'];
|
|
193
|
+
const toPatch = _asyncFSKeys.filter(key => !noPatch.includes(key));
|
|
194
|
+
for (const key of toPatch) {
|
|
191
195
|
// TS does not narrow the union based on the key
|
|
192
196
|
const originalMethod = this[key].bind(this);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
197
|
+
function isInLoop(depth, error) {
|
|
198
|
+
if (!error) {
|
|
199
|
+
error = new Error();
|
|
200
|
+
Error.captureStackTrace(error, isInLoop);
|
|
201
|
+
}
|
|
202
|
+
if (!error.stack)
|
|
203
|
+
return false;
|
|
204
|
+
const stack = error.stack.split('\n').slice(depth).join('\n');
|
|
196
205
|
// From the async queue
|
|
197
|
-
|
|
198
|
-
|| stack.includes(`at <computed> [as ${key}]`)
|
|
206
|
+
return (stack.includes(`at <computed> [as ${key}]`)
|
|
199
207
|
|| stack.includes(`at async <computed> [as ${key}]`)
|
|
200
|
-
|| stack.includes(`${key}Sync `))
|
|
208
|
+
|| stack.includes(`${key}Sync `));
|
|
209
|
+
}
|
|
210
|
+
this[key] = async (...args) => {
|
|
211
|
+
const result = await originalMethod(...args);
|
|
212
|
+
if (isInLoop(2))
|
|
201
213
|
return result;
|
|
202
214
|
if (!this._isInitialized) {
|
|
203
215
|
this._skippedCacheUpdates++;
|
|
@@ -208,10 +220,7 @@ export function Async(FS) {
|
|
|
208
220
|
this._sync?.[`${key}Sync`]?.(...args);
|
|
209
221
|
}
|
|
210
222
|
catch (e) {
|
|
211
|
-
|
|
212
|
-
if (stack.includes(`at <computed> [as ${key}]`)
|
|
213
|
-
|| stack.includes(`at async <computed> [as ${key}]`)
|
|
214
|
-
|| stack.includes(`${key}Sync `))
|
|
223
|
+
if (isInLoop(3, e))
|
|
215
224
|
return result;
|
|
216
225
|
e.message += ' (Out of sync!)';
|
|
217
226
|
throw err(e);
|
|
@@ -219,6 +228,7 @@ export function Async(FS) {
|
|
|
219
228
|
return result;
|
|
220
229
|
};
|
|
221
230
|
}
|
|
231
|
+
debug(`Async: patched ${toPatch.length} methods`);
|
|
222
232
|
}
|
|
223
233
|
}
|
|
224
234
|
return AsyncFS;
|
package/dist/mixins/mutexed.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export declare class _MutexedFS<T extends FileSystem> implements FileSystem {
|
|
|
49
49
|
* If the path is currently locked, waits for it to be unlocked.
|
|
50
50
|
* @internal
|
|
51
51
|
*/
|
|
52
|
-
lock(): Promise<MutexLock>;
|
|
52
|
+
lock(timeout?: number): Promise<MutexLock>;
|
|
53
53
|
/**
|
|
54
54
|
* Locks `path` asynchronously.
|
|
55
55
|
* If the path is currently locked, an error will be thrown
|
package/dist/mixins/mutexed.js
CHANGED
|
@@ -135,7 +135,7 @@ export class _MutexedFS {
|
|
|
135
135
|
* If the path is currently locked, waits for it to be unlocked.
|
|
136
136
|
* @internal
|
|
137
137
|
*/
|
|
138
|
-
async lock() {
|
|
138
|
+
async lock(timeout = 5000) {
|
|
139
139
|
const previous = this.currentLock;
|
|
140
140
|
const lock = this.addLock();
|
|
141
141
|
const stack = new Error().stack;
|
|
@@ -145,7 +145,7 @@ export class _MutexedFS {
|
|
|
145
145
|
error.stack += stack?.slice('Error'.length);
|
|
146
146
|
throw err(error);
|
|
147
147
|
}
|
|
148
|
-
},
|
|
148
|
+
}, timeout);
|
|
149
149
|
await previous?.done();
|
|
150
150
|
return lock;
|
|
151
151
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.11",
|
|
4
4
|
"description": "A filesystem, anywhere",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"format": "prettier --write .",
|
|
61
61
|
"format:check": "prettier --check .",
|
|
62
62
|
"lint": "eslint src tests",
|
|
63
|
-
"test": "npx zenfs-test --clean; npx zenfs-test -
|
|
63
|
+
"test": "npx zenfs-test --clean; npx zenfs-test -abcp; tests/fetch/run.sh; npx zenfs-test --report",
|
|
64
64
|
"build": "tsc -p tsconfig.json",
|
|
65
65
|
"build:docs": "typedoc",
|
|
66
66
|
"dev": "npm run build -- --watch",
|
|
@@ -12,7 +12,7 @@ setupLogs();
|
|
|
12
12
|
const timeoutChannel = new MessageChannel();
|
|
13
13
|
timeoutChannel.port2.unref();
|
|
14
14
|
|
|
15
|
-
await suite('Timeout',
|
|
15
|
+
await suite('Timeout', () => {
|
|
16
16
|
test('Misconfiguration', async () => {
|
|
17
17
|
const configured = configure({
|
|
18
18
|
mounts: {
|
|
@@ -21,13 +21,13 @@ await suite('Timeout', { timeout: 1000 }, () => {
|
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
await assert.rejects(configured, { code: '
|
|
24
|
+
await assert.rejects(configured, { code: 'ETIMEDOUT' });
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
test('Remote not attached', async () => {
|
|
28
28
|
const configured = configureSingle({ backend: Port, port: timeoutChannel.port1, timeout: 100 });
|
|
29
29
|
|
|
30
|
-
await assert.rejects(configured, { code: '
|
|
30
|
+
await assert.rejects(configured, { code: 'ETIMEDOUT' });
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
after(() => {
|
|
@@ -20,11 +20,11 @@ suite('Mutexed FS', () => {
|
|
|
20
20
|
let lock1Resolved = false;
|
|
21
21
|
let lock2Resolved = false;
|
|
22
22
|
|
|
23
|
-
const lock1 = fs.lock().then(lock => {
|
|
23
|
+
const lock1 = fs.lock(100).then(lock => {
|
|
24
24
|
lock1Resolved = true;
|
|
25
25
|
lock.unlock();
|
|
26
26
|
});
|
|
27
|
-
const lock2 = fs.lock().then(lock => {
|
|
27
|
+
const lock2 = fs.lock(100).then(lock => {
|
|
28
28
|
lock2Resolved = true;
|
|
29
29
|
lock.unlock();
|
|
30
30
|
});
|
|
@@ -50,7 +50,7 @@ suite('Mutexed FS', () => {
|
|
|
50
50
|
let x = 1;
|
|
51
51
|
|
|
52
52
|
async function foo() {
|
|
53
|
-
const lock = await fs.lock();
|
|
53
|
+
const lock = await fs.lock(100);
|
|
54
54
|
await wait(25);
|
|
55
55
|
x++;
|
|
56
56
|
lock.unlock();
|
package/tests/common.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { join, resolve } from 'node:path';
|
|
2
2
|
import { fs as defaultFS } from '../dist/index.js';
|
|
3
3
|
import { setupLogs } from './logs.js';
|
|
4
|
+
import { styleText } from 'node:util';
|
|
4
5
|
export type * from '../dist/index.js';
|
|
5
6
|
|
|
6
7
|
setupLogs();
|
|
7
8
|
|
|
8
9
|
const setupPath = resolve(process.env.SETUP || join(import.meta.dirname, 'setup/memory.ts'));
|
|
9
10
|
|
|
11
|
+
process.on('unhandledRejection', (reason: Error) => {
|
|
12
|
+
console.error('Unhandled rejection:', styleText('red', reason.stack || reason.message));
|
|
13
|
+
});
|
|
14
|
+
|
|
10
15
|
const setup = await import(setupPath).catch(error => {
|
|
11
16
|
console.log('Failed to import test setup:');
|
|
12
17
|
throw error;
|
package/tests/fs/read.test.ts
CHANGED
|
@@ -3,7 +3,8 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import type { OpenMode, PathLike } from 'node:fs';
|
|
4
4
|
import { suite, test } from 'node:test';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
-
import {
|
|
6
|
+
import { sync } from '../../dist/config.js';
|
|
7
|
+
import { fs } from '../common.js';
|
|
7
8
|
|
|
8
9
|
const filepath = 'x.txt';
|
|
9
10
|
const expected = 'xyz\n';
|
|
@@ -73,6 +74,8 @@ suite('read', () => {
|
|
|
73
74
|
const path = '/text.txt';
|
|
74
75
|
|
|
75
76
|
fs.writeFileSync(path, 'hello world');
|
|
77
|
+
await sync();
|
|
78
|
+
|
|
76
79
|
const fd: number = (await promisify<PathLike, OpenMode, number | string>(fs.open)(path, 0, 0)) as any;
|
|
77
80
|
|
|
78
81
|
const read = promisify(fs.read);
|
package/tests/fs/scaling.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
|
+
import { sync } from '../../dist/config.js';
|
|
3
4
|
import { fs } from '../common.js';
|
|
4
5
|
|
|
5
6
|
const n_files = 130;
|
|
@@ -14,6 +15,7 @@ suite('Scaling', () => {
|
|
|
14
15
|
fs.writeFileSync('/n/' + i, i.toString(16));
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
await sync();
|
|
17
19
|
assert.equal(fs.readdirSync('/n').length, n_files);
|
|
18
20
|
|
|
19
21
|
const results = [];
|
package/tests/setup/port.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { after } from 'node:test';
|
|
1
|
+
import { after, afterEach } from 'node:test';
|
|
2
2
|
import { MessageChannel } from 'node:worker_threads';
|
|
3
|
-
import { InMemory, Port, configureSingle, fs, resolveMountConfig, resolveRemoteMount } from '../../dist/index.js';
|
|
3
|
+
import { InMemory, Port, configureSingle, fs, resolveMountConfig, resolveRemoteMount, sync } from '../../dist/index.js';
|
|
4
4
|
import { copySync, data } from '../setup.js';
|
|
5
5
|
|
|
6
6
|
const { port1: localPort, port2: remotePort } = new MessageChannel();
|
|
@@ -15,6 +15,8 @@ await resolveRemoteMount(remotePort, tmpfs);
|
|
|
15
15
|
|
|
16
16
|
await configureSingle({ backend: Port, port: localPort });
|
|
17
17
|
|
|
18
|
+
afterEach(sync);
|
|
19
|
+
|
|
18
20
|
after(() => {
|
|
19
21
|
localPort.close();
|
|
20
22
|
remotePort.close();
|