@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.
@@ -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: string;
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
  */
@@ -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
- readonly description: "Used as the URL prefix for fetched files. Default: Fetch files relative to the index.";
68
+ };
69
+ readonly requestInit: {
70
+ readonly type: "object";
71
+ readonly required: false;
65
72
  };
66
73
  };
67
74
  readonly isAvailable: () => boolean;
@@ -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
- type: ['string', 'object'],
112
- required: false,
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';
@@ -22,7 +22,6 @@ declare const _InMemory: {
22
22
  readonly name: {
23
23
  readonly type: "string";
24
24
  readonly required: false;
25
- readonly description: "The name of the store";
26
25
  };
27
26
  };
28
27
  readonly create: ({ name }: {
@@ -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;
@@ -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
- * @experimental
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
- isBuffered: boolean;
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
- * @experimental
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
- createDevice(path: string, driver: DeviceDriver): Device;
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
- * @experimental
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
- isBuffered: false,
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
- isBuffered: false,
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
- isBuffered: false,
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
- isBuffered: false,
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;
@@ -43,7 +43,7 @@ export function mount(mountPoint, fs) {
43
43
  */
44
44
  export function umount(mountPoint) {
45
45
  if (mountPoint[0] !== '/') {
46
- mountPoint = `/${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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
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 logo](https://avatars.githubusercontent.com/deco-cx?size=20) 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.
@@ -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: string;
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.
@@ -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
- type: ['string', 'object'],
159
- required: false,
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 {
@@ -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));
@@ -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
- * @experimental
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: boolean;
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
- * @experimental
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
- public createDevice(path: string, driver: DeviceDriver): Device {
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
- isBuffered: false,
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
- isBuffered: false,
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
- isBuffered: false,
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
- isBuffered: false,
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++) {
@@ -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 = `/${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.