@zenfs/core 2.5.7 → 2.6.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/cow.d.ts +13 -0
- package/dist/backends/cow.js +48 -11
- package/dist/backends/single_buffer.d.ts +2 -2
- package/dist/backends/store/fs.d.ts +6 -6
- package/dist/backends/store/fs.js +14 -10
- package/dist/internal/devices.d.ts +6 -6
- package/dist/internal/filesystem.d.ts +4 -4
- package/dist/internal/index_fs.d.ts +4 -4
- package/dist/internal/index_fs.js +6 -8
- package/dist/internal/inode.js +3 -0
- 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 +4 -2
- package/dist/node/promises.js +130 -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 +19 -3
- 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 +66 -0
- package/dist/vfs/vnode.js +200 -0
- package/dist/vfs/watchers.js +4 -2
- package/dist/vfs/xattr.js +2 -2
- package/eslint.shared.js +1 -1
- package/package.json +6 -3
- package/scripts/make-index.js +7 -1
- package/scripts/test.js +28 -21
- package/tests/backend/port.test.ts +3 -3
- package/tests/backend/store.test.ts +49 -0
- package/tests/common/inode.test.ts +8 -0
- package/tests/common.ts +62 -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 +52 -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
|
*/
|
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,25 +179,26 @@ 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) {
|
|
@@ -180,11 +211,11 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
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)))
|
|
@@ -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
|
/**
|
|
@@ -454,12 +454,10 @@ export class StoreFS extends FileSystem {
|
|
|
454
454
|
}
|
|
455
455
|
}
|
|
456
456
|
async createFile(path, options) {
|
|
457
|
-
options.mode
|
|
458
|
-
return await this.commitNew(path, options, new Uint8Array());
|
|
457
|
+
return await this.commitNew(path, { ...options, mode: options.mode | S_IFREG }, new Uint8Array());
|
|
459
458
|
}
|
|
460
459
|
createFileSync(path, options) {
|
|
461
|
-
options.mode
|
|
462
|
-
return this.commitNewSync(path, options, new Uint8Array());
|
|
460
|
+
return this.commitNewSync(path, { ...options, mode: options.mode | S_IFREG }, new Uint8Array());
|
|
463
461
|
}
|
|
464
462
|
async unlink(path) {
|
|
465
463
|
return this.remove(path, false);
|
|
@@ -478,12 +476,10 @@ export class StoreFS extends FileSystem {
|
|
|
478
476
|
this.removeSync(path, true);
|
|
479
477
|
}
|
|
480
478
|
async mkdir(path, options) {
|
|
481
|
-
options.mode
|
|
482
|
-
return await this.commitNew(path, options, encodeUTF8('{}'));
|
|
479
|
+
return await this.commitNew(path, { ...options, mode: options.mode | S_IFDIR }, encodeUTF8('{}'));
|
|
483
480
|
}
|
|
484
481
|
mkdirSync(path, options) {
|
|
485
|
-
options.mode
|
|
486
|
-
return this.commitNewSync(path, options, encodeUTF8('{}'));
|
|
482
|
+
return this.commitNewSync(path, { ...options, mode: options.mode | S_IFDIR }, encodeUTF8('{}'));
|
|
487
483
|
}
|
|
488
484
|
async readdir(path) {
|
|
489
485
|
const env_11 = { stack: [], error: void 0, hasError: false };
|
|
@@ -618,13 +614,17 @@ export class StoreFS extends FileSystem {
|
|
|
618
614
|
try {
|
|
619
615
|
const tx = __addDisposableResource(env_17, this.transaction(), true);
|
|
620
616
|
const inode = await this.findInode(tx, path);
|
|
617
|
+
const end = offset + data.byteLength;
|
|
621
618
|
let buffer = data;
|
|
622
619
|
if (!tx.flag('partial')) {
|
|
623
|
-
buffer = extendBuffer((await tx.get(inode.data)) ?? new Uint8Array(),
|
|
620
|
+
buffer = extendBuffer((await tx.get(inode.data)) ?? new Uint8Array(), end);
|
|
624
621
|
buffer.set(data, offset);
|
|
625
622
|
offset = 0;
|
|
626
623
|
}
|
|
627
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);
|
|
628
628
|
this._add(inode.ino, path);
|
|
629
629
|
await tx.commit();
|
|
630
630
|
}
|
|
@@ -643,13 +643,17 @@ export class StoreFS extends FileSystem {
|
|
|
643
643
|
try {
|
|
644
644
|
const tx = __addDisposableResource(env_18, this.transaction(), false);
|
|
645
645
|
const inode = this.findInodeSync(tx, path);
|
|
646
|
+
const end = offset + data.byteLength;
|
|
646
647
|
let buffer = data;
|
|
647
648
|
if (!tx.flag('partial')) {
|
|
648
|
-
buffer = extendBuffer(tx.getSync(inode.data) ?? new Uint8Array(),
|
|
649
|
+
buffer = extendBuffer(tx.getSync(inode.data) ?? new Uint8Array(), end);
|
|
649
650
|
buffer.set(data, offset);
|
|
650
651
|
offset = 0;
|
|
651
652
|
}
|
|
652
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);
|
|
653
657
|
this._add(inode.ino, path);
|
|
654
658
|
tx.commitSync();
|
|
655
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>;
|
|
@@ -91,21 +91,21 @@ export interface FileSystemAttributes {
|
|
|
91
91
|
* @category Internals
|
|
92
92
|
* @internal
|
|
93
93
|
*/
|
|
94
|
-
export interface CreationOptions extends Partial<InodeLike
|
|
94
|
+
export interface CreationOptions extends Readonly<Partial<InodeLike>> {
|
|
95
95
|
/**
|
|
96
96
|
* The uid to create the file.
|
|
97
97
|
* This is ignored if the FS supports setuid and the setuid bit is set
|
|
98
98
|
*/
|
|
99
|
-
uid: number;
|
|
99
|
+
readonly uid: number;
|
|
100
100
|
/**
|
|
101
101
|
* The gid to create the file.
|
|
102
102
|
* This is ignored if the FS supports setgid and the setgid bit is set
|
|
103
103
|
*/
|
|
104
|
-
gid: number;
|
|
104
|
+
readonly gid: number;
|
|
105
105
|
/**
|
|
106
106
|
* The mode to create the file with.
|
|
107
107
|
*/
|
|
108
|
-
mode: number;
|
|
108
|
+
readonly mode: number;
|
|
109
109
|
}
|
|
110
110
|
/**
|
|
111
111
|
* Options for streaming operations
|
|
@@ -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[]>;
|
|
@@ -94,13 +94,13 @@ export class IndexFS extends FileSystem {
|
|
|
94
94
|
const inode = this.index.get(path);
|
|
95
95
|
if (!inode)
|
|
96
96
|
throw withErrno('ENOENT');
|
|
97
|
-
return inode;
|
|
97
|
+
return new Inode(inode);
|
|
98
98
|
}
|
|
99
99
|
statSync(path) {
|
|
100
100
|
const inode = this.index.get(path);
|
|
101
101
|
if (!inode)
|
|
102
102
|
throw withErrno('ENOENT');
|
|
103
|
-
return inode;
|
|
103
|
+
return new Inode(inode);
|
|
104
104
|
}
|
|
105
105
|
async touch(path, metadata) {
|
|
106
106
|
const inode = this.index.get(path) ?? _throw(withErrno('ENOENT'));
|
|
@@ -165,21 +165,19 @@ export class IndexFS extends FileSystem {
|
|
|
165
165
|
return inode;
|
|
166
166
|
}
|
|
167
167
|
async createFile(path, options) {
|
|
168
|
-
options.mode
|
|
169
|
-
return this.create(path, options);
|
|
168
|
+
return this.create(path, { ...options, mode: options.mode | S_IFREG });
|
|
170
169
|
}
|
|
171
170
|
createFileSync(path, options) {
|
|
172
|
-
options.mode
|
|
173
|
-
return this.create(path, options);
|
|
171
|
+
return this.create(path, { ...options, mode: options.mode | S_IFREG });
|
|
174
172
|
}
|
|
175
173
|
async mkdir(path, options) {
|
|
176
|
-
options.mode
|
|
174
|
+
options = { ...options, mode: options.mode | S_IFDIR };
|
|
177
175
|
const inode = this.create(path, options);
|
|
178
176
|
await this._mkdir?.(path, options);
|
|
179
177
|
return inode;
|
|
180
178
|
}
|
|
181
179
|
mkdirSync(path, options) {
|
|
182
|
-
options.mode
|
|
180
|
+
options = { ...options, mode: options.mode | S_IFDIR };
|
|
183
181
|
const inode = this.create(path, options);
|
|
184
182
|
this._mkdirSync?.(path, options);
|
|
185
183
|
return inode;
|
package/dist/internal/inode.js
CHANGED
|
@@ -451,6 +451,9 @@ let Inode = (() => {
|
|
|
451
451
|
data = args[0];
|
|
452
452
|
args = [sizeof(Inode)];
|
|
453
453
|
}
|
|
454
|
+
if (args[0] instanceof Inode) {
|
|
455
|
+
args = [new Uint8Array(args[0].buffer, args[0].byteOffset, args[0].byteLength)];
|
|
456
|
+
}
|
|
454
457
|
super(...args);
|
|
455
458
|
__runInitializers(this, ___data_extraInitializers);
|
|
456
459
|
if (this.byteLength < sizeof(Inode)) {
|
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
|
/**
|
|
@@ -239,7 +241,7 @@ export declare function writeFile(this: V_Context, path: fs.PathLike | promises.
|
|
|
239
241
|
* @option mode Defaults to `0644`.
|
|
240
242
|
* @option flag Defaults to `'a'`.
|
|
241
243
|
*/
|
|
242
|
-
export declare function appendFile(this: V_Context, path: fs.PathLike | promises.FileHandle, data: FileContents
|
|
244
|
+
export declare function appendFile(this: V_Context, path: fs.PathLike | promises.FileHandle, data: FileContents | Iterable<string | ArrayBufferView> | AsyncIterable<string | ArrayBufferView>, _options?: BufferEncoding | (fs.EncodingOption & {
|
|
243
245
|
mode?: fs.Mode;
|
|
244
246
|
flag?: fs.OpenMode;
|
|
245
247
|
}) | null): Promise<void>;
|