@zenfs/core 2.5.8 → 2.6.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/cow.d.ts +17 -0
- package/dist/backends/cow.js +72 -25
- package/dist/backends/fetch.d.ts +4 -0
- package/dist/backends/fetch.js +7 -3
- package/dist/backends/single_buffer.d.ts +2 -2
- package/dist/backends/store/fs.d.ts +6 -6
- package/dist/backends/store/fs.js +10 -2
- package/dist/internal/devices.d.ts +6 -6
- package/dist/internal/index_fs.d.ts +4 -4
- package/dist/node/async.js +1 -1
- package/dist/node/dir.d.ts +3 -1
- package/dist/node/dir.js +23 -5
- package/dist/node/promises.d.ts +3 -1
- package/dist/node/promises.js +128 -89
- package/dist/node/streams.d.ts +4 -0
- package/dist/node/streams.js +9 -10
- package/dist/node/sync.js +71 -40
- package/dist/utils.d.ts +8 -1
- package/dist/utils.js +26 -7
- package/dist/vfs/async.js +224 -85
- package/dist/vfs/file.d.ts +13 -7
- package/dist/vfs/file.js +661 -357
- package/dist/vfs/shared.d.ts +1 -0
- package/dist/vfs/shared.js +18 -1
- package/dist/vfs/sync.js +214 -85
- package/dist/vfs/vcache.d.ts +66 -0
- package/dist/vfs/vcache.js +249 -0
- package/dist/vfs/vnode.d.ts +72 -0
- package/dist/vfs/vnode.js +215 -0
- package/dist/vfs/watchers.js +4 -2
- package/dist/vfs/xattr.js +2 -2
- package/eslint.shared.js +1 -1
- package/package.json +3 -3
- package/scripts/make-index.js +43 -16
- package/scripts/test.js +28 -21
- package/tests/backend/fetch.test.ts +24 -1
- package/tests/backend/port.test.ts +3 -3
- package/tests/backend/store.test.ts +49 -0
- package/tests/common.ts +60 -0
- package/tests/fetch/fetch.ts +6 -0
- package/tests/fetch/run.sh +2 -2
- package/tests/fetch/server.js +3 -3
- package/tests/fs/append.test.ts +5 -5
- package/tests/fs/concurrency.test.ts +90 -0
- package/tests/fs/dir.test.ts +13 -15
- package/tests/fs/directory.test.ts +32 -31
- package/tests/fs/errors.test.ts +151 -31
- package/tests/fs/exists.test.ts +4 -4
- package/tests/fs/links.test.ts +4 -11
- package/tests/fs/open.test.ts +4 -4
- package/tests/fs/permissions.test.ts +15 -15
- package/tests/fs/read.test.ts +9 -9
- package/tests/fs/readFile.test.ts +9 -9
- package/tests/fs/rename.test.ts +8 -8
- package/tests/fs/scaling.test.ts +4 -4
- package/tests/fs/stat.test.ts +20 -20
- package/tests/fs/streams.test.ts +9 -6
- package/tests/fs/tempdir.test.ts +33 -10
- package/tests/fs/times.test.ts +11 -11
- package/tests/fs/truncate.test.ts +6 -6
- package/tests/fs/watch.test.ts +107 -53
- package/tests/fs/write.test.ts +10 -10
- package/tests/fs/xattr.test.ts +3 -3
- package/tests/native/run.sh +3 -0
- package/tests/native/setup.ts +230 -0
- package/types/readable-stream.d.ts +2 -2
package/dist/backends/cow.d.ts
CHANGED
|
@@ -70,6 +70,19 @@ export declare class CopyOnWriteFS extends FileSystem {
|
|
|
70
70
|
/** The journal to use for persisting deletions */
|
|
71
71
|
journal?: Journal);
|
|
72
72
|
isDeleted(path: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Inode numbers presented to the VFS for each path.
|
|
75
|
+
*
|
|
76
|
+
* The readable and writable layers allocate inode numbers independently, so they can collide.
|
|
77
|
+
* They can not be used as-is, since the VFS uses `ino` to tell files apart.
|
|
78
|
+
* This also keeps a file's inode number stable when it is copied up to the writable layer.
|
|
79
|
+
*/
|
|
80
|
+
protected inos: Map<string, number>;
|
|
81
|
+
private _nextIno;
|
|
82
|
+
/** Moves the public inode numbers for a rename, including a directory's descendants */
|
|
83
|
+
protected renameInos(oldPath: string, newPath: string): void;
|
|
84
|
+
/** Replaces the layer's inode number with the public one for `path` */
|
|
85
|
+
protected remapIno(path: string, inode: InodeLike): InodeLike;
|
|
73
86
|
/**
|
|
74
87
|
* @todo Consider trying to track information on the writable as well
|
|
75
88
|
*/
|
|
@@ -114,11 +127,15 @@ export declare class CopyOnWriteFS extends FileSystem {
|
|
|
114
127
|
* Helper function:
|
|
115
128
|
* - Ensures p is on writable before proceeding. Throws an error if it doesn't exist.
|
|
116
129
|
* - Calls f to perform operation on writable.
|
|
130
|
+
*
|
|
131
|
+
* `keep` is the size the file is about to be truncated to, when the caller knows it.
|
|
132
|
+
* Bytes past it are discarded by the operation that follows, so they are not worth copying up.
|
|
117
133
|
*/
|
|
118
134
|
private copyForWriteSync;
|
|
119
135
|
private copyForWrite;
|
|
120
136
|
/**
|
|
121
137
|
* Copy from readable to writable storage.
|
|
138
|
+
* Only `[0, keep)` is copied; the caller is responsible for setting the final size.
|
|
122
139
|
* PRECONDITION: File does not exist on writable storage.
|
|
123
140
|
*/
|
|
124
141
|
private copyToWritableSync;
|
package/dist/backends/cow.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { EventEmitter } from 'eventemitter3';
|
|
2
2
|
import { withErrno } from 'kerium';
|
|
3
3
|
import { debug, err, warn } from 'kerium/log';
|
|
4
|
-
import { canary } from 'utilium';
|
|
4
|
+
import { canary, pick } from 'utilium';
|
|
5
5
|
import { resolveMountConfig } from '../config.js';
|
|
6
6
|
import { FileSystem } from '../internal/filesystem.js';
|
|
7
|
-
import { isDirectory } from '../internal/inode.js';
|
|
7
|
+
import { _inode_fields, isDirectory } from '../internal/inode.js';
|
|
8
8
|
import { dirname, join } from '../path.js';
|
|
9
9
|
const journalOperations = new Set(['delete']);
|
|
10
10
|
/** Because TS doesn't work right w/o it */
|
|
11
11
|
function isJournalOp(op) {
|
|
12
12
|
return journalOperations.has(op);
|
|
13
13
|
}
|
|
14
|
-
const maxOpLength = Math.max(...journalOperations.
|
|
14
|
+
const maxOpLength = Math.max(...[...journalOperations].map(op => op.length));
|
|
15
15
|
const journalMagicString = '#journal@v0\n';
|
|
16
16
|
/**
|
|
17
17
|
* Tracks various operations for the CoW backend
|
|
@@ -102,6 +102,35 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
102
102
|
isDeleted(path) {
|
|
103
103
|
return this.journal.isDeleted(path);
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Inode numbers presented to the VFS for each path.
|
|
107
|
+
*
|
|
108
|
+
* The readable and writable layers allocate inode numbers independently, so they can collide.
|
|
109
|
+
* They can not be used as-is, since the VFS uses `ino` to tell files apart.
|
|
110
|
+
* This also keeps a file's inode number stable when it is copied up to the writable layer.
|
|
111
|
+
*/
|
|
112
|
+
inos = new Map();
|
|
113
|
+
_nextIno = 1;
|
|
114
|
+
/** Moves the public inode numbers for a rename, including a directory's descendants */
|
|
115
|
+
renameInos(oldPath, newPath) {
|
|
116
|
+
for (const [path, ino] of [...this.inos]) {
|
|
117
|
+
if (path != oldPath && !path.startsWith(oldPath + '/'))
|
|
118
|
+
continue;
|
|
119
|
+
this.inos.delete(path);
|
|
120
|
+
this.inos.set(newPath + path.slice(oldPath.length), ino);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Replaces the layer's inode number with the public one for `path` */
|
|
124
|
+
remapIno(path, inode) {
|
|
125
|
+
let ino = this.inos.get(path);
|
|
126
|
+
if (ino === undefined) {
|
|
127
|
+
ino = this._nextIno++;
|
|
128
|
+
this.inos.set(path, ino);
|
|
129
|
+
}
|
|
130
|
+
/* Note the layer's inode is not mutated, since it may be a persistent object.
|
|
131
|
+
`Inode` fields are accessors, so a copy must be explicit rather than a spread. */
|
|
132
|
+
return { ...pick(inode, _inode_fields), attributes: inode.attributes, ino };
|
|
133
|
+
}
|
|
105
134
|
/**
|
|
106
135
|
* @todo Consider trying to track information on the writable as well
|
|
107
136
|
*/
|
|
@@ -139,6 +168,7 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
139
168
|
if (this.isDeleted(oldPath))
|
|
140
169
|
throw withErrno('ENOENT');
|
|
141
170
|
}
|
|
171
|
+
this.renameInos(oldPath, newPath);
|
|
142
172
|
}
|
|
143
173
|
renameSync(oldPath, newPath) {
|
|
144
174
|
this.copyForWriteSync(oldPath);
|
|
@@ -149,42 +179,43 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
149
179
|
if (this.isDeleted(oldPath))
|
|
150
180
|
throw withErrno('ENOENT');
|
|
151
181
|
}
|
|
182
|
+
this.renameInos(oldPath, newPath);
|
|
152
183
|
}
|
|
153
184
|
async stat(path) {
|
|
154
185
|
try {
|
|
155
|
-
return await this.writable.stat(path);
|
|
186
|
+
return this.remapIno(path, await this.writable.stat(path));
|
|
156
187
|
}
|
|
157
188
|
catch {
|
|
158
189
|
if (this.isDeleted(path))
|
|
159
190
|
throw withErrno('ENOENT');
|
|
160
|
-
return await this.readable.stat(path);
|
|
191
|
+
return this.remapIno(path, await this.readable.stat(path));
|
|
161
192
|
}
|
|
162
193
|
}
|
|
163
194
|
statSync(path) {
|
|
164
195
|
try {
|
|
165
|
-
return this.writable.statSync(path);
|
|
196
|
+
return this.remapIno(path, this.writable.statSync(path));
|
|
166
197
|
}
|
|
167
198
|
catch {
|
|
168
199
|
if (this.isDeleted(path))
|
|
169
200
|
throw withErrno('ENOENT');
|
|
170
|
-
return this.readable.statSync(path);
|
|
201
|
+
return this.remapIno(path, this.readable.statSync(path));
|
|
171
202
|
}
|
|
172
203
|
}
|
|
173
204
|
async touch(path, metadata) {
|
|
174
|
-
await this.copyForWrite(path);
|
|
205
|
+
await this.copyForWrite(path, metadata.size);
|
|
175
206
|
await this.writable.touch(path, metadata);
|
|
176
207
|
}
|
|
177
208
|
touchSync(path, metadata) {
|
|
178
|
-
this.copyForWriteSync(path);
|
|
209
|
+
this.copyForWriteSync(path, metadata.size);
|
|
179
210
|
this.writable.touchSync(path, metadata);
|
|
180
211
|
}
|
|
181
212
|
async createFile(path, options) {
|
|
182
213
|
await this.createParentDirectories(path);
|
|
183
|
-
return await this.writable.createFile(path, options);
|
|
214
|
+
return this.remapIno(path, await this.writable.createFile(path, options));
|
|
184
215
|
}
|
|
185
216
|
createFileSync(path, options) {
|
|
186
217
|
this.createParentDirectoriesSync(path);
|
|
187
|
-
return this.writable.createFileSync(path, options);
|
|
218
|
+
return this.remapIno(path, this.writable.createFileSync(path, options));
|
|
188
219
|
}
|
|
189
220
|
async link(srcpath, dstpath) {
|
|
190
221
|
await this.copyForWrite(srcpath);
|
|
@@ -204,6 +235,7 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
204
235
|
if (await this.exists(path)) {
|
|
205
236
|
this.journal.add('delete', path);
|
|
206
237
|
}
|
|
238
|
+
this.inos.delete(path);
|
|
207
239
|
}
|
|
208
240
|
unlinkSync(path) {
|
|
209
241
|
if (!this.existsSync(path))
|
|
@@ -215,6 +247,7 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
215
247
|
if (this.existsSync(path)) {
|
|
216
248
|
this.journal.add('delete', path);
|
|
217
249
|
}
|
|
250
|
+
this.inos.delete(path);
|
|
218
251
|
}
|
|
219
252
|
async rmdir(path) {
|
|
220
253
|
if (!(await this.exists(path)))
|
|
@@ -223,12 +256,14 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
223
256
|
await this.writable.rmdir(path);
|
|
224
257
|
}
|
|
225
258
|
if (!(await this.exists(path))) {
|
|
259
|
+
this.inos.delete(path);
|
|
226
260
|
return;
|
|
227
261
|
}
|
|
228
262
|
// Check if directory is empty.
|
|
229
263
|
if ((await this.readdir(path)).length)
|
|
230
264
|
throw withErrno('ENOTEMPTY');
|
|
231
265
|
this.journal.add('delete', path);
|
|
266
|
+
this.inos.delete(path);
|
|
232
267
|
}
|
|
233
268
|
rmdirSync(path) {
|
|
234
269
|
if (!this.existsSync(path))
|
|
@@ -237,24 +272,26 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
237
272
|
this.writable.rmdirSync(path);
|
|
238
273
|
}
|
|
239
274
|
if (!this.existsSync(path)) {
|
|
275
|
+
this.inos.delete(path);
|
|
240
276
|
return;
|
|
241
277
|
}
|
|
242
278
|
// Check if directory is empty.
|
|
243
279
|
if (this.readdirSync(path).length)
|
|
244
280
|
throw withErrno('ENOTEMPTY');
|
|
245
281
|
this.journal.add('delete', path);
|
|
282
|
+
this.inos.delete(path);
|
|
246
283
|
}
|
|
247
284
|
async mkdir(path, options) {
|
|
248
285
|
if (await this.exists(path))
|
|
249
286
|
throw withErrno('EEXIST');
|
|
250
287
|
await this.createParentDirectories(path);
|
|
251
|
-
return await this.writable.mkdir(path, options);
|
|
288
|
+
return this.remapIno(path, await this.writable.mkdir(path, options));
|
|
252
289
|
}
|
|
253
290
|
mkdirSync(path, options) {
|
|
254
291
|
if (this.existsSync(path))
|
|
255
292
|
throw withErrno('EEXIST');
|
|
256
293
|
this.createParentDirectoriesSync(path);
|
|
257
|
-
return this.writable.mkdirSync(path, options);
|
|
294
|
+
return this.remapIno(path, this.writable.mkdirSync(path, options));
|
|
258
295
|
}
|
|
259
296
|
async readdir(path) {
|
|
260
297
|
if (this.isDeleted(path) || !(await this.exists(path)))
|
|
@@ -323,32 +360,36 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
323
360
|
* Helper function:
|
|
324
361
|
* - Ensures p is on writable before proceeding. Throws an error if it doesn't exist.
|
|
325
362
|
* - Calls f to perform operation on writable.
|
|
363
|
+
*
|
|
364
|
+
* `keep` is the size the file is about to be truncated to, when the caller knows it.
|
|
365
|
+
* Bytes past it are discarded by the operation that follows, so they are not worth copying up.
|
|
326
366
|
*/
|
|
327
|
-
copyForWriteSync(path) {
|
|
367
|
+
copyForWriteSync(path, keep) {
|
|
328
368
|
if (!this.existsSync(path))
|
|
329
369
|
throw withErrno('ENOENT');
|
|
330
370
|
if (!this.writable.existsSync(dirname(path))) {
|
|
331
371
|
this.createParentDirectoriesSync(path);
|
|
332
372
|
}
|
|
333
373
|
if (!this.writable.existsSync(path)) {
|
|
334
|
-
this.copyToWritableSync(path);
|
|
374
|
+
this.copyToWritableSync(path, keep);
|
|
335
375
|
}
|
|
336
376
|
}
|
|
337
|
-
async copyForWrite(path) {
|
|
377
|
+
async copyForWrite(path, keep) {
|
|
338
378
|
if (!(await this.exists(path)))
|
|
339
379
|
throw withErrno('ENOENT');
|
|
340
380
|
if (!(await this.writable.exists(dirname(path)))) {
|
|
341
381
|
await this.createParentDirectories(path);
|
|
342
382
|
}
|
|
343
383
|
if (!(await this.writable.exists(path))) {
|
|
344
|
-
return this.copyToWritable(path);
|
|
384
|
+
return this.copyToWritable(path, keep);
|
|
345
385
|
}
|
|
346
386
|
}
|
|
347
387
|
/**
|
|
348
388
|
* Copy from readable to writable storage.
|
|
389
|
+
* Only `[0, keep)` is copied; the caller is responsible for setting the final size.
|
|
349
390
|
* PRECONDITION: File does not exist on writable storage.
|
|
350
391
|
*/
|
|
351
|
-
copyToWritableSync(path) {
|
|
392
|
+
copyToWritableSync(path, keep) {
|
|
352
393
|
const stats = this.readable.statSync(path);
|
|
353
394
|
if (isDirectory(stats)) {
|
|
354
395
|
this.writable.mkdirSync(path, stats);
|
|
@@ -357,13 +398,16 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
357
398
|
}
|
|
358
399
|
return;
|
|
359
400
|
}
|
|
360
|
-
const
|
|
361
|
-
|
|
401
|
+
const size = Math.min(stats.size, keep ?? stats.size);
|
|
402
|
+
const data = new Uint8Array(size);
|
|
403
|
+
if (size)
|
|
404
|
+
this.readable.readSync(path, data, 0, size);
|
|
362
405
|
this.writable.createFileSync(path, stats);
|
|
363
406
|
this.writable.touchSync(path, stats);
|
|
364
|
-
|
|
407
|
+
if (size)
|
|
408
|
+
this.writable.writeSync(path, data, 0);
|
|
365
409
|
}
|
|
366
|
-
async copyToWritable(path) {
|
|
410
|
+
async copyToWritable(path, keep) {
|
|
367
411
|
const stats = await this.readable.stat(path);
|
|
368
412
|
if (isDirectory(stats)) {
|
|
369
413
|
await this.writable.mkdir(path, stats);
|
|
@@ -372,11 +416,14 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
372
416
|
}
|
|
373
417
|
return;
|
|
374
418
|
}
|
|
375
|
-
const
|
|
376
|
-
|
|
419
|
+
const size = Math.min(stats.size, keep ?? stats.size);
|
|
420
|
+
const data = new Uint8Array(size);
|
|
421
|
+
if (size)
|
|
422
|
+
await this.readable.read(path, data, 0, size);
|
|
377
423
|
await this.writable.createFile(path, stats);
|
|
378
424
|
await this.writable.touch(path, stats);
|
|
379
|
-
|
|
425
|
+
if (size)
|
|
426
|
+
await this.writable.write(path, data, 0);
|
|
380
427
|
}
|
|
381
428
|
}
|
|
382
429
|
const _CopyOnWrite = {
|
package/dist/backends/fetch.d.ts
CHANGED
|
@@ -39,6 +39,10 @@ export declare class FetchFS extends IndexFS {
|
|
|
39
39
|
* @internal @hidden
|
|
40
40
|
*/
|
|
41
41
|
_asyncDone: Promise<unknown>;
|
|
42
|
+
/**
|
|
43
|
+
* Queue an async operation started from a sync method.
|
|
44
|
+
* Failures aren't rethrown to prevent further async ops from failing
|
|
45
|
+
*/
|
|
42
46
|
protected _async(p: Promise<unknown>): void;
|
|
43
47
|
constructor(index: Index, baseUrl: string, requestInit?: RequestInit, remoteWrite?: boolean | undefined);
|
|
44
48
|
protected remove(path: string): Promise<void>;
|
package/dist/backends/fetch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
2
2
|
import { withErrno } from 'kerium';
|
|
3
|
-
import { err, warn } from 'kerium/log';
|
|
3
|
+
import { debug, err, warn } from 'kerium/log';
|
|
4
4
|
import { decodeUTF8 } from 'utilium';
|
|
5
5
|
import * as requests from 'utilium/requests';
|
|
6
6
|
import { Index } from '../internal/file_index.js';
|
|
@@ -35,8 +35,12 @@ export class FetchFS extends IndexFS {
|
|
|
35
35
|
* @internal @hidden
|
|
36
36
|
*/
|
|
37
37
|
_asyncDone = Promise.resolve();
|
|
38
|
+
/**
|
|
39
|
+
* Queue an async operation started from a sync method.
|
|
40
|
+
* Failures aren't rethrown to prevent further async ops from failing
|
|
41
|
+
*/
|
|
38
42
|
_async(p) {
|
|
39
|
-
this._asyncDone = this._asyncDone.then(() => p);
|
|
43
|
+
this._asyncDone = this._asyncDone.then(() => p).catch(e => debug('Async operation failed: ' + (e.message ?? e)));
|
|
40
44
|
}
|
|
41
45
|
constructor(index, baseUrl, requestInit = {}, remoteWrite) {
|
|
42
46
|
super(0x206e6673, 'nfs', index);
|
|
@@ -74,7 +78,7 @@ export class FetchFS extends IndexFS {
|
|
|
74
78
|
if (!data)
|
|
75
79
|
throw withErrno('ENODATA');
|
|
76
80
|
if (missing.length) {
|
|
77
|
-
this._async(requests.get(this.baseUrl + path, { start: offset, end, size: inode.size, warn }));
|
|
81
|
+
this._async(requests.get(this.baseUrl + path, { start: offset, end, size: inode.size, warn }, this.requestInit));
|
|
78
82
|
throw withErrno('EAGAIN');
|
|
79
83
|
}
|
|
80
84
|
buffer.set(data);
|
|
@@ -5,7 +5,7 @@ import type { UsageInfo } from '../internal/filesystem.js';
|
|
|
5
5
|
import { StoreFS } from './store/fs.js';
|
|
6
6
|
import { SyncMapTransaction, type SyncMapStore } from './store/map.js';
|
|
7
7
|
import type { Store } from './store/store.js';
|
|
8
|
-
type
|
|
8
|
+
type SBLock = Disposable & (() => void);
|
|
9
9
|
declare const MetadataEntry_base: import("memium").StructConstructor<BufferView<any>>;
|
|
10
10
|
declare class MetadataEntry extends MetadataEntry_base {
|
|
11
11
|
static name: string;
|
|
@@ -52,7 +52,7 @@ export declare class MetadataBlock extends MetadataBlock_base<ArrayBufferLike> {
|
|
|
52
52
|
* Wait for the block to be unlocked.
|
|
53
53
|
*/
|
|
54
54
|
waitUnlocked(depth?: number): void;
|
|
55
|
-
lock():
|
|
55
|
+
lock(): SBLock;
|
|
56
56
|
}
|
|
57
57
|
declare const SuperBlock_base: import("memium/decorators").StructFromTypedArray<BigUint64Array<ArrayBuffer>>;
|
|
58
58
|
/**
|
|
@@ -63,18 +63,18 @@ export declare class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
63
63
|
*/
|
|
64
64
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
65
65
|
renameSync(oldPath: string, newPath: string): void;
|
|
66
|
-
stat(path: string): Promise<
|
|
67
|
-
statSync(path: string):
|
|
66
|
+
stat(path: string): Promise<Inode>;
|
|
67
|
+
statSync(path: string): Inode;
|
|
68
68
|
touch(path: string, metadata: Partial<InodeLike>): Promise<void>;
|
|
69
69
|
touchSync(path: string, metadata: Partial<InodeLike>): void;
|
|
70
|
-
createFile(path: string, options: CreationOptions): Promise<
|
|
71
|
-
createFileSync(path: string, options: CreationOptions):
|
|
70
|
+
createFile(path: string, options: CreationOptions): Promise<Inode>;
|
|
71
|
+
createFileSync(path: string, options: CreationOptions): Inode;
|
|
72
72
|
unlink(path: string): Promise<void>;
|
|
73
73
|
unlinkSync(path: string): void;
|
|
74
74
|
rmdir(path: string): Promise<void>;
|
|
75
75
|
rmdirSync(path: string): void;
|
|
76
|
-
mkdir(path: string, options: CreationOptions): Promise<
|
|
77
|
-
mkdirSync(path: string, options: CreationOptions):
|
|
76
|
+
mkdir(path: string, options: CreationOptions): Promise<Inode>;
|
|
77
|
+
mkdirSync(path: string, options: CreationOptions): Inode;
|
|
78
78
|
readdir(path: string): Promise<string[]>;
|
|
79
79
|
readdirSync(path: string): string[];
|
|
80
80
|
/**
|
|
@@ -614,13 +614,17 @@ export class StoreFS extends FileSystem {
|
|
|
614
614
|
try {
|
|
615
615
|
const tx = __addDisposableResource(env_17, this.transaction(), true);
|
|
616
616
|
const inode = await this.findInode(tx, path);
|
|
617
|
+
const end = offset + data.byteLength;
|
|
617
618
|
let buffer = data;
|
|
618
619
|
if (!tx.flag('partial')) {
|
|
619
|
-
buffer = extendBuffer((await tx.get(inode.data)) ?? new Uint8Array(),
|
|
620
|
+
buffer = extendBuffer((await tx.get(inode.data)) ?? new Uint8Array(), end);
|
|
620
621
|
buffer.set(data, offset);
|
|
621
622
|
offset = 0;
|
|
622
623
|
}
|
|
623
624
|
await tx.set(inode.data, buffer, offset);
|
|
625
|
+
// Update the inode in the same transaction, so data and metadata can not get out of sync (#286)
|
|
626
|
+
inode.update({ size: Math.max(inode.size, end), mtimeMs: Date.now() });
|
|
627
|
+
await tx.set(inode.ino, inode);
|
|
624
628
|
this._add(inode.ino, path);
|
|
625
629
|
await tx.commit();
|
|
626
630
|
}
|
|
@@ -639,13 +643,17 @@ export class StoreFS extends FileSystem {
|
|
|
639
643
|
try {
|
|
640
644
|
const tx = __addDisposableResource(env_18, this.transaction(), false);
|
|
641
645
|
const inode = this.findInodeSync(tx, path);
|
|
646
|
+
const end = offset + data.byteLength;
|
|
642
647
|
let buffer = data;
|
|
643
648
|
if (!tx.flag('partial')) {
|
|
644
|
-
buffer = extendBuffer(tx.getSync(inode.data) ?? new Uint8Array(),
|
|
649
|
+
buffer = extendBuffer(tx.getSync(inode.data) ?? new Uint8Array(), end);
|
|
645
650
|
buffer.set(data, offset);
|
|
646
651
|
offset = 0;
|
|
647
652
|
}
|
|
648
653
|
tx.setSync(inode.data, buffer, offset);
|
|
654
|
+
// Update the inode in the same transaction, so data and metadata can not get out of sync (#286)
|
|
655
|
+
inode.update({ size: Math.max(inode.size, end), mtimeMs: Date.now() });
|
|
656
|
+
tx.setSync(inode.ino, inode);
|
|
649
657
|
this._add(inode.ino, path);
|
|
650
658
|
tx.commitSync();
|
|
651
659
|
}
|
|
@@ -103,18 +103,18 @@ export declare class DeviceFS extends StoreFS<InMemoryStore> {
|
|
|
103
103
|
constructor();
|
|
104
104
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
105
105
|
renameSync(oldPath: string, newPath: string): void;
|
|
106
|
-
stat(path: string): Promise<
|
|
107
|
-
statSync(path: string):
|
|
106
|
+
stat(path: string): Promise<Inode>;
|
|
107
|
+
statSync(path: string): Inode;
|
|
108
108
|
touch(path: string, metadata: InodeLike): Promise<void>;
|
|
109
109
|
touchSync(path: string, metadata: InodeLike): void;
|
|
110
|
-
createFile(path: string, options: CreationOptions): Promise<
|
|
111
|
-
createFileSync(path: string, options: CreationOptions):
|
|
110
|
+
createFile(path: string, options: CreationOptions): Promise<Inode>;
|
|
111
|
+
createFileSync(path: string, options: CreationOptions): Inode;
|
|
112
112
|
unlink(path: string): Promise<void>;
|
|
113
113
|
unlinkSync(path: string): void;
|
|
114
114
|
rmdir(path: string): Promise<void>;
|
|
115
115
|
rmdirSync(path: string): void;
|
|
116
|
-
mkdir(path: string, options: CreationOptions): Promise<
|
|
117
|
-
mkdirSync(path: string, options: CreationOptions):
|
|
116
|
+
mkdir(path: string, options: CreationOptions): Promise<Inode>;
|
|
117
|
+
mkdirSync(path: string, options: CreationOptions): Inode;
|
|
118
118
|
readdir(path: string): Promise<string[]>;
|
|
119
119
|
readdirSync(path: string): string[];
|
|
120
120
|
link(target: string, link: string): Promise<void>;
|
|
@@ -28,12 +28,12 @@ export declare abstract class IndexFS extends FileSystem {
|
|
|
28
28
|
rmdir(path: string): Promise<void>;
|
|
29
29
|
rmdirSync(path: string): void;
|
|
30
30
|
protected create(path: string, options: CreationOptions): Inode;
|
|
31
|
-
createFile(path: string, options: CreationOptions): Promise<
|
|
32
|
-
createFileSync(path: string, options: CreationOptions):
|
|
31
|
+
createFile(path: string, options: CreationOptions): Promise<Inode>;
|
|
32
|
+
createFileSync(path: string, options: CreationOptions): Inode;
|
|
33
33
|
protected _mkdir?(path: string, options: CreationOptions): Promise<void>;
|
|
34
34
|
protected _mkdirSync?(path: string, options: CreationOptions): void;
|
|
35
|
-
mkdir(path: string, options: CreationOptions): Promise<
|
|
36
|
-
mkdirSync(path: string, options: CreationOptions):
|
|
35
|
+
mkdir(path: string, options: CreationOptions): Promise<Inode>;
|
|
36
|
+
mkdirSync(path: string, options: CreationOptions): Inode;
|
|
37
37
|
link(target: string, link: string): Promise<void>;
|
|
38
38
|
linkSync(target: string, link: string): void;
|
|
39
39
|
readdir(path: string): Promise<string[]>;
|
package/dist/node/async.js
CHANGED
|
@@ -373,7 +373,7 @@ export function watchFile(path, options, listener) {
|
|
|
373
373
|
listener(curr, prev);
|
|
374
374
|
}
|
|
375
375
|
});
|
|
376
|
-
statWatchers.set(normalizedPath, { watcher, listeners: new Set() });
|
|
376
|
+
statWatchers.set(normalizedPath, { watcher, listeners: new Set([listener]) });
|
|
377
377
|
}
|
|
378
378
|
watchFile;
|
|
379
379
|
/**
|
package/dist/node/dir.d.ts
CHANGED
|
@@ -22,9 +22,11 @@ export declare class Dirent<Name extends string | Buffer = string> implements _D
|
|
|
22
22
|
*/
|
|
23
23
|
get path(): string;
|
|
24
24
|
/**
|
|
25
|
+
* `vfs.path` is relative to the directory that was read,
|
|
26
|
+
* so `parentPath` is resolved against it— like Node does.
|
|
25
27
|
* @internal
|
|
26
28
|
*/
|
|
27
|
-
static from(vfs: VFSDirent, encoding?: BufferEncoding | 'buffer' | null): Dirent<string>;
|
|
29
|
+
static from(vfs: VFSDirent, encoding?: BufferEncoding | 'buffer' | null, parentPath?: string): Dirent<string>;
|
|
28
30
|
isFile(): boolean;
|
|
29
31
|
isDirectory(): boolean;
|
|
30
32
|
isBlockDevice(): boolean;
|
package/dist/node/dir.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
|
-
import { withErrno } from 'kerium';
|
|
3
2
|
import { warn } from 'kerium/log';
|
|
4
|
-
import { parse } from '../path.js';
|
|
3
|
+
import { join, parse } from '../path.js';
|
|
5
4
|
import { DirType } from '../vfs/dir.js';
|
|
6
5
|
import { readdir } from './promises.js';
|
|
7
6
|
import { readdirSync } from './sync.js';
|
|
@@ -32,12 +31,14 @@ export class Dirent {
|
|
|
32
31
|
return this._parentPath;
|
|
33
32
|
}
|
|
34
33
|
/**
|
|
34
|
+
* `vfs.path` is relative to the directory that was read,
|
|
35
|
+
* so `parentPath` is resolved against it— like Node does.
|
|
35
36
|
* @internal
|
|
36
37
|
*/
|
|
37
|
-
static from(vfs, encoding) {
|
|
38
|
+
static from(vfs, encoding, parentPath = '.') {
|
|
38
39
|
const dirent = new Dirent();
|
|
39
40
|
const { base, dir } = parse(vfs.path);
|
|
40
|
-
dirent._parentPath = dir
|
|
41
|
+
dirent._parentPath = join(parentPath, dir);
|
|
41
42
|
dirent._name = base;
|
|
42
43
|
dirent.ino = vfs.ino;
|
|
43
44
|
dirent.type = vfs.type;
|
|
@@ -66,6 +67,14 @@ export class Dirent {
|
|
|
66
67
|
return this.type === DirType.SOCK;
|
|
67
68
|
}
|
|
68
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Thrown when a `Dir` is used after it has been closed.
|
|
72
|
+
* Note this is a plain `Error` with a Node error code, not an `Exception`.
|
|
73
|
+
* @internal @hidden
|
|
74
|
+
*/
|
|
75
|
+
function dirClosed() {
|
|
76
|
+
return Object.assign(new Error('Directory handle was closed'), { code: 'ERR_DIR_CLOSED' });
|
|
77
|
+
}
|
|
69
78
|
/**
|
|
70
79
|
* A class representing a directory stream.
|
|
71
80
|
*/
|
|
@@ -75,7 +84,7 @@ export class Dir {
|
|
|
75
84
|
closed = false;
|
|
76
85
|
checkClosed() {
|
|
77
86
|
if (this.closed)
|
|
78
|
-
throw
|
|
87
|
+
throw dirClosed();
|
|
79
88
|
}
|
|
80
89
|
_entries;
|
|
81
90
|
constructor(path, context) {
|
|
@@ -83,6 +92,13 @@ export class Dir {
|
|
|
83
92
|
this.context = context;
|
|
84
93
|
}
|
|
85
94
|
close(cb) {
|
|
95
|
+
if (this.closed) {
|
|
96
|
+
const error = dirClosed();
|
|
97
|
+
if (!cb)
|
|
98
|
+
return Promise.reject(error);
|
|
99
|
+
cb(error);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
86
102
|
this.closed = true;
|
|
87
103
|
if (!cb) {
|
|
88
104
|
return Promise.resolve();
|
|
@@ -94,6 +110,8 @@ export class Dir {
|
|
|
94
110
|
* Subsequent reads will result in errors.
|
|
95
111
|
*/
|
|
96
112
|
closeSync() {
|
|
113
|
+
if (this.closed)
|
|
114
|
+
throw dirClosed();
|
|
97
115
|
this.closed = true;
|
|
98
116
|
}
|
|
99
117
|
async _read() {
|
package/dist/node/promises.d.ts
CHANGED
|
@@ -18,8 +18,10 @@ import { ReadStream, WriteStream } from './streams.js';
|
|
|
18
18
|
export * as constants from '../constants.js';
|
|
19
19
|
export declare class FileHandle implements promises.FileHandle {
|
|
20
20
|
protected context: V_Context;
|
|
21
|
-
readonly fd: number;
|
|
22
21
|
protected vfs: Handle;
|
|
22
|
+
protected _fd: number;
|
|
23
|
+
/** The file descriptor, or -1 once the handle has been closed */
|
|
24
|
+
get fd(): number;
|
|
23
25
|
constructor(context: V_Context, fd: number);
|
|
24
26
|
private _emitChange;
|
|
25
27
|
/**
|