@zenfs/core 1.7.2 → 1.8.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.
Files changed (68) hide show
  1. package/dist/backends/backend.js +3 -4
  2. package/dist/backends/fetch.d.ts +17 -18
  3. package/dist/backends/fetch.js +95 -58
  4. package/dist/backends/index.d.ts +2 -1
  5. package/dist/backends/index.js +2 -1
  6. package/dist/backends/memory.d.ts +1 -1
  7. package/dist/backends/overlay.d.ts +8 -14
  8. package/dist/backends/overlay.js +38 -31
  9. package/dist/backends/passthrough.d.ts +8 -3
  10. package/dist/backends/passthrough.js +148 -4
  11. package/dist/backends/port/fs.d.ts +15 -49
  12. package/dist/backends/port/fs.js +28 -116
  13. package/dist/backends/port/rpc.d.ts +13 -6
  14. package/dist/backends/port/rpc.js +9 -7
  15. package/dist/backends/store/file_index.d.ts +38 -0
  16. package/dist/backends/store/file_index.js +76 -0
  17. package/dist/backends/store/fs.d.ts +39 -34
  18. package/dist/backends/store/fs.js +407 -238
  19. package/dist/backends/store/index_fs.d.ts +34 -0
  20. package/dist/backends/store/index_fs.js +67 -0
  21. package/dist/backends/store/inode.d.ts +26 -8
  22. package/dist/backends/store/inode.js +92 -91
  23. package/dist/backends/store/simple.d.ts +20 -20
  24. package/dist/backends/store/simple.js +3 -4
  25. package/dist/backends/store/store.d.ts +12 -12
  26. package/dist/backends/store/store.js +4 -6
  27. package/dist/devices.d.ts +44 -21
  28. package/dist/devices.js +110 -55
  29. package/dist/file.d.ts +111 -7
  30. package/dist/file.js +324 -92
  31. package/dist/filesystem.d.ts +44 -4
  32. package/dist/mixins/async.js +12 -6
  33. package/dist/mixins/mutexed.d.ts +8 -3
  34. package/dist/mixins/mutexed.js +57 -1
  35. package/dist/mixins/readonly.d.ts +17 -16
  36. package/dist/mixins/readonly.js +6 -0
  37. package/dist/mixins/sync.d.ts +1 -1
  38. package/dist/stats.d.ts +12 -6
  39. package/dist/stats.js +14 -6
  40. package/dist/utils.d.ts +23 -3
  41. package/dist/utils.js +58 -10
  42. package/dist/vfs/async.js +1 -1
  43. package/dist/vfs/constants.d.ts +2 -2
  44. package/dist/vfs/constants.js +2 -2
  45. package/dist/vfs/dir.js +3 -1
  46. package/dist/vfs/index.js +4 -1
  47. package/dist/vfs/promises.js +33 -13
  48. package/dist/vfs/shared.js +2 -0
  49. package/dist/vfs/sync.js +25 -13
  50. package/dist/vfs/types.d.ts +15 -0
  51. package/eslint.shared.js +1 -0
  52. package/package.json +2 -3
  53. package/readme.md +2 -2
  54. package/scripts/test.js +73 -11
  55. package/tests/common/mutex.test.ts +1 -1
  56. package/tests/fetch/run.sh +16 -0
  57. package/tests/fetch/server.ts +49 -0
  58. package/tests/fetch/setup.ts +13 -0
  59. package/tests/fs/read.test.ts +10 -10
  60. package/tests/fs/times.test.ts +2 -2
  61. package/tests/fs/write.test.ts +6 -11
  62. package/tests/setup/index.ts +38 -0
  63. package/tests/setup/port.ts +15 -0
  64. package/dist/backends/file_index.d.ts +0 -63
  65. package/dist/backends/file_index.js +0 -163
  66. package/tests/common/async.test.ts +0 -31
  67. package/tests/setup/cow+fetch.ts +0 -45
  68. /package/tests/fs/{appendFile.test.ts → append.test.ts} +0 -0
@@ -1,7 +1,59 @@
1
- import { FileSystem } from '../filesystem.js';
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
2
53
  import { ErrnoError } from '../error.js';
3
- import { Stats } from '../stats.js';
4
54
  import { File } from '../file.js';
55
+ import { FileSystem } from '../filesystem.js';
56
+ import { Stats } from '../stats.js';
5
57
  import { join, resolve } from '../vfs/path.js';
6
58
  class PassthroughFile extends File {
7
59
  constructor(fs, path, fd) {
@@ -281,7 +333,23 @@ export class PassthroughFS extends FileSystem {
281
333
  */
282
334
  async sync(path, data, stats) {
283
335
  try {
284
- await this.nodeFS.promises.writeFile(this.path(path), data);
336
+ const env_1 = { stack: [], error: void 0, hasError: false };
337
+ try {
338
+ const handle = __addDisposableResource(env_1, await this.nodeFS.promises.open(this.path(path), 'w'), true);
339
+ await handle.writeFile(data);
340
+ await handle.chmod(stats.mode);
341
+ await handle.chown(stats.uid, stats.gid);
342
+ await handle.utimes(stats.atimeMs, stats.mtimeMs);
343
+ }
344
+ catch (e_1) {
345
+ env_1.error = e_1;
346
+ env_1.hasError = true;
347
+ }
348
+ finally {
349
+ const result_1 = __disposeResources(env_1);
350
+ if (result_1)
351
+ await result_1;
352
+ }
285
353
  }
286
354
  catch (err) {
287
355
  this.error(err, path);
@@ -292,7 +360,11 @@ export class PassthroughFS extends FileSystem {
292
360
  */
293
361
  syncSync(path, data, stats) {
294
362
  try {
295
- this.nodeFS.writeFileSync(this.path(path), data);
363
+ const p = this.path(path);
364
+ this.nodeFS.writeFileSync(p, data);
365
+ this.nodeFS.chmodSync(p, stats.mode);
366
+ this.nodeFS.chownSync(p, stats.uid, stats.gid);
367
+ this.nodeFS.utimesSync(p, stats.atimeMs, stats.mtimeMs);
296
368
  }
297
369
  catch (err) {
298
370
  this.error(err, path);
@@ -320,6 +392,78 @@ export class PassthroughFS extends FileSystem {
320
392
  this.error(err, target);
321
393
  }
322
394
  }
395
+ async read(path, buffer, offset, end) {
396
+ try {
397
+ const env_2 = { stack: [], error: void 0, hasError: false };
398
+ try {
399
+ const handle = __addDisposableResource(env_2, await this.nodeFS.promises.open(this.path(path), 'r'), true);
400
+ await handle.read({ buffer, offset, length: end - offset });
401
+ }
402
+ catch (e_2) {
403
+ env_2.error = e_2;
404
+ env_2.hasError = true;
405
+ }
406
+ finally {
407
+ const result_2 = __disposeResources(env_2);
408
+ if (result_2)
409
+ await result_2;
410
+ }
411
+ }
412
+ catch (err) {
413
+ this.error(err, path);
414
+ }
415
+ }
416
+ readSync(path, buffer, offset, end) {
417
+ let fd;
418
+ try {
419
+ fd = this.nodeFS.openSync(this.path(path), 'r');
420
+ this.nodeFS.readSync(fd, buffer, { offset, length: end - offset });
421
+ }
422
+ catch (err) {
423
+ this.error(err, path);
424
+ }
425
+ finally {
426
+ if (fd)
427
+ this.nodeFS.closeSync(fd);
428
+ }
429
+ // unreachable
430
+ throw ErrnoError.With('EIO', path, 'read');
431
+ }
432
+ async write(path, buffer, offset) {
433
+ try {
434
+ const env_3 = { stack: [], error: void 0, hasError: false };
435
+ try {
436
+ const handle = __addDisposableResource(env_3, await this.nodeFS.promises.open(this.path(path), 'w'), true);
437
+ await handle.write(buffer, offset);
438
+ }
439
+ catch (e_3) {
440
+ env_3.error = e_3;
441
+ env_3.hasError = true;
442
+ }
443
+ finally {
444
+ const result_3 = __disposeResources(env_3);
445
+ if (result_3)
446
+ await result_3;
447
+ }
448
+ }
449
+ catch (err) {
450
+ this.error(err, path);
451
+ }
452
+ }
453
+ writeSync(path, buffer, offset) {
454
+ let fd;
455
+ try {
456
+ fd = this.nodeFS.openSync(this.path(path), 'w');
457
+ this.nodeFS.writeSync(fd, buffer, offset);
458
+ }
459
+ catch (err) {
460
+ this.error(err, path);
461
+ }
462
+ finally {
463
+ if (fd)
464
+ this.nodeFS.closeSync(fd);
465
+ }
466
+ }
323
467
  }
324
468
  const _Passthrough = {
325
469
  name: 'Passthrough',
@@ -1,56 +1,20 @@
1
- import type { FileReadResult } from 'node:fs/promises';
2
1
  import type { ExtractProperties } from 'utilium';
3
2
  import type { MountConfiguration } from '../../config.js';
4
3
  import type { CreationOptions, FileSystemMetadata } from '../../filesystem.js';
5
4
  import type { Backend, FilesystemOf } from '../backend.js';
6
- import { File } from '../../file.js';
5
+ import type { Inode, InodeLike } from '../store/inode.js';
6
+ import type { File } from '../../file.js';
7
7
  import { FileSystem } from '../../filesystem.js';
8
8
  import { Stats } from '../../stats.js';
9
9
  import * as RPC from './rpc.js';
10
- type FileMethods = Omit<ExtractProperties<File, (...args: any[]) => Promise<any>>, typeof Symbol.asyncDispose>;
11
- type FileMethod = keyof FileMethods;
12
- /** @internal */
13
- export interface FileRequest<TMethod extends FileMethod = FileMethod> extends RPC.Request {
14
- fd: number;
15
- scope: 'file';
16
- method: TMethod;
17
- args: Parameters<FileMethods[TMethod]>;
18
- }
19
- export declare class PortFile extends File {
20
- fs: PortFS;
21
- readonly fd: number;
22
- position: number;
23
- constructor(fs: PortFS, fd: number, path: string, position: number);
24
- rpc<const T extends FileMethod>(method: T, ...args: Parameters<FileMethods[T]>): Promise<Awaited<ReturnType<FileMethods[T]>>>;
25
- protected _throwNoSync(syscall: string): never;
26
- stat(): Promise<Stats>;
27
- statSync(): Stats;
28
- truncate(len: number): Promise<void>;
29
- truncateSync(): void;
30
- write(buffer: Uint8Array, offset?: number, length?: number, position?: number): Promise<number>;
31
- writeSync(): number;
32
- read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<FileReadResult<TBuffer>>;
33
- readSync(): number;
34
- chown(uid: number, gid: number): Promise<void>;
35
- chownSync(): void;
36
- chmod(mode: number): Promise<void>;
37
- chmodSync(): void;
38
- utimes(atime: Date, mtime: Date): Promise<void>;
39
- utimesSync(): void;
40
- _setTypeSync(): void;
41
- close(): Promise<void>;
42
- closeSync(): void;
43
- sync(): Promise<void>;
44
- syncSync(): void;
45
- }
46
10
  type FSMethods = ExtractProperties<FileSystem, (...args: any[]) => Promise<any> | FileSystemMetadata>;
47
11
  type FSMethod = keyof FSMethods;
48
- /** @internal */
49
- export interface FSRequest<TMethod extends FSMethod = FSMethod> extends RPC.Request {
50
- scope: 'fs';
51
- method: TMethod;
52
- args: Parameters<FSMethods[TMethod]>;
53
- }
12
+ export type FSRequest<TMethod extends FSMethod = FSMethod> = RPC.Message & {
13
+ [M in TMethod]: {
14
+ method: M;
15
+ args: Parameters<FSMethods[M]>;
16
+ };
17
+ }[TMethod];
54
18
  declare const PortFS_base: import("../../index.js").Mixin<typeof FileSystem, import("../../mixins/async.js").AsyncMixin>;
55
19
  /**
56
20
  * PortFS lets you access an FS instance that is running in a port, or the other way around.
@@ -61,7 +25,7 @@ declare const PortFS_base: import("../../index.js").Mixin<typeof FileSystem, imp
61
25
  export declare class PortFS extends PortFS_base {
62
26
  readonly options: RPC.Options;
63
27
  readonly port: RPC.Port;
64
- /**
28
+ /**`
65
29
  * @hidden
66
30
  */
67
31
  _sync: import("../index.js").StoreFS<import("../memory.js").InMemoryStore>;
@@ -74,7 +38,7 @@ export declare class PortFS extends PortFS_base {
74
38
  ready(): Promise<void>;
75
39
  rename(oldPath: string, newPath: string): Promise<void>;
76
40
  stat(path: string): Promise<Stats>;
77
- sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
41
+ sync(path: string, data: Uint8Array | undefined, stats: Readonly<InodeLike | Inode>): Promise<void>;
78
42
  openFile(path: string, flag: string): Promise<File>;
79
43
  createFile(path: string, flag: string, mode: number, options: CreationOptions): Promise<File>;
80
44
  unlink(path: string): Promise<void>;
@@ -83,11 +47,13 @@ export declare class PortFS extends PortFS_base {
83
47
  readdir(path: string): Promise<string[]>;
84
48
  exists(path: string): Promise<boolean>;
85
49
  link(srcpath: string, dstpath: string): Promise<void>;
50
+ read(path: string, buffer: Uint8Array, offset: number, length: number): Promise<void>;
51
+ write(path: string, buffer: Uint8Array, offset: number): Promise<void>;
86
52
  }
87
53
  /** @internal */
88
- export type FileOrFSRequest = FSRequest | FileRequest;
89
- /** @internal */
90
- export declare function handleRequest(port: RPC.Port, fs: FileSystem, request: FileOrFSRequest): Promise<void>;
54
+ export declare function handleRequest(port: RPC.Port, fs: FileSystem & {
55
+ _descriptors?: Map<number, File>;
56
+ }, request: FSRequest): Promise<void>;
91
57
  export declare function attachFS(port: RPC.Port, fs: FileSystem): void;
92
58
  export declare function detachFS(port: RPC.Port, fs: FileSystem): void;
93
59
  declare const _Port: {
@@ -1,88 +1,11 @@
1
+ import { pick } from 'utilium';
1
2
  import { resolveMountConfig } from '../../config.js';
2
3
  import { Errno, ErrnoError } from '../../error.js';
3
- import { File } from '../../file.js';
4
4
  import { FileSystem } from '../../filesystem.js';
5
5
  import { Async } from '../../mixins/async.js';
6
6
  import { Stats } from '../../stats.js';
7
7
  import { InMemory } from '../memory.js';
8
8
  import * as RPC from './rpc.js';
9
- export class PortFile extends File {
10
- constructor(fs, fd, path, position) {
11
- super(fs, path);
12
- this.fs = fs;
13
- this.fd = fd;
14
- this.position = position;
15
- }
16
- rpc(method, ...args) {
17
- return RPC.request({
18
- scope: 'file',
19
- fd: this.fd,
20
- method,
21
- args,
22
- }, this.fs.options);
23
- }
24
- _throwNoSync(syscall) {
25
- throw new ErrnoError(Errno.ENOTSUP, 'Synchronous operations not supported on PortFile', this.path, syscall);
26
- }
27
- async stat() {
28
- return new Stats(await this.rpc('stat'));
29
- }
30
- statSync() {
31
- this._throwNoSync('stat');
32
- }
33
- truncate(len) {
34
- return this.rpc('truncate', len);
35
- }
36
- truncateSync() {
37
- this._throwNoSync('truncate');
38
- }
39
- write(buffer, offset, length, position) {
40
- return this.rpc('write', buffer, offset, length, position);
41
- }
42
- writeSync() {
43
- this._throwNoSync('write');
44
- }
45
- async read(buffer, offset, length, position) {
46
- const result = await this.rpc('read', buffer, offset, length, position);
47
- return result;
48
- }
49
- readSync() {
50
- this._throwNoSync('read');
51
- }
52
- chown(uid, gid) {
53
- return this.rpc('chown', uid, gid);
54
- }
55
- chownSync() {
56
- this._throwNoSync('chown');
57
- }
58
- chmod(mode) {
59
- return this.rpc('chmod', mode);
60
- }
61
- chmodSync() {
62
- this._throwNoSync('chmod');
63
- }
64
- utimes(atime, mtime) {
65
- return this.rpc('utimes', atime, mtime);
66
- }
67
- utimesSync() {
68
- this._throwNoSync('utimes');
69
- }
70
- _setTypeSync() {
71
- this._throwNoSync('_setType');
72
- }
73
- close() {
74
- return this.rpc('close');
75
- }
76
- closeSync() {
77
- this._throwNoSync('close');
78
- }
79
- sync() {
80
- return this.rpc('sync');
81
- }
82
- syncSync() {
83
- this._throwNoSync('sync');
84
- }
85
- }
86
9
  /**
87
10
  * PortFS lets you access an FS instance that is running in a port, or the other way around.
88
11
  *
@@ -96,7 +19,7 @@ export class PortFS extends Async(FileSystem) {
96
19
  constructor(options) {
97
20
  super();
98
21
  this.options = options;
99
- /**
22
+ /**`
100
23
  * @hidden
101
24
  */
102
25
  this._sync = InMemory.create({ name: 'port-tmpfs' });
@@ -110,11 +33,7 @@ export class PortFS extends Async(FileSystem) {
110
33
  };
111
34
  }
112
35
  rpc(method, ...args) {
113
- return RPC.request({
114
- scope: 'fs',
115
- method,
116
- args,
117
- }, { ...this.options, fs: this });
36
+ return RPC.request({ method, args }, { ...this.options, fs: this });
118
37
  }
119
38
  async ready() {
120
39
  await this.rpc('ready');
@@ -127,6 +46,7 @@ export class PortFS extends Async(FileSystem) {
127
46
  return new Stats(await this.rpc('stat', path));
128
47
  }
129
48
  sync(path, data, stats) {
49
+ stats = 'toJSON' in stats ? stats.toJSON() : stats;
130
50
  return this.rpc('sync', path, data, stats);
131
51
  }
132
52
  openFile(path, flag) {
@@ -153,51 +73,43 @@ export class PortFS extends Async(FileSystem) {
153
73
  link(srcpath, dstpath) {
154
74
  return this.rpc('link', srcpath, dstpath);
155
75
  }
76
+ async read(path, buffer, offset, length) {
77
+ const _buf = (await this.rpc('read', path, buffer, offset, length));
78
+ buffer.set(_buf);
79
+ }
80
+ write(path, buffer, offset) {
81
+ return this.rpc('write', path, buffer, offset);
82
+ }
156
83
  }
157
- let nextFd = 0;
158
- const descriptors = new Map();
159
84
  /** @internal */
160
85
  export async function handleRequest(port, fs, request) {
161
- if (!RPC.isMessage(request)) {
86
+ if (!RPC.isMessage(request))
162
87
  return;
163
- }
164
- const { method, args, id, scope, stack } = request;
88
+ const { method, args, id, stack } = request;
165
89
  let value, error = false;
166
90
  try {
167
- switch (scope) {
168
- case 'fs':
169
- // @ts-expect-error 2556
170
- value = await fs[method](...args);
171
- if (value instanceof File) {
172
- descriptors.set(++nextFd, value);
173
- value = {
174
- fd: nextFd,
175
- path: value.path,
176
- position: value.position,
177
- };
178
- }
179
- break;
180
- case 'file': {
181
- const { fd } = request;
182
- if (!descriptors.has(fd)) {
183
- throw new ErrnoError(Errno.EBADF);
184
- }
185
- // @ts-expect-error 2556
186
- value = await descriptors.get(fd)[method](...args);
187
- if (method == 'close') {
188
- descriptors.delete(fd);
189
- }
91
+ // @ts-expect-error 2556
92
+ value = await fs[method](...args);
93
+ switch (method) {
94
+ case 'openFile':
95
+ case 'createFile': {
96
+ value = {
97
+ path: args[0],
98
+ flag: args[1],
99
+ stats: await fs.stat(args[0]),
100
+ };
190
101
  break;
191
102
  }
192
- default:
193
- return;
103
+ case 'read':
104
+ value = args[1];
105
+ break;
194
106
  }
195
107
  }
196
108
  catch (e) {
197
- value = e instanceof ErrnoError ? e.toJSON() : e.toString();
109
+ value = e instanceof ErrnoError ? e.toJSON() : pick(e, 'message', 'stack');
198
110
  error = true;
199
111
  }
200
- port.postMessage({ _zenfs: true, scope, id, error, method, stack, value });
112
+ port.postMessage({ _zenfs: true, id, error, method, stack, value });
201
113
  }
202
114
  export function attachFS(port, fs) {
203
115
  RPC.attach(port, request => handleRequest(port, fs, request));
@@ -1,12 +1,15 @@
1
+ import type { TransferListItem } from 'node:worker_threads';
2
+ import type { WithOptional } from 'utilium';
1
3
  import type { ErrnoErrorJSON } from '../../error.js';
2
4
  import type { Backend, FilesystemOf } from '../backend.js';
3
5
  import type { PortFS } from './fs.js';
6
+ import { type StatsLike } from '../../stats.js';
4
7
  type _MessageEvent<T = any> = T | {
5
8
  data: T;
6
9
  };
7
10
  /** @internal */
8
11
  export interface Port {
9
- postMessage(value: unknown): void;
12
+ postMessage(value: unknown, transfer?: TransferListItem[]): void;
10
13
  on?(event: 'message', listener: (value: unknown) => void): this;
11
14
  off?(event: 'message', listener: (value: unknown) => void): this;
12
15
  addEventListener?(type: 'message', listener: (ev: _MessageEvent) => void): void;
@@ -27,7 +30,6 @@ export interface Options {
27
30
  */
28
31
  export interface Message {
29
32
  _zenfs: true;
30
- scope: 'fs' | 'file';
31
33
  id: string;
32
34
  method: string;
33
35
  stack: string;
@@ -37,17 +39,22 @@ export interface Request extends Message {
37
39
  }
38
40
  interface _ResponseWithError extends Message {
39
41
  error: true;
40
- value: ErrnoErrorJSON | string;
42
+ value: WithOptional<ErrnoErrorJSON, 'code' | 'errno'>;
41
43
  }
42
44
  interface _ResponseWithValue<T> extends Message {
43
45
  error: false;
44
46
  value: Awaited<T> extends File ? FileData : Awaited<T>;
45
47
  }
46
- export type Response<T = unknown> = _ResponseWithError | _ResponseWithValue<T>;
48
+ interface _ResponseRead extends Message {
49
+ error: false;
50
+ method: 'read';
51
+ value: Uint8Array;
52
+ }
53
+ export type Response<T = unknown> = _ResponseWithError | _ResponseWithValue<T> | _ResponseRead;
47
54
  export interface FileData {
48
- fd: number;
49
55
  path: string;
50
- position: number;
56
+ flag: string;
57
+ stats: StatsLike<number>;
51
58
  }
52
59
  export { FileData as File };
53
60
  export declare function isMessage(arg: unknown): arg is Message;
@@ -1,7 +1,9 @@
1
1
  import { Errno, ErrnoError } from '../../error.js';
2
- import { handleRequest, PortFile } from './fs.js';
2
+ import { LazyFile } from '../../file.js';
3
+ import { Stats } from '../../stats.js';
4
+ import { handleRequest } from './fs.js';
3
5
  function isFileData(value) {
4
- return typeof value == 'object' && value != null && 'fd' in value && 'path' in value && 'position' in value;
6
+ return typeof value == 'object' && value != null && 'path' in value && 'flag' in value;
5
7
  }
6
8
  // general types
7
9
  export function isMessage(arg) {
@@ -18,7 +20,7 @@ export function request(request, { port, timeout = 1000, fs } = {}) {
18
20
  executors.set(id, { resolve, reject, fs });
19
21
  port.postMessage({ ...request, _zenfs: true, id, stack });
20
22
  const _ = setTimeout(() => {
21
- const error = new ErrnoError(Errno.EIO, 'RPC Failed');
23
+ const error = new ErrnoError(Errno.EIO, 'RPC Failed', typeof request.args[0] == 'string' ? request.args[0] : '', request.method);
22
24
  error.stack += stack;
23
25
  reject(error);
24
26
  if (typeof _ == 'object')
@@ -38,15 +40,15 @@ export function handleResponse(response) {
38
40
  }
39
41
  const { resolve, reject, fs } = executors.get(id);
40
42
  if (error) {
41
- const e = typeof value == 'string' ? new Error(value) : ErrnoError.fromJSON(value);
43
+ const e = ErrnoError.fromJSON({ code: 'EIO', errno: Errno.EIO, ...value });
42
44
  e.stack += stack;
43
45
  reject(e);
44
46
  executors.delete(id);
45
47
  return;
46
48
  }
47
49
  if (isFileData(value)) {
48
- const { fd, path, position } = value;
49
- const file = new PortFile(fs, fd, path, position);
50
+ const { path, flag, stats } = value;
51
+ const file = new LazyFile(fs, path, flag, new Stats(stats));
50
52
  resolve(file);
51
53
  executors.delete(id);
52
54
  return;
@@ -78,7 +80,7 @@ export function catchMessages(port) {
78
80
  return function (fs) {
79
81
  detach(port, handler);
80
82
  for (const event of events) {
81
- const request = ('data' in event ? event.data : event);
83
+ const request = 'data' in event ? event.data : event;
82
84
  void handleRequest(port, fs, request);
83
85
  }
84
86
  };
@@ -0,0 +1,38 @@
1
+ import type { InodeLike } from './inode.js';
2
+ import { Inode } from './inode.js';
3
+ /**
4
+ * An Index in JSON form
5
+ * @internal
6
+ */
7
+ export interface IndexData {
8
+ version: number;
9
+ entries: Record<string, InodeLike>;
10
+ }
11
+ export declare const version = 1;
12
+ /**
13
+ * An index of files
14
+ * @internal
15
+ */
16
+ export declare class Index extends Map<string, Readonly<Inode>> {
17
+ protected _directories?: Map<string, Record<string, number>>;
18
+ /**
19
+ * Converts the index to JSON
20
+ */
21
+ toJSON(): IndexData;
22
+ /**
23
+ * Converts the index to a string
24
+ */
25
+ toString(): string;
26
+ /**
27
+ * Gets a list of entries for each directory in the index. Memoized.
28
+ */
29
+ directories(): Map<string, Record<string, number>>;
30
+ /**
31
+ * Loads the index from JSON data
32
+ */
33
+ fromJSON(json: IndexData): void;
34
+ /**
35
+ * Parses an index from a string
36
+ */
37
+ static parse(data: string): Index;
38
+ }
@@ -0,0 +1,76 @@
1
+ /* Note: this file is named file_index.ts because Typescript has special behavior regarding index.ts which can't be disabled. */
2
+ import { isJSON, randomInt } from 'utilium';
3
+ import { Errno, ErrnoError } from '../../error.js';
4
+ import { S_IFDIR, S_IFMT, size_max } from '../../vfs/constants.js';
5
+ import { basename, dirname } from '../../vfs/path.js';
6
+ import { Inode } from './inode.js';
7
+ export const version = 1;
8
+ /**
9
+ * An index of files
10
+ * @internal
11
+ */
12
+ export class Index extends Map {
13
+ /**
14
+ * Converts the index to JSON
15
+ */
16
+ toJSON() {
17
+ return {
18
+ version,
19
+ entries: Object.fromEntries([...this].map(([k, v]) => [k, v.toJSON()])),
20
+ };
21
+ }
22
+ /**
23
+ * Converts the index to a string
24
+ */
25
+ toString() {
26
+ return JSON.stringify(this.toJSON());
27
+ }
28
+ /**
29
+ * Gets a list of entries for each directory in the index. Memoized.
30
+ */
31
+ directories() {
32
+ if (this._directories)
33
+ return this._directories;
34
+ const dirs = new Map();
35
+ for (const [path, node] of this) {
36
+ if ((node.mode & S_IFMT) != S_IFDIR)
37
+ continue;
38
+ const entries = {};
39
+ for (const entry of this.keys()) {
40
+ if (dirname(entry) == path && entry != path)
41
+ entries[basename(entry)] = this.get(entry).ino;
42
+ }
43
+ dirs.set(path, entries);
44
+ }
45
+ this._directories = dirs;
46
+ return dirs;
47
+ }
48
+ /**
49
+ * Loads the index from JSON data
50
+ */
51
+ fromJSON(json) {
52
+ var _a;
53
+ if (json.version != version) {
54
+ throw new ErrnoError(Errno.EINVAL, 'Index version mismatch');
55
+ }
56
+ this.clear();
57
+ for (const [path, node] of Object.entries(json.entries)) {
58
+ (_a = node.data) !== null && _a !== void 0 ? _a : (node.data = randomInt(1, size_max));
59
+ if (path == '/')
60
+ node.ino = 0;
61
+ this.set(path, new Inode(node));
62
+ }
63
+ }
64
+ /**
65
+ * Parses an index from a string
66
+ */
67
+ static parse(data) {
68
+ if (!isJSON(data)) {
69
+ throw new ErrnoError(Errno.EINVAL, 'Invalid JSON');
70
+ }
71
+ const json = JSON.parse(data);
72
+ const index = new Index();
73
+ index.fromJSON(json);
74
+ return index;
75
+ }
76
+ }