@zenfs/core 1.2.7 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backends/backend.d.ts +2 -1
- package/dist/backends/fetch.d.ts +10 -3
- package/dist/backends/fetch.js +9 -15
- package/dist/backends/memory.d.ts +0 -1
- package/dist/backends/memory.js +1 -5
- package/dist/backends/port/fs.d.ts +0 -2
- package/dist/backends/port/fs.js +0 -2
- package/dist/devices.d.ts +46 -13
- package/dist/devices.js +23 -6
- package/dist/emulation/shared.d.ts +1 -1
- package/dist/emulation/shared.js +2 -3
- package/package.json +1 -1
- package/readme.md +4 -0
- package/src/backends/backend.ts +2 -1
- package/src/backends/fetch.ts +18 -18
- package/src/backends/memory.ts +1 -5
- package/src/backends/port/fs.ts +0 -2
- package/src/devices.ts +65 -12
- package/src/emulation/shared.ts +2 -3
|
@@ -12,8 +12,9 @@ export type OptionsConfig<T> = {
|
|
|
12
12
|
type: OptionType | readonly OptionType[];
|
|
13
13
|
/**
|
|
14
14
|
* Description of the option. Used in error messages and documentation.
|
|
15
|
+
* @deprecated
|
|
15
16
|
*/
|
|
16
|
-
description
|
|
17
|
+
description?: string;
|
|
17
18
|
/**
|
|
18
19
|
* Whether or not the option is required (optional can be set to null or undefined). Defaults to false.
|
|
19
20
|
*/
|
package/dist/backends/fetch.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ import type { IndexData } from './file_index.js';
|
|
|
6
6
|
* Configuration options for FetchFS.
|
|
7
7
|
*/
|
|
8
8
|
export interface FetchOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Options to pass through to fetch calls
|
|
11
|
+
*/
|
|
12
|
+
requestInit?: RequestInit;
|
|
9
13
|
/**
|
|
10
14
|
* URL to a file index as a JSON file or the file index object itself.
|
|
11
15
|
* Defaults to `index.json`.
|
|
@@ -37,8 +41,9 @@ export interface FetchOptions {
|
|
|
37
41
|
*/
|
|
38
42
|
export declare class FetchFS extends IndexFS {
|
|
39
43
|
readonly baseUrl: string;
|
|
44
|
+
readonly requestInit?: RequestInit;
|
|
40
45
|
ready(): Promise<void>;
|
|
41
|
-
constructor({ index, baseUrl }: FetchOptions);
|
|
46
|
+
constructor({ index, baseUrl, requestInit }: FetchOptions);
|
|
42
47
|
metadata(): FileSystemMetadata;
|
|
43
48
|
/**
|
|
44
49
|
* Preload the `path` into the index.
|
|
@@ -56,12 +61,14 @@ declare const _Fetch: {
|
|
|
56
61
|
readonly index: {
|
|
57
62
|
readonly type: readonly ["string", "object"];
|
|
58
63
|
readonly required: false;
|
|
59
|
-
readonly description: "URL to a file index as a JSON file or the file index object itself, generated with the make-index script. Defaults to `index.json`.";
|
|
60
64
|
};
|
|
61
65
|
readonly baseUrl: {
|
|
62
66
|
readonly type: "string";
|
|
63
67
|
readonly required: false;
|
|
64
|
-
|
|
68
|
+
};
|
|
69
|
+
readonly requestInit: {
|
|
70
|
+
readonly type: "object";
|
|
71
|
+
readonly required: false;
|
|
65
72
|
};
|
|
66
73
|
};
|
|
67
74
|
readonly isAvailable: () => boolean;
|
package/dist/backends/fetch.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Errno, ErrnoError } from '../error.js';
|
|
2
2
|
import { IndexFS } from './file_index.js';
|
|
3
|
-
async function fetchFile(path, type) {
|
|
4
|
-
const response = await fetch(path).catch((e) => {
|
|
3
|
+
async function fetchFile(path, type, init) {
|
|
4
|
+
const response = await fetch(path, init).catch((e) => {
|
|
5
5
|
throw new ErrnoError(Errno.EIO, e.message, path);
|
|
6
6
|
});
|
|
7
7
|
if (!response.ok) {
|
|
@@ -57,13 +57,14 @@ export class FetchFS extends IndexFS {
|
|
|
57
57
|
await this.getData(path, stats);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
constructor({ index = 'index.json', baseUrl = '' }) {
|
|
60
|
+
constructor({ index = 'index.json', baseUrl = '', requestInit }) {
|
|
61
61
|
// prefix url must end in a directory separator.
|
|
62
62
|
if (baseUrl.at(-1) != '/') {
|
|
63
63
|
baseUrl += '/';
|
|
64
64
|
}
|
|
65
|
-
super(typeof index != 'string' ? index : fetchFile(baseUrl + index, 'json'));
|
|
65
|
+
super(typeof index != 'string' ? index : fetchFile(baseUrl + index, 'json', requestInit));
|
|
66
66
|
this.baseUrl = baseUrl;
|
|
67
|
+
this.requestInit = requestInit;
|
|
67
68
|
}
|
|
68
69
|
metadata() {
|
|
69
70
|
return {
|
|
@@ -93,7 +94,7 @@ export class FetchFS extends IndexFS {
|
|
|
93
94
|
if (stats.fileData) {
|
|
94
95
|
return stats.fileData;
|
|
95
96
|
}
|
|
96
|
-
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer');
|
|
97
|
+
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer', this.requestInit);
|
|
97
98
|
stats.fileData = data;
|
|
98
99
|
return data;
|
|
99
100
|
}
|
|
@@ -107,16 +108,9 @@ export class FetchFS extends IndexFS {
|
|
|
107
108
|
const _Fetch = {
|
|
108
109
|
name: 'Fetch',
|
|
109
110
|
options: {
|
|
110
|
-
index: {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
description: 'URL to a file index as a JSON file or the file index object itself, generated with the make-index script. Defaults to `index.json`.',
|
|
114
|
-
},
|
|
115
|
-
baseUrl: {
|
|
116
|
-
type: 'string',
|
|
117
|
-
required: false,
|
|
118
|
-
description: 'Used as the URL prefix for fetched files. Default: Fetch files relative to the index.',
|
|
119
|
-
},
|
|
111
|
+
index: { type: ['string', 'object'], required: false },
|
|
112
|
+
baseUrl: { type: 'string', required: false },
|
|
113
|
+
requestInit: { type: 'object', required: false },
|
|
120
114
|
},
|
|
121
115
|
isAvailable() {
|
|
122
116
|
return typeof globalThis.fetch == 'function';
|
package/dist/backends/memory.js
CHANGED
|
@@ -26,11 +26,7 @@ const _InMemory = {
|
|
|
26
26
|
return true;
|
|
27
27
|
},
|
|
28
28
|
options: {
|
|
29
|
-
name: {
|
|
30
|
-
type: 'string',
|
|
31
|
-
required: false,
|
|
32
|
-
description: 'The name of the store',
|
|
33
|
-
},
|
|
29
|
+
name: { type: 'string', required: false },
|
|
34
30
|
},
|
|
35
31
|
create({ name }) {
|
|
36
32
|
const fs = new StoreFS(new InMemoryStore(name));
|
|
@@ -110,13 +110,11 @@ declare const _Port: {
|
|
|
110
110
|
port: {
|
|
111
111
|
type: "object";
|
|
112
112
|
required: true;
|
|
113
|
-
description: string;
|
|
114
113
|
validator(port: RPC.Port): void;
|
|
115
114
|
};
|
|
116
115
|
timeout: {
|
|
117
116
|
type: "number";
|
|
118
117
|
required: false;
|
|
119
|
-
description: string;
|
|
120
118
|
};
|
|
121
119
|
};
|
|
122
120
|
isAvailable(): boolean;
|
package/dist/backends/port/fs.js
CHANGED
|
@@ -214,7 +214,6 @@ const _Port = {
|
|
|
214
214
|
port: {
|
|
215
215
|
type: 'object',
|
|
216
216
|
required: true,
|
|
217
|
-
description: 'The target port that you want to connect to',
|
|
218
217
|
validator(port) {
|
|
219
218
|
// Check for a `postMessage` function.
|
|
220
219
|
if (typeof port?.postMessage != 'function') {
|
|
@@ -225,7 +224,6 @@ const _Port = {
|
|
|
225
224
|
timeout: {
|
|
226
225
|
type: 'number',
|
|
227
226
|
required: false,
|
|
228
|
-
description: 'How long to wait before the request times out',
|
|
229
227
|
},
|
|
230
228
|
},
|
|
231
229
|
isAvailable() {
|
package/dist/devices.d.ts
CHANGED
|
@@ -8,9 +8,10 @@ import type { Ino } from './inode.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* A device
|
|
10
10
|
* @todo Maybe add major/minor number or some other device information, like a UUID?
|
|
11
|
-
* @
|
|
11
|
+
* @privateRemarks
|
|
12
|
+
* UUIDs were considered, however they don't make sense without an easy mechanism for persistance
|
|
12
13
|
*/
|
|
13
|
-
export interface Device {
|
|
14
|
+
export interface Device<TData = any> {
|
|
14
15
|
/**
|
|
15
16
|
* The device's driver
|
|
16
17
|
*/
|
|
@@ -19,34 +20,64 @@ export interface Device {
|
|
|
19
20
|
* Which inode the device is assigned
|
|
20
21
|
*/
|
|
21
22
|
ino: Ino;
|
|
23
|
+
/**
|
|
24
|
+
* Data associated with a device.
|
|
25
|
+
* This is meant to be used by device drivers.
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
data: TData;
|
|
29
|
+
/**
|
|
30
|
+
* Major device number
|
|
31
|
+
* @experimental
|
|
32
|
+
*/
|
|
33
|
+
major: number;
|
|
34
|
+
/**
|
|
35
|
+
* Minor device number
|
|
36
|
+
* @experimental
|
|
37
|
+
*/
|
|
38
|
+
minor: number;
|
|
22
39
|
}
|
|
23
40
|
/**
|
|
24
41
|
* A device driver
|
|
25
|
-
* @experimental
|
|
26
42
|
*/
|
|
27
|
-
export interface DeviceDriver {
|
|
43
|
+
export interface DeviceDriver<TData = any> {
|
|
28
44
|
/**
|
|
29
45
|
* The name of the device driver
|
|
30
46
|
*/
|
|
31
47
|
name: string;
|
|
32
48
|
/**
|
|
33
49
|
* Whether the device is buffered (a "block" device) or unbuffered (a "character" device)
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
isBuffered?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Initializes a new device.
|
|
55
|
+
* @returns `Device.data`
|
|
56
|
+
* @experimental
|
|
34
57
|
*/
|
|
35
|
-
|
|
58
|
+
init?(ino: Ino): {
|
|
59
|
+
data?: TData;
|
|
60
|
+
minor?: number;
|
|
61
|
+
major?: number;
|
|
62
|
+
};
|
|
36
63
|
/**
|
|
37
64
|
* Synchronously read from the device
|
|
65
|
+
* @group File operations
|
|
38
66
|
*/
|
|
39
67
|
read(file: DeviceFile, buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number;
|
|
40
68
|
/**
|
|
41
69
|
* Synchronously write to the device
|
|
70
|
+
* @group File operations
|
|
42
71
|
*/
|
|
43
72
|
write(file: DeviceFile, buffer: Uint8Array, offset: number, length: number, position?: number): number;
|
|
44
73
|
/**
|
|
45
74
|
* Sync the device
|
|
75
|
+
* @group File operations
|
|
46
76
|
*/
|
|
47
77
|
sync?(file: DeviceFile): void;
|
|
48
78
|
/**
|
|
49
79
|
* Close the device
|
|
80
|
+
* @group File operations
|
|
50
81
|
*/
|
|
51
82
|
close?(file: DeviceFile): void;
|
|
52
83
|
}
|
|
@@ -55,7 +86,6 @@ export interface DeviceDriver {
|
|
|
55
86
|
* This class only does some simple things:
|
|
56
87
|
* It implements `truncate` using `write` and it has non-device methods throw.
|
|
57
88
|
* It is up to device drivers to implement the rest of the functionality.
|
|
58
|
-
* @experimental
|
|
59
89
|
*/
|
|
60
90
|
export declare class DeviceFile extends File {
|
|
61
91
|
fs: DeviceFS;
|
|
@@ -86,11 +116,14 @@ export declare class DeviceFile extends File {
|
|
|
86
116
|
_setTypeSync(): void;
|
|
87
117
|
}
|
|
88
118
|
/**
|
|
89
|
-
*
|
|
119
|
+
* A temporary file system that manages and interfaces with devices
|
|
90
120
|
*/
|
|
91
121
|
export declare class DeviceFS extends StoreFS<InMemoryStore> {
|
|
92
|
-
protected readonly devices: Map<string, Device
|
|
93
|
-
|
|
122
|
+
protected readonly devices: Map<string, Device<any>>;
|
|
123
|
+
/**
|
|
124
|
+
* Creates a new device at `path` relative to the `DeviceFS` root.
|
|
125
|
+
*/
|
|
126
|
+
createDevice<TData = any>(path: string, driver: DeviceDriver<TData>): Device<TData | Record<string, never>>;
|
|
94
127
|
constructor();
|
|
95
128
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
96
129
|
renameSync(oldPath: string, newPath: string): void;
|
|
@@ -150,9 +183,9 @@ export declare const randomDevice: DeviceDriver;
|
|
|
150
183
|
* @experimental
|
|
151
184
|
*/
|
|
152
185
|
declare const _default: {
|
|
153
|
-
null: DeviceDriver
|
|
154
|
-
zero: DeviceDriver
|
|
155
|
-
full: DeviceDriver
|
|
156
|
-
random: DeviceDriver
|
|
186
|
+
null: DeviceDriver<any>;
|
|
187
|
+
zero: DeviceDriver<any>;
|
|
188
|
+
full: DeviceDriver<any>;
|
|
189
|
+
random: DeviceDriver<any>;
|
|
157
190
|
};
|
|
158
191
|
export default _default;
|
package/dist/devices.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This is a great resource: https://www.kernel.org/doc/html/latest/admin-guide/devices.html
|
|
3
|
+
*/
|
|
1
4
|
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
5
|
if (value !== null && value !== void 0) {
|
|
3
6
|
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
@@ -57,7 +60,6 @@ import { basename, dirname } from './emulation/path.js';
|
|
|
57
60
|
* This class only does some simple things:
|
|
58
61
|
* It implements `truncate` using `write` and it has non-device methods throw.
|
|
59
62
|
* It is up to device drivers to implement the rest of the functionality.
|
|
60
|
-
* @experimental
|
|
61
63
|
*/
|
|
62
64
|
export class DeviceFile extends File {
|
|
63
65
|
constructor(fs, path, device) {
|
|
@@ -142,9 +144,12 @@ export class DeviceFile extends File {
|
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
/**
|
|
145
|
-
*
|
|
147
|
+
* A temporary file system that manages and interfaces with devices
|
|
146
148
|
*/
|
|
147
149
|
export class DeviceFS extends StoreFS {
|
|
150
|
+
/**
|
|
151
|
+
* Creates a new device at `path` relative to the `DeviceFS` root.
|
|
152
|
+
*/
|
|
148
153
|
createDevice(path, driver) {
|
|
149
154
|
if (this.existsSync(path)) {
|
|
150
155
|
throw ErrnoError.With('EEXIST', path, 'mknod');
|
|
@@ -155,6 +160,10 @@ export class DeviceFS extends StoreFS {
|
|
|
155
160
|
const dev = {
|
|
156
161
|
driver,
|
|
157
162
|
ino,
|
|
163
|
+
data: {},
|
|
164
|
+
minor: 0,
|
|
165
|
+
major: 0,
|
|
166
|
+
...driver.init?.(ino),
|
|
158
167
|
};
|
|
159
168
|
this.devices.set(path, dev);
|
|
160
169
|
return dev;
|
|
@@ -332,7 +341,9 @@ function defaultWrite(file, buffer, offset, length) {
|
|
|
332
341
|
*/
|
|
333
342
|
export const nullDevice = {
|
|
334
343
|
name: 'null',
|
|
335
|
-
|
|
344
|
+
init() {
|
|
345
|
+
return { major: 1, minor: 3 };
|
|
346
|
+
},
|
|
336
347
|
read() {
|
|
337
348
|
return 0;
|
|
338
349
|
},
|
|
@@ -350,7 +361,9 @@ export const nullDevice = {
|
|
|
350
361
|
*/
|
|
351
362
|
export const zeroDevice = {
|
|
352
363
|
name: 'zero',
|
|
353
|
-
|
|
364
|
+
init() {
|
|
365
|
+
return { major: 1, minor: 5 };
|
|
366
|
+
},
|
|
354
367
|
read(file, buffer, offset = 0, length = buffer.byteLength) {
|
|
355
368
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
356
369
|
for (let i = offset; i < offset + length; i++) {
|
|
@@ -369,7 +382,9 @@ export const zeroDevice = {
|
|
|
369
382
|
*/
|
|
370
383
|
export const fullDevice = {
|
|
371
384
|
name: 'full',
|
|
372
|
-
|
|
385
|
+
init() {
|
|
386
|
+
return { major: 1, minor: 7 };
|
|
387
|
+
},
|
|
373
388
|
read(file, buffer, offset = 0, length = buffer.byteLength) {
|
|
374
389
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
375
390
|
for (let i = offset; i < offset + length; i++) {
|
|
@@ -390,7 +405,9 @@ export const fullDevice = {
|
|
|
390
405
|
*/
|
|
391
406
|
export const randomDevice = {
|
|
392
407
|
name: 'random',
|
|
393
|
-
|
|
408
|
+
init() {
|
|
409
|
+
return { major: 1, minor: 8 };
|
|
410
|
+
},
|
|
394
411
|
read(file, buffer, offset = 0, length = buffer.byteLength) {
|
|
395
412
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
396
413
|
for (let i = offset; i < offset + length; i++) {
|
|
@@ -21,7 +21,7 @@ export declare function mount(mountPoint: string, fs: FileSystem): void;
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function umount(mountPoint: string): void;
|
|
23
23
|
/**
|
|
24
|
-
* Gets the internal FileSystem for the path, then returns it along with the path relative to the FS' root
|
|
24
|
+
* Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root
|
|
25
25
|
*/
|
|
26
26
|
export declare function resolveMount(path: string): {
|
|
27
27
|
fs: FileSystem;
|
package/dist/emulation/shared.js
CHANGED
|
@@ -43,7 +43,7 @@ export function mount(mountPoint, fs) {
|
|
|
43
43
|
*/
|
|
44
44
|
export function umount(mountPoint) {
|
|
45
45
|
if (mountPoint[0] !== '/') {
|
|
46
|
-
mountPoint =
|
|
46
|
+
mountPoint = '/' + mountPoint;
|
|
47
47
|
}
|
|
48
48
|
mountPoint = resolve(mountPoint);
|
|
49
49
|
if (!mounts.has(mountPoint)) {
|
|
@@ -52,11 +52,10 @@ export function umount(mountPoint) {
|
|
|
52
52
|
mounts.delete(mountPoint);
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
|
-
* Gets the internal FileSystem for the path, then returns it along with the path relative to the FS' root
|
|
55
|
+
* Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root
|
|
56
56
|
*/
|
|
57
57
|
export function resolveMount(path) {
|
|
58
58
|
path = normalizePath(path);
|
|
59
|
-
// Maybe do something for devices here
|
|
60
59
|
const sortedMounts = [...mounts].sort((a, b) => (a[0].length > b[0].length ? -1 : 1)); // descending order of the string length
|
|
61
60
|
for (const [mountPoint, fs] of sortedMounts) {
|
|
62
61
|
// We know path is normalized, so it would be a substring of the mount point.
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -201,6 +201,10 @@ If you would like to see a more intuitive way adding custom devices (e.g. `fs.mk
|
|
|
201
201
|
|
|
202
202
|
ZenFS exports a drop-in for Node's `fs` module (up to the version of `@types/node` in package.json), so you can use it for your bundler of preference using the default export.
|
|
203
203
|
|
|
204
|
+
## Sponsors
|
|
205
|
+
|
|
206
|
+
A huge thank you to [ Deco.cx](https://github.com/deco-cx) for sponsoring ZenFS and helping to make this possible.
|
|
207
|
+
|
|
204
208
|
## Building
|
|
205
209
|
|
|
206
210
|
- Make sure you have Node and NPM installed. You must have Node v18 or newer.
|
package/src/backends/backend.ts
CHANGED
|
@@ -17,8 +17,9 @@ export type OptionsConfig<T> = {
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Description of the option. Used in error messages and documentation.
|
|
20
|
+
* @deprecated
|
|
20
21
|
*/
|
|
21
|
-
description
|
|
22
|
+
description?: string;
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Whether or not the option is required (optional can be set to null or undefined). Defaults to false.
|
package/src/backends/fetch.ts
CHANGED
|
@@ -12,11 +12,11 @@ import type { IndexData } from './file_index.js';
|
|
|
12
12
|
* constants.
|
|
13
13
|
* @hidden
|
|
14
14
|
*/
|
|
15
|
-
async function fetchFile(path: string, type: 'buffer'): Promise<Uint8Array>;
|
|
16
|
-
async function fetchFile<T extends object>(path: string, type: 'json'): Promise<T>;
|
|
17
|
-
async function fetchFile<T extends object>(path: string, type: 'buffer' | 'json'): Promise<T | Uint8Array>;
|
|
18
|
-
async function fetchFile<T extends object>(path: string, type: string): Promise<T | Uint8Array> {
|
|
19
|
-
const response = await fetch(path).catch((e: Error) => {
|
|
15
|
+
async function fetchFile(path: string, type: 'buffer', init?: RequestInit): Promise<Uint8Array>;
|
|
16
|
+
async function fetchFile<T extends object>(path: string, type: 'json', init?: RequestInit): Promise<T>;
|
|
17
|
+
async function fetchFile<T extends object>(path: string, type: 'buffer' | 'json', init?: RequestInit): Promise<T | Uint8Array>;
|
|
18
|
+
async function fetchFile<T extends object>(path: string, type: string, init?: RequestInit): Promise<T | Uint8Array> {
|
|
19
|
+
const response = await fetch(path, init).catch((e: Error) => {
|
|
20
20
|
throw new ErrnoError(Errno.EIO, e.message, path);
|
|
21
21
|
});
|
|
22
22
|
if (!response.ok) {
|
|
@@ -42,6 +42,11 @@ async function fetchFile<T extends object>(path: string, type: string): Promise<
|
|
|
42
42
|
* Configuration options for FetchFS.
|
|
43
43
|
*/
|
|
44
44
|
export interface FetchOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Options to pass through to fetch calls
|
|
47
|
+
*/
|
|
48
|
+
requestInit?: RequestInit;
|
|
49
|
+
|
|
45
50
|
/**
|
|
46
51
|
* URL to a file index as a JSON file or the file index object itself.
|
|
47
52
|
* Defaults to `index.json`.
|
|
@@ -75,6 +80,7 @@ export interface FetchOptions {
|
|
|
75
80
|
*/
|
|
76
81
|
export class FetchFS extends IndexFS {
|
|
77
82
|
public readonly baseUrl: string;
|
|
83
|
+
public readonly requestInit?: RequestInit;
|
|
78
84
|
|
|
79
85
|
public async ready(): Promise<void> {
|
|
80
86
|
if (this._isInitialized) {
|
|
@@ -94,15 +100,16 @@ export class FetchFS extends IndexFS {
|
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
public constructor({ index = 'index.json', baseUrl = '' }: FetchOptions) {
|
|
103
|
+
public constructor({ index = 'index.json', baseUrl = '', requestInit }: FetchOptions) {
|
|
98
104
|
// prefix url must end in a directory separator.
|
|
99
105
|
if (baseUrl.at(-1) != '/') {
|
|
100
106
|
baseUrl += '/';
|
|
101
107
|
}
|
|
102
108
|
|
|
103
|
-
super(typeof index != 'string' ? index : fetchFile<IndexData>(baseUrl + index, 'json'));
|
|
109
|
+
super(typeof index != 'string' ? index : fetchFile<IndexData>(baseUrl + index, 'json', requestInit));
|
|
104
110
|
|
|
105
111
|
this.baseUrl = baseUrl;
|
|
112
|
+
this.requestInit = requestInit;
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
public metadata(): FileSystemMetadata {
|
|
@@ -136,7 +143,7 @@ export class FetchFS extends IndexFS {
|
|
|
136
143
|
return stats.fileData;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer');
|
|
146
|
+
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer', this.requestInit);
|
|
140
147
|
stats.fileData = data;
|
|
141
148
|
return data;
|
|
142
149
|
}
|
|
@@ -154,16 +161,9 @@ const _Fetch = {
|
|
|
154
161
|
name: 'Fetch',
|
|
155
162
|
|
|
156
163
|
options: {
|
|
157
|
-
index: {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
description: 'URL to a file index as a JSON file or the file index object itself, generated with the make-index script. Defaults to `index.json`.',
|
|
161
|
-
},
|
|
162
|
-
baseUrl: {
|
|
163
|
-
type: 'string',
|
|
164
|
-
required: false,
|
|
165
|
-
description: 'Used as the URL prefix for fetched files. Default: Fetch files relative to the index.',
|
|
166
|
-
},
|
|
164
|
+
index: { type: ['string', 'object'], required: false },
|
|
165
|
+
baseUrl: { type: 'string', required: false },
|
|
166
|
+
requestInit: { type: 'object', required: false },
|
|
167
167
|
},
|
|
168
168
|
|
|
169
169
|
isAvailable(): boolean {
|
package/src/backends/memory.ts
CHANGED
|
@@ -32,11 +32,7 @@ const _InMemory = {
|
|
|
32
32
|
return true;
|
|
33
33
|
},
|
|
34
34
|
options: {
|
|
35
|
-
name: {
|
|
36
|
-
type: 'string',
|
|
37
|
-
required: false,
|
|
38
|
-
description: 'The name of the store',
|
|
39
|
-
},
|
|
35
|
+
name: { type: 'string', required: false },
|
|
40
36
|
},
|
|
41
37
|
create({ name }: { name?: string }) {
|
|
42
38
|
const fs = new StoreFS(new InMemoryStore(name));
|
package/src/backends/port/fs.ts
CHANGED
|
@@ -298,7 +298,6 @@ const _Port = {
|
|
|
298
298
|
port: {
|
|
299
299
|
type: 'object',
|
|
300
300
|
required: true,
|
|
301
|
-
description: 'The target port that you want to connect to',
|
|
302
301
|
validator(port: RPC.Port) {
|
|
303
302
|
// Check for a `postMessage` function.
|
|
304
303
|
if (typeof port?.postMessage != 'function') {
|
|
@@ -309,7 +308,6 @@ const _Port = {
|
|
|
309
308
|
timeout: {
|
|
310
309
|
type: 'number',
|
|
311
310
|
required: false,
|
|
312
|
-
description: 'How long to wait before the request times out',
|
|
313
311
|
},
|
|
314
312
|
},
|
|
315
313
|
|
package/src/devices.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This is a great resource: https://www.kernel.org/doc/html/latest/admin-guide/devices.html
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import type { FileReadResult } from 'node:fs/promises';
|
|
2
6
|
import { InMemoryStore } from './backends/memory.js';
|
|
3
7
|
import { StoreFS } from './backends/store/fs.js';
|
|
@@ -12,9 +16,10 @@ import type { Ino } from './inode.js';
|
|
|
12
16
|
/**
|
|
13
17
|
* A device
|
|
14
18
|
* @todo Maybe add major/minor number or some other device information, like a UUID?
|
|
15
|
-
* @
|
|
19
|
+
* @privateRemarks
|
|
20
|
+
* UUIDs were considered, however they don't make sense without an easy mechanism for persistance
|
|
16
21
|
*/
|
|
17
|
-
export interface Device {
|
|
22
|
+
export interface Device<TData = any> {
|
|
18
23
|
/**
|
|
19
24
|
* The device's driver
|
|
20
25
|
*/
|
|
@@ -24,13 +29,31 @@ export interface Device {
|
|
|
24
29
|
* Which inode the device is assigned
|
|
25
30
|
*/
|
|
26
31
|
ino: Ino;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Data associated with a device.
|
|
35
|
+
* This is meant to be used by device drivers.
|
|
36
|
+
* @experimental
|
|
37
|
+
*/
|
|
38
|
+
data: TData;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Major device number
|
|
42
|
+
* @experimental
|
|
43
|
+
*/
|
|
44
|
+
major: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Minor device number
|
|
48
|
+
* @experimental
|
|
49
|
+
*/
|
|
50
|
+
minor: number;
|
|
27
51
|
}
|
|
28
52
|
|
|
29
53
|
/**
|
|
30
54
|
* A device driver
|
|
31
|
-
* @experimental
|
|
32
55
|
*/
|
|
33
|
-
export interface DeviceDriver {
|
|
56
|
+
export interface DeviceDriver<TData = any> {
|
|
34
57
|
/**
|
|
35
58
|
* The name of the device driver
|
|
36
59
|
*/
|
|
@@ -38,26 +61,42 @@ export interface DeviceDriver {
|
|
|
38
61
|
|
|
39
62
|
/**
|
|
40
63
|
* Whether the device is buffered (a "block" device) or unbuffered (a "character" device)
|
|
64
|
+
* @default false
|
|
41
65
|
*/
|
|
42
|
-
isBuffered
|
|
66
|
+
isBuffered?: boolean;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Initializes a new device.
|
|
70
|
+
* @returns `Device.data`
|
|
71
|
+
* @experimental
|
|
72
|
+
*/
|
|
73
|
+
init?(ino: Ino): {
|
|
74
|
+
data?: TData;
|
|
75
|
+
minor?: number;
|
|
76
|
+
major?: number;
|
|
77
|
+
};
|
|
43
78
|
|
|
44
79
|
/**
|
|
45
80
|
* Synchronously read from the device
|
|
81
|
+
* @group File operations
|
|
46
82
|
*/
|
|
47
83
|
read(file: DeviceFile, buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number;
|
|
48
84
|
|
|
49
85
|
/**
|
|
50
86
|
* Synchronously write to the device
|
|
87
|
+
* @group File operations
|
|
51
88
|
*/
|
|
52
89
|
write(file: DeviceFile, buffer: Uint8Array, offset: number, length: number, position?: number): number;
|
|
53
90
|
|
|
54
91
|
/**
|
|
55
92
|
* Sync the device
|
|
93
|
+
* @group File operations
|
|
56
94
|
*/
|
|
57
95
|
sync?(file: DeviceFile): void;
|
|
58
96
|
|
|
59
97
|
/**
|
|
60
98
|
* Close the device
|
|
99
|
+
* @group File operations
|
|
61
100
|
*/
|
|
62
101
|
close?(file: DeviceFile): void;
|
|
63
102
|
}
|
|
@@ -67,7 +106,6 @@ export interface DeviceDriver {
|
|
|
67
106
|
* This class only does some simple things:
|
|
68
107
|
* It implements `truncate` using `write` and it has non-device methods throw.
|
|
69
108
|
* It is up to device drivers to implement the rest of the functionality.
|
|
70
|
-
* @experimental
|
|
71
109
|
*/
|
|
72
110
|
export class DeviceFile extends File {
|
|
73
111
|
public position = 0;
|
|
@@ -182,12 +220,15 @@ export class DeviceFile extends File {
|
|
|
182
220
|
}
|
|
183
221
|
|
|
184
222
|
/**
|
|
185
|
-
*
|
|
223
|
+
* A temporary file system that manages and interfaces with devices
|
|
186
224
|
*/
|
|
187
225
|
export class DeviceFS extends StoreFS<InMemoryStore> {
|
|
188
226
|
protected readonly devices = new Map<string, Device>();
|
|
189
227
|
|
|
190
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Creates a new device at `path` relative to the `DeviceFS` root.
|
|
230
|
+
*/
|
|
231
|
+
public createDevice<TData = any>(path: string, driver: DeviceDriver<TData>): Device<TData | Record<string, never>> {
|
|
191
232
|
if (this.existsSync(path)) {
|
|
192
233
|
throw ErrnoError.With('EEXIST', path, 'mknod');
|
|
193
234
|
}
|
|
@@ -196,6 +237,10 @@ export class DeviceFS extends StoreFS<InMemoryStore> {
|
|
|
196
237
|
const dev = {
|
|
197
238
|
driver,
|
|
198
239
|
ino,
|
|
240
|
+
data: {},
|
|
241
|
+
minor: 0,
|
|
242
|
+
major: 0,
|
|
243
|
+
...driver.init?.(ino),
|
|
199
244
|
};
|
|
200
245
|
this.devices.set(path, dev);
|
|
201
246
|
return dev;
|
|
@@ -373,7 +418,9 @@ function defaultWrite(file: DeviceFile, buffer: Uint8Array, offset: number, leng
|
|
|
373
418
|
*/
|
|
374
419
|
export const nullDevice: DeviceDriver = {
|
|
375
420
|
name: 'null',
|
|
376
|
-
|
|
421
|
+
init() {
|
|
422
|
+
return { major: 1, minor: 3 };
|
|
423
|
+
},
|
|
377
424
|
read(): number {
|
|
378
425
|
return 0;
|
|
379
426
|
},
|
|
@@ -392,7 +439,9 @@ export const nullDevice: DeviceDriver = {
|
|
|
392
439
|
*/
|
|
393
440
|
export const zeroDevice: DeviceDriver = {
|
|
394
441
|
name: 'zero',
|
|
395
|
-
|
|
442
|
+
init() {
|
|
443
|
+
return { major: 1, minor: 5 };
|
|
444
|
+
},
|
|
396
445
|
read(file: DeviceFile, buffer: ArrayBufferView, offset = 0, length = buffer.byteLength): number {
|
|
397
446
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
398
447
|
for (let i = offset; i < offset + length; i++) {
|
|
@@ -412,7 +461,9 @@ export const zeroDevice: DeviceDriver = {
|
|
|
412
461
|
*/
|
|
413
462
|
export const fullDevice: DeviceDriver = {
|
|
414
463
|
name: 'full',
|
|
415
|
-
|
|
464
|
+
init() {
|
|
465
|
+
return { major: 1, minor: 7 };
|
|
466
|
+
},
|
|
416
467
|
read(file: DeviceFile, buffer: ArrayBufferView, offset = 0, length = buffer.byteLength): number {
|
|
417
468
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
418
469
|
for (let i = offset; i < offset + length; i++) {
|
|
@@ -435,7 +486,9 @@ export const fullDevice: DeviceDriver = {
|
|
|
435
486
|
*/
|
|
436
487
|
export const randomDevice: DeviceDriver = {
|
|
437
488
|
name: 'random',
|
|
438
|
-
|
|
489
|
+
init() {
|
|
490
|
+
return { major: 1, minor: 8 };
|
|
491
|
+
},
|
|
439
492
|
read(file: DeviceFile, buffer: ArrayBufferView, offset = 0, length = buffer.byteLength): number {
|
|
440
493
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
441
494
|
for (let i = offset; i < offset + length; i++) {
|
package/src/emulation/shared.ts
CHANGED
|
@@ -54,7 +54,7 @@ export function mount(mountPoint: string, fs: FileSystem): void {
|
|
|
54
54
|
*/
|
|
55
55
|
export function umount(mountPoint: string): void {
|
|
56
56
|
if (mountPoint[0] !== '/') {
|
|
57
|
-
mountPoint =
|
|
57
|
+
mountPoint = '/' + mountPoint;
|
|
58
58
|
}
|
|
59
59
|
mountPoint = resolve(mountPoint);
|
|
60
60
|
if (!mounts.has(mountPoint)) {
|
|
@@ -64,11 +64,10 @@ export function umount(mountPoint: string): void {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Gets the internal FileSystem for the path, then returns it along with the path relative to the FS' root
|
|
67
|
+
* Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root
|
|
68
68
|
*/
|
|
69
69
|
export function resolveMount(path: string): { fs: FileSystem; path: string; mountPoint: string } {
|
|
70
70
|
path = normalizePath(path);
|
|
71
|
-
// Maybe do something for devices here
|
|
72
71
|
const sortedMounts = [...mounts].sort((a, b) => (a[0].length > b[0].length ? -1 : 1)); // descending order of the string length
|
|
73
72
|
for (const [mountPoint, fs] of sortedMounts) {
|
|
74
73
|
// We know path is normalized, so it would be a substring of the mount point.
|