@zenfs/core 1.10.4 → 1.11.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 +7 -4
- package/dist/backends/cow.js +16 -6
- package/dist/backends/fetch.js +8 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/internal/filesystem.d.ts +28 -1
- package/dist/internal/filesystem.js +39 -0
- package/dist/internal/log.d.ts +1 -1
- package/dist/internal/log.js +3 -3
- package/dist/mixins/async.js +16 -0
- package/dist/mixins/mutexed.d.ts +4 -2
- package/dist/mixins/mutexed.js +28 -0
- package/dist/mixins/readonly.js +3 -0
- package/dist/stats.d.ts +1 -1
- package/dist/vfs/async.d.ts +1 -30
- package/dist/vfs/async.js +2 -72
- package/dist/vfs/promises.d.ts +3 -2
- package/dist/vfs/promises.js +6 -66
- package/dist/vfs/shared.js +3 -4
- package/dist/vfs/streams.d.ts +65 -7
- package/dist/vfs/streams.js +108 -24
- package/package.json +4 -3
- package/tests/fs/streams.test.ts +16 -49
package/dist/backends/cow.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import type { File } from '../internal/file.js';
|
|
2
|
-
import type { CreationOptions, UsageInfo } from '../internal/filesystem.js';
|
|
2
|
+
import type { CreationOptions, StreamOptions, UsageInfo } from '../internal/filesystem.js';
|
|
3
3
|
import type { InodeLike } from '../internal/inode.js';
|
|
4
4
|
import type { Stats } from '../stats.js';
|
|
5
5
|
import { FileSystem } from '../internal/filesystem.js';
|
|
6
6
|
import { EventEmitter } from 'eventemitter3';
|
|
7
|
+
import { type MountConfiguration } from '../config.js';
|
|
7
8
|
/**
|
|
8
9
|
* Configuration options for CoW.
|
|
9
10
|
* @category Backends and Configuration
|
|
10
11
|
*/
|
|
11
12
|
export interface CopyOnWriteOptions {
|
|
12
13
|
/** The file system that initially populates this file system. */
|
|
13
|
-
readable:
|
|
14
|
+
readable: MountConfiguration<any>;
|
|
14
15
|
/** The file system to write modified files to. */
|
|
15
|
-
writable:
|
|
16
|
+
writable: MountConfiguration<any>;
|
|
16
17
|
/** @see {@link Journal} */
|
|
17
18
|
journal?: Journal;
|
|
18
19
|
}
|
|
@@ -98,6 +99,8 @@ export declare class CopyOnWriteFS extends FileSystem {
|
|
|
98
99
|
mkdirSync(path: string, mode: number, options: CreationOptions): void;
|
|
99
100
|
readdir(path: string): Promise<string[]>;
|
|
100
101
|
readdirSync(path: string): string[];
|
|
102
|
+
streamRead(path: string, options: StreamOptions): ReadableStream;
|
|
103
|
+
streamWrite(path: string, options: StreamOptions): WritableStream;
|
|
101
104
|
/**
|
|
102
105
|
* Create the needed parent directories on the writable storage should they not exist.
|
|
103
106
|
* Use modes from the read-only storage.
|
|
@@ -143,7 +146,7 @@ declare const _CopyOnWrite: {
|
|
|
143
146
|
readonly required: false;
|
|
144
147
|
};
|
|
145
148
|
};
|
|
146
|
-
readonly create: (options: CopyOnWriteOptions) => CopyOnWriteFS
|
|
149
|
+
readonly create: (options: CopyOnWriteOptions) => Promise<CopyOnWriteFS>;
|
|
147
150
|
};
|
|
148
151
|
type _CopyOnWrite = typeof _CopyOnWrite;
|
|
149
152
|
export interface CopyOnWrite extends _CopyOnWrite {
|
package/dist/backends/cow.js
CHANGED
|
@@ -57,6 +57,7 @@ import { FileSystem } from '../internal/filesystem.js';
|
|
|
57
57
|
import { debug, err, warn } from '../internal/log.js';
|
|
58
58
|
import { dirname, join } from '../vfs/path.js';
|
|
59
59
|
import { EventEmitter } from 'eventemitter3';
|
|
60
|
+
import { resolveMountConfig } from '../config.js';
|
|
60
61
|
const journalOperations = ['delete'];
|
|
61
62
|
/** Because TS doesn't work right w/o it */
|
|
62
63
|
function isJournalOp(op) {
|
|
@@ -323,9 +324,9 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
323
324
|
this.writable.mkdirSync(path, mode, options);
|
|
324
325
|
}
|
|
325
326
|
async readdir(path) {
|
|
326
|
-
if (this.isDeleted(path))
|
|
327
|
+
if (this.isDeleted(path) || !(await this.exists(path)))
|
|
327
328
|
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
328
|
-
const entries = await this.
|
|
329
|
+
const entries = (await this.readable.exists(path)) ? await this.readable.readdir(path) : [];
|
|
329
330
|
if (await this.writable.exists(path))
|
|
330
331
|
for (const entry of await this.writable.readdir(path)) {
|
|
331
332
|
if (!entries.includes(entry))
|
|
@@ -334,9 +335,9 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
334
335
|
return entries.filter(entry => !this.isDeleted(join(path, entry)));
|
|
335
336
|
}
|
|
336
337
|
readdirSync(path) {
|
|
337
|
-
if (this.isDeleted(path))
|
|
338
|
+
if (this.isDeleted(path) || !this.existsSync(path))
|
|
338
339
|
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
339
|
-
const entries = this.
|
|
340
|
+
const entries = this.readable.existsSync(path) ? this.readable.readdirSync(path) : [];
|
|
340
341
|
if (this.writable.existsSync(path))
|
|
341
342
|
for (const entry of this.writable.readdirSync(path)) {
|
|
342
343
|
if (!entries.includes(entry))
|
|
@@ -344,6 +345,13 @@ export class CopyOnWriteFS extends FileSystem {
|
|
|
344
345
|
}
|
|
345
346
|
return entries.filter(entry => !this.isDeleted(join(path, entry)));
|
|
346
347
|
}
|
|
348
|
+
streamRead(path, options) {
|
|
349
|
+
return this.writable.existsSync(path) ? this.writable.streamRead(path, options) : this.readable.streamRead(path, options);
|
|
350
|
+
}
|
|
351
|
+
streamWrite(path, options) {
|
|
352
|
+
this.copyForWriteSync(path);
|
|
353
|
+
return this.writable.streamWrite(path, options);
|
|
354
|
+
}
|
|
347
355
|
/**
|
|
348
356
|
* Create the needed parent directories on the writable storage should they not exist.
|
|
349
357
|
* Use modes from the read-only storage.
|
|
@@ -478,8 +486,10 @@ const _CopyOnWrite = {
|
|
|
478
486
|
readable: { type: 'object', required: true },
|
|
479
487
|
journal: { type: 'object', required: false },
|
|
480
488
|
},
|
|
481
|
-
create(options) {
|
|
482
|
-
|
|
489
|
+
async create(options) {
|
|
490
|
+
const readable = await resolveMountConfig(options.readable);
|
|
491
|
+
const writable = await resolveMountConfig(options.writable);
|
|
492
|
+
return new CopyOnWriteFS(readable, writable, options.journal);
|
|
483
493
|
},
|
|
484
494
|
};
|
|
485
495
|
/**
|
package/dist/backends/fetch.js
CHANGED
|
@@ -50,7 +50,6 @@ export class FetchFS extends IndexFS {
|
|
|
50
50
|
const inode = this.index.get(path);
|
|
51
51
|
if (!inode)
|
|
52
52
|
throw ErrnoError.With('ENOENT', path, 'read');
|
|
53
|
-
end !== null && end !== void 0 ? end : (end = inode.size);
|
|
54
53
|
if (end - offset == 0)
|
|
55
54
|
return;
|
|
56
55
|
const data = await requests
|
|
@@ -65,7 +64,6 @@ export class FetchFS extends IndexFS {
|
|
|
65
64
|
const inode = this.index.get(path);
|
|
66
65
|
if (!inode)
|
|
67
66
|
throw ErrnoError.With('ENOENT', path, 'read');
|
|
68
|
-
end !== null && end !== void 0 ? end : (end = inode.size);
|
|
69
67
|
if (end - offset == 0)
|
|
70
68
|
return;
|
|
71
69
|
const { data, missing } = requests.getCached(this.baseUrl + path, { start: offset, end, size: inode.size, warn });
|
|
@@ -78,9 +76,17 @@ export class FetchFS extends IndexFS {
|
|
|
78
76
|
buffer.set(data);
|
|
79
77
|
}
|
|
80
78
|
async write(path, data, offset) {
|
|
79
|
+
const inode = this.index.get(path);
|
|
80
|
+
if (!inode)
|
|
81
|
+
throw ErrnoError.With('ENOENT', path, 'write');
|
|
82
|
+
inode.update({ mtimeMs: Date.now(), size: Math.max(inode.size, data.byteLength + offset) });
|
|
81
83
|
await requests.set(this.baseUrl + path, data, { offset, warn, cacheOnly: !this.remoteWrite }, this.requestInit).catch(parseError(path, this));
|
|
82
84
|
}
|
|
83
85
|
writeSync(path, data, offset) {
|
|
86
|
+
const inode = this.index.get(path);
|
|
87
|
+
if (!inode)
|
|
88
|
+
throw ErrnoError.With('ENOENT', path, 'write');
|
|
89
|
+
inode.update({ mtimeMs: Date.now(), size: Math.max(inode.size, data.byteLength + offset) });
|
|
84
90
|
this._async(requests.set(this.baseUrl + path, data, { offset, warn, cacheOnly: !this.remoteWrite }, this.requestInit).catch(parseError(path, this)));
|
|
85
91
|
}
|
|
86
92
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -96,6 +96,16 @@ export type FileSystemAttributes = {
|
|
|
96
96
|
* This should be set for read-only file systems.
|
|
97
97
|
*/
|
|
98
98
|
no_write: void;
|
|
99
|
+
/**
|
|
100
|
+
* The FS is using the default implementation for `streamRead`
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
default_stream_read: void;
|
|
104
|
+
/**
|
|
105
|
+
* The FS is using the default implementation for `streamWrite`
|
|
106
|
+
* @internal
|
|
107
|
+
*/
|
|
108
|
+
default_stream_write: void;
|
|
99
109
|
};
|
|
100
110
|
/**
|
|
101
111
|
* Options used when creating files and directories.
|
|
@@ -131,6 +141,13 @@ export interface PureCreationOptions extends CreationOptions {
|
|
|
131
141
|
*/
|
|
132
142
|
mode: number;
|
|
133
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
export interface StreamOptions {
|
|
148
|
+
start?: number;
|
|
149
|
+
end?: number;
|
|
150
|
+
}
|
|
134
151
|
/**
|
|
135
152
|
* Provides a consistent and easy to use internal API.
|
|
136
153
|
* Default implementations for `exists` and `existsSync` are included.
|
|
@@ -158,7 +175,7 @@ export declare abstract class FileSystem {
|
|
|
158
175
|
/**
|
|
159
176
|
* @see FileSystemAttributes
|
|
160
177
|
*/
|
|
161
|
-
readonly attributes: ConstMap<FileSystemAttributes>;
|
|
178
|
+
readonly attributes: ConstMap<FileSystemAttributes> & Map<string, any>;
|
|
162
179
|
constructor(
|
|
163
180
|
/**
|
|
164
181
|
* A unique ID for this kind of file system.
|
|
@@ -253,4 +270,14 @@ export declare abstract class FileSystem {
|
|
|
253
270
|
* @param offset The offset in the file to start writing
|
|
254
271
|
*/
|
|
255
272
|
abstract writeSync(path: string, buffer: Uint8Array, offset: number): void;
|
|
273
|
+
/**
|
|
274
|
+
* Read a file using a stream.
|
|
275
|
+
* @privateRemarks The default implementation of `streamRead` uses "chunked" `read`s
|
|
276
|
+
*/
|
|
277
|
+
streamRead(path: string, options: StreamOptions): ReadableStream;
|
|
278
|
+
/**
|
|
279
|
+
* Write a file using stream.
|
|
280
|
+
* @privateRemarks The default implementation of `streamWrite` uses "chunked" `write`s
|
|
281
|
+
*/
|
|
282
|
+
streamWrite(path: string, options: StreamOptions): WritableStream;
|
|
256
283
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const _chunkSize = 0x1000;
|
|
1
2
|
/**
|
|
2
3
|
* Provides a consistent and easy to use internal API.
|
|
3
4
|
* Default implementations for `exists` and `existsSync` are included.
|
|
@@ -23,6 +24,10 @@ export class FileSystem {
|
|
|
23
24
|
* @see FileSystemAttributes
|
|
24
25
|
*/
|
|
25
26
|
this.attributes = new Map();
|
|
27
|
+
if (this.streamRead === FileSystem.prototype.streamRead)
|
|
28
|
+
this.attributes.set('default_stream_read');
|
|
29
|
+
if (this.streamWrite === FileSystem.prototype.streamWrite)
|
|
30
|
+
this.attributes.set('default_stream_write');
|
|
26
31
|
}
|
|
27
32
|
toString() {
|
|
28
33
|
var _a;
|
|
@@ -81,4 +86,38 @@ export class FileSystem {
|
|
|
81
86
|
return e.code != 'ENOENT';
|
|
82
87
|
}
|
|
83
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Read a file using a stream.
|
|
91
|
+
* @privateRemarks The default implementation of `streamRead` uses "chunked" `read`s
|
|
92
|
+
*/
|
|
93
|
+
streamRead(path, options) {
|
|
94
|
+
return new ReadableStream({
|
|
95
|
+
start: async (controller) => {
|
|
96
|
+
const { size } = await this.stat(path);
|
|
97
|
+
const { start = 0, end = size } = options;
|
|
98
|
+
for (let offset = start; offset < end; offset += _chunkSize) {
|
|
99
|
+
const bytesRead = offset + _chunkSize > end ? end - offset : _chunkSize;
|
|
100
|
+
const buffer = new Uint8Array(bytesRead);
|
|
101
|
+
await this.read(path, buffer, offset, offset + bytesRead).catch(controller.error.bind(controller));
|
|
102
|
+
controller.enqueue(buffer);
|
|
103
|
+
}
|
|
104
|
+
controller.close();
|
|
105
|
+
},
|
|
106
|
+
type: 'bytes',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Write a file using stream.
|
|
111
|
+
* @privateRemarks The default implementation of `streamWrite` uses "chunked" `write`s
|
|
112
|
+
*/
|
|
113
|
+
streamWrite(path, options) {
|
|
114
|
+
var _a;
|
|
115
|
+
let position = (_a = options.start) !== null && _a !== void 0 ? _a : 0;
|
|
116
|
+
return new WritableStream({
|
|
117
|
+
write: async (chunk, controller) => {
|
|
118
|
+
await this.write(path, chunk, position).catch(controller.error.bind(controller));
|
|
119
|
+
position += chunk.byteLength;
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
84
123
|
}
|
package/dist/internal/log.d.ts
CHANGED
|
@@ -96,7 +96,7 @@ export declare const formats: {
|
|
|
96
96
|
readonly ansi_message: (this: void, entry: Entry) => string;
|
|
97
97
|
readonly css_level: (this: void, entry: Entry) => string[];
|
|
98
98
|
readonly css_message: (this: void, entry: Entry) => string[];
|
|
99
|
-
readonly default: (this: void, entry: Entry) => string;
|
|
99
|
+
readonly default: (this: void, entry: Entry) => string[];
|
|
100
100
|
};
|
|
101
101
|
export declare function format(entry: Entry): string[];
|
|
102
102
|
/** Whether log entries are being recorded */
|
package/dist/internal/log.js
CHANGED
|
@@ -172,20 +172,20 @@ export const formats = {
|
|
|
172
172
|
},
|
|
173
173
|
css_level(entry) {
|
|
174
174
|
const levelLabel = levels[entry.level].toUpperCase();
|
|
175
|
-
return [..._prettyMs(entry, 'css'),
|
|
175
|
+
return [..._prettyMs(entry, 'css'), '%c' + levelLabel, _cssLevelColor[entry.level], entry.message];
|
|
176
176
|
},
|
|
177
177
|
css_message(entry) {
|
|
178
178
|
const text = _prettyMs(entry, 'css');
|
|
179
179
|
const isImportant = entry.level < Level.CRIT;
|
|
180
180
|
if (isImportant) {
|
|
181
181
|
const levelLabel = levels[entry.level].toUpperCase();
|
|
182
|
-
text.push(
|
|
182
|
+
text.push('%c' + levelLabel, _cssLevelColor[entry.level]);
|
|
183
183
|
}
|
|
184
184
|
text.push('%c' + entry.message, _cssMessageColor[entry.level]);
|
|
185
185
|
return text;
|
|
186
186
|
},
|
|
187
187
|
default(entry) {
|
|
188
|
-
return
|
|
188
|
+
return [_prettyMs(entry), entry.message];
|
|
189
189
|
},
|
|
190
190
|
};
|
|
191
191
|
let _format = formats.default;
|
package/dist/mixins/async.js
CHANGED
|
@@ -186,6 +186,22 @@ export function Async(FS) {
|
|
|
186
186
|
this._sync.writeSync(path, buffer, offset);
|
|
187
187
|
this._async(this.write(path, buffer, offset));
|
|
188
188
|
}
|
|
189
|
+
streamWrite(path, options) {
|
|
190
|
+
this.checkSync(path, 'streamWrite');
|
|
191
|
+
const sync = this._sync.streamWrite(path, options).getWriter();
|
|
192
|
+
const async = super.streamWrite(path, options).getWriter();
|
|
193
|
+
return new WritableStream({
|
|
194
|
+
async write(chunk, controller) {
|
|
195
|
+
await Promise.all([sync.write(chunk), async.write(chunk)]).catch(controller.error.bind(controller));
|
|
196
|
+
},
|
|
197
|
+
async close() {
|
|
198
|
+
await Promise.all([sync.close(), async.close()]);
|
|
199
|
+
},
|
|
200
|
+
async abort(reason) {
|
|
201
|
+
await Promise.all([sync.abort(reason), async.abort(reason)]);
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
189
205
|
/**
|
|
190
206
|
* @internal
|
|
191
207
|
*/
|
package/dist/mixins/mutexed.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { File } from '../internal/file.js';
|
|
2
|
-
import type { CreationOptions, FileSystem, FileSystemMetadata, UsageInfo } from '../internal/filesystem.js';
|
|
2
|
+
import type { CreationOptions, FileSystem, FileSystemMetadata, StreamOptions, UsageInfo } from '../internal/filesystem.js';
|
|
3
3
|
import type { InodeLike } from '../internal/inode.js';
|
|
4
4
|
import type { Stats } from '../stats.js';
|
|
5
5
|
import type { Concrete } from '../utils.js';
|
|
@@ -31,7 +31,7 @@ export declare class _MutexedFS<T extends FileSystem> implements FileSystem {
|
|
|
31
31
|
get name(): string;
|
|
32
32
|
get label(): string | undefined;
|
|
33
33
|
set label(value: string | undefined);
|
|
34
|
-
get attributes(): import("utilium").ConstMap<import("../internal/filesystem.js").FileSystemAttributes, keyof import("../internal/filesystem.js").FileSystemAttributes, void>;
|
|
34
|
+
get attributes(): import("utilium").ConstMap<import("../internal/filesystem.js").FileSystemAttributes, keyof import("../internal/filesystem.js").FileSystemAttributes, void> & Map<string, any>;
|
|
35
35
|
ready(): Promise<void>;
|
|
36
36
|
usage(): UsageInfo;
|
|
37
37
|
metadata(): FileSystemMetadata;
|
|
@@ -86,6 +86,8 @@ export declare class _MutexedFS<T extends FileSystem> implements FileSystem {
|
|
|
86
86
|
readSync(path: string, buffer: Uint8Array, offset: number, end: number): void;
|
|
87
87
|
write(path: string, buffer: Uint8Array, offset: number): Promise<void>;
|
|
88
88
|
writeSync(path: string, buffer: Uint8Array, offset: number): void;
|
|
89
|
+
streamRead(path: string, options: StreamOptions): ReadableStream;
|
|
90
|
+
streamWrite(path: string, options: StreamOptions): WritableStream;
|
|
89
91
|
}
|
|
90
92
|
/**
|
|
91
93
|
* This serializes access to an underlying async filesystem.
|
package/dist/mixins/mutexed.js
CHANGED
|
@@ -528,6 +528,34 @@ export class _MutexedFS {
|
|
|
528
528
|
__disposeResources(env_26);
|
|
529
529
|
}
|
|
530
530
|
}
|
|
531
|
+
streamRead(path, options) {
|
|
532
|
+
const env_27 = { stack: [], error: void 0, hasError: false };
|
|
533
|
+
try {
|
|
534
|
+
const _ = __addDisposableResource(env_27, this.lockSync(path, 'streamRead'), false);
|
|
535
|
+
return this._fs.streamRead(path, options);
|
|
536
|
+
}
|
|
537
|
+
catch (e_27) {
|
|
538
|
+
env_27.error = e_27;
|
|
539
|
+
env_27.hasError = true;
|
|
540
|
+
}
|
|
541
|
+
finally {
|
|
542
|
+
__disposeResources(env_27);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
streamWrite(path, options) {
|
|
546
|
+
const env_28 = { stack: [], error: void 0, hasError: false };
|
|
547
|
+
try {
|
|
548
|
+
const _ = __addDisposableResource(env_28, this.lockSync(path, 'streamWrite'), false);
|
|
549
|
+
return this._fs.streamWrite(path, options);
|
|
550
|
+
}
|
|
551
|
+
catch (e_28) {
|
|
552
|
+
env_28.error = e_28;
|
|
553
|
+
env_28.hasError = true;
|
|
554
|
+
}
|
|
555
|
+
finally {
|
|
556
|
+
__disposeResources(env_28);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
531
559
|
}
|
|
532
560
|
/**
|
|
533
561
|
* This serializes access to an underlying async filesystem.
|
package/dist/mixins/readonly.js
CHANGED
package/dist/stats.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { InodeFields, InodeLike } from './internal/inode.js';
|
|
|
4
4
|
import * as c from './vfs/constants.js';
|
|
5
5
|
/**
|
|
6
6
|
* Indicates the type of a file. Applied to 'mode'.
|
|
7
|
+
* @deprecated
|
|
7
8
|
*/
|
|
8
9
|
export type FileType = typeof c.S_IFREG | typeof c.S_IFDIR | typeof c.S_IFLNK;
|
|
9
10
|
export interface StatsLike<T extends number | bigint = number | bigint> {
|
|
@@ -60,7 +61,6 @@ export declare abstract class StatsCommon<T extends number | bigint> implements
|
|
|
60
61
|
set blocks(value: T);
|
|
61
62
|
/**
|
|
62
63
|
* Unix-style file mode (e.g. 0o644) that includes the type of the item.
|
|
63
|
-
* Type of the item can be FILE, DIRECTORY, SYMLINK, or SOCKET
|
|
64
64
|
*/
|
|
65
65
|
mode: T;
|
|
66
66
|
/**
|
package/dist/vfs/async.d.ts
CHANGED
|
@@ -7,8 +7,7 @@ import type { FileContents } from './types.js';
|
|
|
7
7
|
import { Buffer } from 'buffer';
|
|
8
8
|
import { ErrnoError } from '../internal/error.js';
|
|
9
9
|
import { BigIntStats } from '../stats.js';
|
|
10
|
-
import
|
|
11
|
-
import { ReadStream, WriteStream } from './streams.js';
|
|
10
|
+
import { ReadStream, WriteStream, type ReadStreamOptions, type WriteStreamOptions } from './streams.js';
|
|
12
11
|
import { FSWatcher } from './watchers.js';
|
|
13
12
|
/**
|
|
14
13
|
* Asynchronous rename. No arguments other than a possible exception are given to the completion callback.
|
|
@@ -237,34 +236,6 @@ export declare function watch(this: V_Context, path: fs.PathLike, listener?: (ev
|
|
|
237
236
|
export declare function watch(this: V_Context, path: fs.PathLike, options: {
|
|
238
237
|
persistent?: boolean;
|
|
239
238
|
}, listener?: (event: string, filename: string) => any): FSWatcher;
|
|
240
|
-
interface StreamOptions {
|
|
241
|
-
flags?: string;
|
|
242
|
-
encoding?: BufferEncoding;
|
|
243
|
-
fd?: number | promises.FileHandle;
|
|
244
|
-
mode?: number;
|
|
245
|
-
autoClose?: boolean;
|
|
246
|
-
emitClose?: boolean;
|
|
247
|
-
start?: number;
|
|
248
|
-
signal?: AbortSignal;
|
|
249
|
-
highWaterMark?: number;
|
|
250
|
-
}
|
|
251
|
-
interface FSImplementation {
|
|
252
|
-
open?: (...args: unknown[]) => unknown;
|
|
253
|
-
close?: (...args: unknown[]) => unknown;
|
|
254
|
-
}
|
|
255
|
-
interface ReadStreamOptions extends StreamOptions {
|
|
256
|
-
fs?: FSImplementation & {
|
|
257
|
-
read: (...args: unknown[]) => unknown;
|
|
258
|
-
};
|
|
259
|
-
end?: number;
|
|
260
|
-
}
|
|
261
|
-
interface WriteStreamOptions extends StreamOptions {
|
|
262
|
-
fs?: FSImplementation & {
|
|
263
|
-
write: (...args: unknown[]) => unknown;
|
|
264
|
-
writev?: (...args: unknown[]) => unknown;
|
|
265
|
-
};
|
|
266
|
-
flush?: boolean;
|
|
267
|
-
}
|
|
268
239
|
/**
|
|
269
240
|
* Opens a file in read mode and creates a Node.js-like ReadStream.
|
|
270
241
|
*
|
package/dist/vfs/async.js
CHANGED
|
@@ -421,47 +421,9 @@ watch;
|
|
|
421
421
|
* @returns A ReadStream object for interacting with the file's contents.
|
|
422
422
|
*/
|
|
423
423
|
export function createReadStream(path, options) {
|
|
424
|
-
var _a;
|
|
425
424
|
options = typeof options == 'object' ? options : { encoding: options };
|
|
426
425
|
const _handle = promises.open.call(this, path, 'r', options === null || options === void 0 ? void 0 : options.mode);
|
|
427
|
-
|
|
428
|
-
if (typeof options.start == 'number')
|
|
429
|
-
handle.file.position = options.start;
|
|
430
|
-
});
|
|
431
|
-
let handle;
|
|
432
|
-
const stream = new ReadStream({
|
|
433
|
-
highWaterMark: options.highWaterMark || 0x1000,
|
|
434
|
-
encoding: (_a = options.encoding) !== null && _a !== void 0 ? _a : undefined,
|
|
435
|
-
async read(size) {
|
|
436
|
-
var _a;
|
|
437
|
-
try {
|
|
438
|
-
handle || (handle = await _handle);
|
|
439
|
-
const start = (_a = options.start) !== null && _a !== void 0 ? _a : handle.file.position;
|
|
440
|
-
if (typeof options.end === 'number' && start >= options.end) {
|
|
441
|
-
stream.push(null);
|
|
442
|
-
await handle.close();
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
if (typeof options.end === 'number') {
|
|
446
|
-
size = Math.min(size, options.end - start);
|
|
447
|
-
}
|
|
448
|
-
const result = await handle.read(new Uint8Array(size), 0, size);
|
|
449
|
-
stream.push(!result.bytesRead ? null : result.buffer.subarray(0, result.bytesRead));
|
|
450
|
-
if (!result.bytesRead)
|
|
451
|
-
await handle.close();
|
|
452
|
-
}
|
|
453
|
-
catch (error) {
|
|
454
|
-
await (handle === null || handle === void 0 ? void 0 : handle.close());
|
|
455
|
-
stream.destroy(error);
|
|
456
|
-
}
|
|
457
|
-
},
|
|
458
|
-
async destroy(error, callback) {
|
|
459
|
-
await (handle === null || handle === void 0 ? void 0 : handle.close().catch(nop));
|
|
460
|
-
callback(error);
|
|
461
|
-
},
|
|
462
|
-
});
|
|
463
|
-
stream.path = path.toString();
|
|
464
|
-
return stream;
|
|
426
|
+
return new ReadStream({ ...options, autoClose: true }, _handle);
|
|
465
427
|
}
|
|
466
428
|
createReadStream;
|
|
467
429
|
/**
|
|
@@ -474,39 +436,7 @@ createReadStream;
|
|
|
474
436
|
export function createWriteStream(path, options) {
|
|
475
437
|
options = typeof options == 'object' ? options : { encoding: options };
|
|
476
438
|
const _handle = promises.open.call(this, path, 'w', options === null || options === void 0 ? void 0 : options.mode);
|
|
477
|
-
|
|
478
|
-
if (typeof options.start == 'number')
|
|
479
|
-
handle.file.position = options.start;
|
|
480
|
-
});
|
|
481
|
-
let handle;
|
|
482
|
-
const { stack } = new Error();
|
|
483
|
-
const stream = new WriteStream({
|
|
484
|
-
highWaterMark: options === null || options === void 0 ? void 0 : options.highWaterMark,
|
|
485
|
-
async write(chunk, encoding, callback) {
|
|
486
|
-
try {
|
|
487
|
-
handle || (handle = await _handle);
|
|
488
|
-
const { bytesWritten } = await handle.write(chunk, null, encoding);
|
|
489
|
-
if (bytesWritten == chunk.length)
|
|
490
|
-
return callback();
|
|
491
|
-
throw new ErrnoError(Errno.EIO, `Failed to write full chunk of write stream (wrote ${bytesWritten}/${chunk.length} bytes)`, handle.file.path);
|
|
492
|
-
}
|
|
493
|
-
catch (error) {
|
|
494
|
-
await (handle === null || handle === void 0 ? void 0 : handle.close());
|
|
495
|
-
error.stack += stack === null || stack === void 0 ? void 0 : stack.slice(5);
|
|
496
|
-
callback(error);
|
|
497
|
-
}
|
|
498
|
-
},
|
|
499
|
-
async destroy(error, callback) {
|
|
500
|
-
await (handle === null || handle === void 0 ? void 0 : handle.close().catch(callback));
|
|
501
|
-
callback(error);
|
|
502
|
-
},
|
|
503
|
-
async final(callback) {
|
|
504
|
-
await (handle === null || handle === void 0 ? void 0 : handle.close().catch(nop));
|
|
505
|
-
callback();
|
|
506
|
-
},
|
|
507
|
-
});
|
|
508
|
-
stream.path = path.toString();
|
|
509
|
-
return stream;
|
|
439
|
+
return new WriteStream(options, _handle);
|
|
510
440
|
}
|
|
511
441
|
createWriteStream;
|
|
512
442
|
export function rm(path, options, callback = nop) {
|
package/dist/vfs/promises.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type * as fs from 'node:fs';
|
|
2
2
|
import type * as promises from 'node:fs/promises';
|
|
3
|
+
import type { Interface as ReadlineInterface } from 'node:readline';
|
|
3
4
|
import type { Stream } from 'node:stream';
|
|
4
|
-
import type {
|
|
5
|
+
import type { ReadableStream as NodeReadableStream } from 'node:stream/web';
|
|
5
6
|
import type { V_Context } from '../context.js';
|
|
6
7
|
import type { File } from '../internal/file.js';
|
|
7
8
|
import type { Stats } from '../stats.js';
|
|
@@ -88,7 +89,7 @@ export declare class FileHandle implements promises.FileHandle {
|
|
|
88
89
|
* Read file data using a `ReadableStream`.
|
|
89
90
|
* The handle will not be closed automatically.
|
|
90
91
|
*/
|
|
91
|
-
readableWebStream(options?: promises.ReadableWebStreamOptions):
|
|
92
|
+
readableWebStream(options?: promises.ReadableWebStreamOptions): NodeReadableStream<Uint8Array>;
|
|
92
93
|
/**
|
|
93
94
|
* @todo Implement
|
|
94
95
|
*/
|
package/dist/vfs/promises.js
CHANGED
|
@@ -51,6 +51,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
53
|
import { Buffer } from 'buffer';
|
|
54
|
+
import { _throw } from 'utilium';
|
|
54
55
|
import { credentials } from '../internal/credentials.js';
|
|
55
56
|
import { Errno, ErrnoError } from '../internal/error.js';
|
|
56
57
|
import { flagToMode, isAppendable, isExclusive, isReadable, isTruncating, isWriteable, parseFlag } from '../internal/file.js';
|
|
@@ -64,7 +65,6 @@ import { dirname, join, parse, resolve } from './path.js';
|
|
|
64
65
|
import { _statfs, fd2file, fdMap, file2fd, fixError, resolveMount } from './shared.js';
|
|
65
66
|
import { ReadStream, WriteStream } from './streams.js';
|
|
66
67
|
import { FSWatcher, emitChange } from './watchers.js';
|
|
67
|
-
import { _throw } from 'utilium';
|
|
68
68
|
export * as constants from './constants.js';
|
|
69
69
|
export class FileHandle {
|
|
70
70
|
constructor(fdOrFile, context) {
|
|
@@ -184,23 +184,7 @@ export class FileHandle {
|
|
|
184
184
|
* The handle will not be closed automatically.
|
|
185
185
|
*/
|
|
186
186
|
readableWebStream(options = {}) {
|
|
187
|
-
return
|
|
188
|
-
start: async (controller) => {
|
|
189
|
-
const chunkSize = 0x1000;
|
|
190
|
-
for (let i = 0; i < 1e7; i++) {
|
|
191
|
-
const result = await this.read(new Uint8Array(chunkSize), 0, chunkSize).catch(controller.error);
|
|
192
|
-
if (!result)
|
|
193
|
-
return;
|
|
194
|
-
if (!result.bytesRead) {
|
|
195
|
-
controller.close();
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
controller.enqueue(result.buffer.subarray(0, result.bytesRead));
|
|
199
|
-
}
|
|
200
|
-
controller.error(new ErrnoError(Errno.EFBIG, 'Too many iterations on readable stream', this.file.path, 'readableWebStream'));
|
|
201
|
-
},
|
|
202
|
-
type: options.type,
|
|
203
|
-
});
|
|
187
|
+
return this.file.fs.streamRead(this.file.path, {});
|
|
204
188
|
}
|
|
205
189
|
/**
|
|
206
190
|
* @todo Implement
|
|
@@ -312,59 +296,15 @@ export class FileHandle {
|
|
|
312
296
|
* Creates a stream for reading from the file.
|
|
313
297
|
* @param options Options for the readable stream
|
|
314
298
|
*/
|
|
315
|
-
createReadStream(options) {
|
|
316
|
-
|
|
317
|
-
const start = (_a = options === null || options === void 0 ? void 0 : options.start) !== null && _a !== void 0 ? _a : this.file.position;
|
|
318
|
-
const stream = new ReadStream({
|
|
319
|
-
highWaterMark: (options === null || options === void 0 ? void 0 : options.highWaterMark) || 64 * 1024,
|
|
320
|
-
encoding: (_b = options === null || options === void 0 ? void 0 : options.encoding) !== null && _b !== void 0 ? _b : undefined,
|
|
321
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
322
|
-
read: async (size) => {
|
|
323
|
-
try {
|
|
324
|
-
if (typeof (options === null || options === void 0 ? void 0 : options.end) === 'number' && start >= options.end) {
|
|
325
|
-
stream.push(null);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
if (typeof (options === null || options === void 0 ? void 0 : options.end) === 'number') {
|
|
329
|
-
size = Math.min(size, options.end - start);
|
|
330
|
-
}
|
|
331
|
-
const result = await this.read(new Uint8Array(size), 0, size, options === null || options === void 0 ? void 0 : options.start);
|
|
332
|
-
stream.push(!result.bytesRead ? null : result.buffer.subarray(0, result.bytesRead));
|
|
333
|
-
}
|
|
334
|
-
catch (error) {
|
|
335
|
-
stream.destroy(error);
|
|
336
|
-
}
|
|
337
|
-
},
|
|
338
|
-
});
|
|
339
|
-
stream.path = this.file.path;
|
|
340
|
-
return stream;
|
|
299
|
+
createReadStream(options = {}) {
|
|
300
|
+
return new ReadStream(options, this);
|
|
341
301
|
}
|
|
342
302
|
/**
|
|
343
303
|
* Creates a stream for writing to the file.
|
|
344
304
|
* @param options Options for the writeable stream.
|
|
345
305
|
*/
|
|
346
|
-
createWriteStream(options) {
|
|
347
|
-
|
|
348
|
-
this.file.position = options.start;
|
|
349
|
-
const { stack } = new Error();
|
|
350
|
-
const stream = new WriteStream({
|
|
351
|
-
highWaterMark: options === null || options === void 0 ? void 0 : options.highWaterMark,
|
|
352
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
353
|
-
write: async (chunk, encoding, callback) => {
|
|
354
|
-
try {
|
|
355
|
-
const { bytesWritten } = await this.write(chunk, null, encoding);
|
|
356
|
-
if (bytesWritten == chunk.length)
|
|
357
|
-
return callback();
|
|
358
|
-
throw new ErrnoError(Errno.EIO, `Failed to write full chunk of write stream (wrote ${bytesWritten}/${chunk.length} bytes)`);
|
|
359
|
-
}
|
|
360
|
-
catch (error) {
|
|
361
|
-
error.stack += stack === null || stack === void 0 ? void 0 : stack.slice(5);
|
|
362
|
-
callback(error);
|
|
363
|
-
}
|
|
364
|
-
},
|
|
365
|
-
});
|
|
366
|
-
stream.path = this.file.path;
|
|
367
|
-
return stream;
|
|
306
|
+
createWriteStream(options = {}) {
|
|
307
|
+
return new WriteStream(options, this);
|
|
368
308
|
}
|
|
369
309
|
}
|
|
370
310
|
export async function rename(oldPath, newPath) {
|
package/dist/vfs/shared.js
CHANGED
|
@@ -46,13 +46,12 @@ export function mount(mountPoint, fs) {
|
|
|
46
46
|
if (mountPoint[0] != '/')
|
|
47
47
|
mountPoint = '/' + mountPoint;
|
|
48
48
|
mountPoint = resolve(mountPoint);
|
|
49
|
-
if (mounts.has(mountPoint))
|
|
50
|
-
throw err(new ErrnoError(Errno.EINVAL, 'Mount point
|
|
51
|
-
}
|
|
49
|
+
if (mounts.has(mountPoint))
|
|
50
|
+
throw err(new ErrnoError(Errno.EINVAL, 'Mount point is already in use: ' + mountPoint));
|
|
52
51
|
fs._mountPoint = mountPoint;
|
|
53
52
|
mounts.set(mountPoint, fs);
|
|
54
53
|
info(`Mounted ${fs.name} on ${mountPoint}`);
|
|
55
|
-
debug(`${fs.name} attributes: ${[...fs.attributes].map(([k, v]) => (v !== undefined && v !== null ? k + '=' + v :
|
|
54
|
+
debug(`${fs.name} attributes: ${[...fs.attributes].map(([k, v]) => (v !== undefined && v !== null ? k + '=' + v : k)).join(', ')}`);
|
|
56
55
|
}
|
|
57
56
|
/**
|
|
58
57
|
* Unmounts the file system at `mountPoint`.
|
package/dist/vfs/streams.d.ts
CHANGED
|
@@ -1,16 +1,74 @@
|
|
|
1
|
+
/// <reference path="../../types/readable-stream.d.ts" preserve="true" />
|
|
2
|
+
import type { Abortable } from 'node:events';
|
|
1
3
|
import type * as fs from 'node:fs';
|
|
4
|
+
import type { CreateReadStreamOptions, CreateWriteStreamOptions } from 'node:fs/promises';
|
|
2
5
|
import type { Callback } from '../utils.js';
|
|
6
|
+
import type { FileHandle } from './promises.js';
|
|
3
7
|
import { Readable, Writable } from 'readable-stream';
|
|
8
|
+
interface FSImplementation {
|
|
9
|
+
open?: (...args: unknown[]) => unknown;
|
|
10
|
+
close?: (...args: unknown[]) => unknown;
|
|
11
|
+
}
|
|
12
|
+
interface StreamOptions extends Abortable {
|
|
13
|
+
flags?: string;
|
|
14
|
+
encoding?: BufferEncoding;
|
|
15
|
+
fd?: number | FileHandle;
|
|
16
|
+
mode?: number;
|
|
17
|
+
autoClose?: boolean;
|
|
18
|
+
emitClose?: boolean;
|
|
19
|
+
start?: number;
|
|
20
|
+
highWaterMark?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* This type is from node:fs but not exported.
|
|
24
|
+
* @hidden
|
|
25
|
+
*/
|
|
26
|
+
export interface ReadStreamOptions extends StreamOptions {
|
|
27
|
+
fs?: FSImplementation & {
|
|
28
|
+
read: (...args: unknown[]) => unknown;
|
|
29
|
+
};
|
|
30
|
+
end?: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* This type is from node:fs but not exported.
|
|
34
|
+
* @hidden
|
|
35
|
+
*/
|
|
36
|
+
export interface WriteStreamOptions extends StreamOptions {
|
|
37
|
+
flush?: boolean;
|
|
38
|
+
fs?: FSImplementation & {
|
|
39
|
+
write: (...args: unknown[]) => unknown;
|
|
40
|
+
writev?: (...args: unknown[]) => unknown;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A ReadStream implementation that wraps an underlying global ReadableStream.
|
|
45
|
+
*/
|
|
4
46
|
export declare class ReadStream extends Readable implements fs.ReadStream {
|
|
5
|
-
close: (callback?: Callback<[void], null>) => void;
|
|
6
|
-
wrap(oldStream: NodeJS.ReadableStream): this;
|
|
7
|
-
bytesRead: number;
|
|
8
|
-
path: string | Buffer;
|
|
9
47
|
pending: boolean;
|
|
48
|
+
private _path;
|
|
49
|
+
private _bytesRead;
|
|
50
|
+
private reader?;
|
|
51
|
+
constructor(opts: CreateReadStreamOptions | undefined, handleOrPromise: FileHandle | Promise<FileHandle>);
|
|
52
|
+
_read(): Promise<void>;
|
|
53
|
+
close(callback?: Callback<[void], null>): void;
|
|
54
|
+
get path(): string;
|
|
55
|
+
get bytesRead(): number;
|
|
56
|
+
wrap(oldStream: NodeJS.ReadableStream): this;
|
|
10
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* A WriteStream implementation that wraps an underlying global WritableStream.
|
|
60
|
+
*/
|
|
11
61
|
export declare class WriteStream extends Writable implements fs.WriteStream {
|
|
12
|
-
close: (callback?: Callback<[void], null>) => void;
|
|
13
|
-
bytesWritten: number;
|
|
14
|
-
path: string | Buffer;
|
|
15
62
|
pending: boolean;
|
|
63
|
+
private _path;
|
|
64
|
+
private _bytesWritten;
|
|
65
|
+
private writer?;
|
|
66
|
+
private ready;
|
|
67
|
+
constructor(opts: CreateWriteStreamOptions | undefined, handleOrPromise: FileHandle | Promise<FileHandle>);
|
|
68
|
+
_write(chunk: any, encoding: BufferEncoding | 'buffer', callback: (error?: Error | null) => void): Promise<void>;
|
|
69
|
+
_final(callback: (error?: Error | null) => void): Promise<void>;
|
|
70
|
+
close(callback?: Callback<[void], null>): void;
|
|
71
|
+
get path(): string;
|
|
72
|
+
get bytesWritten(): number;
|
|
16
73
|
}
|
|
74
|
+
export {};
|
package/dist/vfs/streams.js
CHANGED
|
@@ -1,36 +1,120 @@
|
|
|
1
1
|
import { Readable, Writable } from 'readable-stream';
|
|
2
2
|
import { Errno, ErrnoError } from '../internal/error.js';
|
|
3
|
+
import { warn } from '../internal/log.js';
|
|
4
|
+
/**
|
|
5
|
+
* A ReadStream implementation that wraps an underlying global ReadableStream.
|
|
6
|
+
*/
|
|
3
7
|
export class ReadStream extends Readable {
|
|
4
|
-
constructor() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
constructor(opts = {}, handleOrPromise) {
|
|
9
|
+
var _a;
|
|
10
|
+
super({ ...opts, encoding: (_a = opts.encoding) !== null && _a !== void 0 ? _a : undefined });
|
|
11
|
+
this.pending = true;
|
|
12
|
+
this._path = '<unknown>';
|
|
13
|
+
this._bytesRead = 0;
|
|
14
|
+
Promise.resolve(handleOrPromise)
|
|
15
|
+
.then(({ file }) => {
|
|
16
|
+
this._path = file.path;
|
|
17
|
+
const internal = file.fs.streamRead(file.path, { start: opts.start, end: opts.end });
|
|
18
|
+
this.reader = internal.getReader();
|
|
19
|
+
this.pending = false;
|
|
20
|
+
return this._read();
|
|
21
|
+
})
|
|
22
|
+
.catch(err => this.destroy(err));
|
|
23
|
+
}
|
|
24
|
+
async _read() {
|
|
25
|
+
if (!this.reader)
|
|
26
|
+
return;
|
|
27
|
+
const { done, value } = await this.reader.read();
|
|
28
|
+
if (done) {
|
|
29
|
+
this.push(null);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this._bytesRead += value.byteLength;
|
|
33
|
+
if (!this.push(value))
|
|
34
|
+
return;
|
|
35
|
+
await this._read();
|
|
36
|
+
}
|
|
37
|
+
close(callback = () => null) {
|
|
38
|
+
try {
|
|
39
|
+
this.destroy();
|
|
40
|
+
this.emit('close');
|
|
41
|
+
callback(null);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
callback(new ErrnoError(Errno.EIO, err.toString()));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
get path() {
|
|
48
|
+
return this._path;
|
|
49
|
+
}
|
|
50
|
+
get bytesRead() {
|
|
51
|
+
return this._bytesRead;
|
|
16
52
|
}
|
|
17
53
|
wrap(oldStream) {
|
|
18
54
|
super.wrap(oldStream);
|
|
19
55
|
return this;
|
|
20
56
|
}
|
|
21
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* A WriteStream implementation that wraps an underlying global WritableStream.
|
|
60
|
+
*/
|
|
22
61
|
export class WriteStream extends Writable {
|
|
23
|
-
constructor() {
|
|
24
|
-
super(
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
62
|
+
constructor(opts = {}, handleOrPromise) {
|
|
63
|
+
super(opts);
|
|
64
|
+
this.pending = true;
|
|
65
|
+
this._path = '<unknown>';
|
|
66
|
+
this._bytesWritten = 0;
|
|
67
|
+
this.ready = Promise.resolve(handleOrPromise)
|
|
68
|
+
.then(({ file }) => {
|
|
69
|
+
this._path = file.path;
|
|
70
|
+
const internal = file.fs.streamWrite(file.path, { start: opts.start });
|
|
71
|
+
this.writer = internal.getWriter();
|
|
72
|
+
this.pending = false;
|
|
73
|
+
})
|
|
74
|
+
.catch(err => this.destroy(err));
|
|
75
|
+
}
|
|
76
|
+
async _write(chunk, encoding, callback) {
|
|
77
|
+
await this.ready;
|
|
78
|
+
if (!this.writer)
|
|
79
|
+
return callback(warn(new ErrnoError(Errno.EAGAIN, 'Underlying writable stream not ready', this._path)));
|
|
80
|
+
if (encoding != 'buffer')
|
|
81
|
+
return callback(warn(new ErrnoError(Errno.ENOTSUP, 'Unsupported encoding for stream', this._path)));
|
|
82
|
+
const data = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
83
|
+
try {
|
|
84
|
+
await this.writer.write(data);
|
|
85
|
+
this._bytesWritten += chunk.byteLength;
|
|
86
|
+
callback();
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
callback(new ErrnoError(Errno.EIO, error.toString()));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async _final(callback) {
|
|
93
|
+
await this.ready;
|
|
94
|
+
if (!this.writer)
|
|
95
|
+
return callback();
|
|
96
|
+
try {
|
|
97
|
+
await this.writer.close();
|
|
98
|
+
callback();
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
callback(new ErrnoError(Errno.EIO, error.toString()));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
close(callback = () => null) {
|
|
105
|
+
try {
|
|
106
|
+
this.destroy();
|
|
107
|
+
this.emit('close');
|
|
108
|
+
callback(null);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
callback(new ErrnoError(Errno.EIO, error.toString()));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
get path() {
|
|
115
|
+
return this._path;
|
|
116
|
+
}
|
|
117
|
+
get bytesWritten() {
|
|
118
|
+
return this._bytesWritten;
|
|
35
119
|
}
|
|
36
120
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenfs/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.1",
|
|
4
4
|
"description": "A filesystem, anywhere",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"url": "https://github.com/zen-fs/core/issues"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
|
-
"node": ">=
|
|
43
|
+
"node": ">= 18"
|
|
44
44
|
},
|
|
45
45
|
"exports": {
|
|
46
46
|
".": "./dist/index.js",
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
"./promises": "./dist/vfs/promises.js",
|
|
50
50
|
"./path": "./dist/vfs/path.js",
|
|
51
51
|
"./eslint": "./eslint.shared.js",
|
|
52
|
-
"./tests/*": "./tests/*"
|
|
52
|
+
"./tests/*": "./tests/*",
|
|
53
|
+
"./types/*": "./types/*"
|
|
53
54
|
},
|
|
54
55
|
"publishConfig": {
|
|
55
56
|
"access": "public",
|
package/tests/fs/streams.test.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
3
|
import { fs } from '../common.js';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
4
|
|
|
6
5
|
// Top-level initialization
|
|
7
6
|
const testFilePath = 'test-file.txt';
|
|
@@ -43,30 +42,6 @@ suite('Streams', () => {
|
|
|
43
42
|
});
|
|
44
43
|
});
|
|
45
44
|
|
|
46
|
-
test('ReadStream declared properties', () => {
|
|
47
|
-
const readStream = new fs.ReadStream();
|
|
48
|
-
assert.equal(readStream.bytesRead, undefined);
|
|
49
|
-
assert.equal(readStream.path, undefined);
|
|
50
|
-
assert.equal(readStream.pending, undefined);
|
|
51
|
-
|
|
52
|
-
// Assign values
|
|
53
|
-
readStream.bytesRead = 10;
|
|
54
|
-
readStream.path = testFilePath;
|
|
55
|
-
readStream.pending = false;
|
|
56
|
-
|
|
57
|
-
assert.equal(readStream.bytesRead, 10);
|
|
58
|
-
assert.equal(readStream.path, testFilePath);
|
|
59
|
-
assert(!readStream.pending);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('ReadStream close method can be called multiple times', async () => {
|
|
63
|
-
const readStream = new fs.ReadStream();
|
|
64
|
-
|
|
65
|
-
const close = promisify(readStream.close);
|
|
66
|
-
await close();
|
|
67
|
-
await close();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
45
|
test('WriteStream writes data correctly', async () => {
|
|
71
46
|
const writeStream = fs.createWriteStream(testFilePathWrite);
|
|
72
47
|
|
|
@@ -92,30 +67,6 @@ suite('Streams', () => {
|
|
|
92
67
|
});
|
|
93
68
|
});
|
|
94
69
|
|
|
95
|
-
test('WriteStream declared properties', () => {
|
|
96
|
-
const writeStream = new fs.WriteStream();
|
|
97
|
-
assert.equal(writeStream.bytesWritten, undefined);
|
|
98
|
-
assert.equal(writeStream.path, undefined);
|
|
99
|
-
assert.equal(writeStream.pending, undefined);
|
|
100
|
-
|
|
101
|
-
// Assign values
|
|
102
|
-
writeStream.bytesWritten = 20;
|
|
103
|
-
writeStream.path = testFilePathWrite;
|
|
104
|
-
writeStream.pending = true;
|
|
105
|
-
|
|
106
|
-
assert.equal(writeStream.bytesWritten, 20);
|
|
107
|
-
assert.equal(writeStream.path, testFilePathWrite);
|
|
108
|
-
assert(writeStream.pending);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('WriteStream close method can be called multiple times', async () => {
|
|
112
|
-
const writeStream = new fs.WriteStream();
|
|
113
|
-
|
|
114
|
-
const close = promisify(writeStream.close);
|
|
115
|
-
await close();
|
|
116
|
-
await close();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
70
|
test('createReadStream with start', async () => {
|
|
120
71
|
await fs.promises.writeFile('hello.txt', 'Hello world');
|
|
121
72
|
|
|
@@ -159,6 +110,22 @@ suite('Streams', () => {
|
|
|
159
110
|
await fileHandle.close();
|
|
160
111
|
});
|
|
161
112
|
|
|
113
|
+
test('readable web stream', async () => {
|
|
114
|
+
const fileHandle = await fs.promises.open(testFilePath, 'r');
|
|
115
|
+
const webStream = fileHandle.readableWebStream();
|
|
116
|
+
|
|
117
|
+
let data = '';
|
|
118
|
+
|
|
119
|
+
const decoder = new TextDecoder();
|
|
120
|
+
|
|
121
|
+
for await (const chunk of webStream) {
|
|
122
|
+
data += decoder.decode(chunk);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
assert.equal(data, testData);
|
|
126
|
+
await fileHandle.close();
|
|
127
|
+
});
|
|
128
|
+
|
|
162
129
|
test('FileHandle.createReadStream after close should give an error', async () => {
|
|
163
130
|
const fileHandle = await fs.promises.open(testFilePath, 'r');
|
|
164
131
|
await fileHandle.close();
|