@zenfs/core 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) 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 +7 -2
  8. package/dist/backends/overlay.js +32 -9
  9. package/dist/backends/passthrough.d.ts +4 -0
  10. package/dist/backends/passthrough.js +128 -0
  11. package/dist/backends/port/fs.d.ts +9 -44
  12. package/dist/backends/port/fs.js +93 -116
  13. package/dist/backends/port/rpc.d.ts +8 -5
  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 +55 -34
  18. package/dist/backends/store/fs.js +417 -233
  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 +11 -10
  28. package/dist/devices.js +15 -11
  29. package/dist/file.d.ts +111 -7
  30. package/dist/file.js +319 -71
  31. package/dist/filesystem.d.ts +22 -4
  32. package/dist/mixins/mutexed.d.ts +7 -2
  33. package/dist/mixins/mutexed.js +56 -0
  34. package/dist/mixins/sync.d.ts +1 -1
  35. package/dist/stats.d.ts +12 -6
  36. package/dist/stats.js +14 -6
  37. package/dist/utils.d.ts +17 -3
  38. package/dist/utils.js +32 -10
  39. package/dist/vfs/constants.d.ts +2 -2
  40. package/dist/vfs/constants.js +2 -2
  41. package/dist/vfs/dir.js +3 -1
  42. package/dist/vfs/index.js +4 -1
  43. package/dist/vfs/promises.js +31 -11
  44. package/dist/vfs/shared.js +2 -0
  45. package/dist/vfs/sync.js +25 -13
  46. package/dist/vfs/types.d.ts +15 -0
  47. package/package.json +2 -3
  48. package/readme.md +2 -2
  49. package/scripts/test.js +73 -11
  50. package/tests/common/mutex.test.ts +1 -1
  51. package/tests/fetch/run.sh +16 -0
  52. package/tests/fetch/server.ts +49 -0
  53. package/tests/fetch/setup.ts +13 -0
  54. package/tests/fs/read.test.ts +10 -10
  55. package/tests/fs/times.test.ts +2 -2
  56. package/tests/setup/index.ts +38 -0
  57. package/tests/setup/port.ts +15 -0
  58. package/dist/backends/file_index.d.ts +0 -63
  59. package/dist/backends/file_index.js +0 -163
  60. package/tests/common/async.test.ts +0 -31
  61. package/tests/setup/cow+fetch.ts +0 -45
  62. /package/tests/fs/{appendFile.test.ts → append.test.ts} +0 -0
@@ -1,53 +1,16 @@
1
- import type { FileReadResult } from 'node:fs/promises';
2
- import type { ExtractProperties } from 'utilium';
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
5
  import { File } from '../../file.js';
7
6
  import { FileSystem } from '../../filesystem.js';
8
7
  import { Stats } from '../../stats.js';
8
+ import type { Inode, InodeLike } from '../store/inode.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
12
  /** @internal */
49
13
  export interface FSRequest<TMethod extends FSMethod = FSMethod> extends RPC.Request {
50
- scope: 'fs';
51
14
  method: TMethod;
52
15
  args: Parameters<FSMethods[TMethod]>;
53
16
  }
@@ -61,7 +24,7 @@ declare const PortFS_base: import("../../index.js").Mixin<typeof FileSystem, imp
61
24
  export declare class PortFS extends PortFS_base {
62
25
  readonly options: RPC.Options;
63
26
  readonly port: RPC.Port;
64
- /**
27
+ /**`
65
28
  * @hidden
66
29
  */
67
30
  _sync: import("../index.js").StoreFS<import("../memory.js").InMemoryStore>;
@@ -74,7 +37,7 @@ export declare class PortFS extends PortFS_base {
74
37
  ready(): Promise<void>;
75
38
  rename(oldPath: string, newPath: string): Promise<void>;
76
39
  stat(path: string): Promise<Stats>;
77
- sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
40
+ sync(path: string, data: Uint8Array | undefined, stats: Readonly<InodeLike | Inode>): Promise<void>;
78
41
  openFile(path: string, flag: string): Promise<File>;
79
42
  createFile(path: string, flag: string, mode: number, options: CreationOptions): Promise<File>;
80
43
  unlink(path: string): Promise<void>;
@@ -83,11 +46,13 @@ export declare class PortFS extends PortFS_base {
83
46
  readdir(path: string): Promise<string[]>;
84
47
  exists(path: string): Promise<boolean>;
85
48
  link(srcpath: string, dstpath: string): Promise<void>;
49
+ read(path: string, offset: number, length: number): Promise<Uint8Array>;
50
+ write(path: string, buffer: Uint8Array, offset: number): Promise<void>;
86
51
  }
87
52
  /** @internal */
88
- export type FileOrFSRequest = FSRequest | FileRequest;
89
- /** @internal */
90
- export declare function handleRequest(port: RPC.Port, fs: FileSystem, request: FileOrFSRequest): Promise<void>;
53
+ export declare function handleRequest(port: RPC.Port, fs: FileSystem & {
54
+ _descriptors?: Map<number, File>;
55
+ }, request: FSRequest): Promise<void>;
91
56
  export declare function attachFS(port: RPC.Port, fs: FileSystem): void;
92
57
  export declare function detachFS(port: RPC.Port, fs: FileSystem): void;
93
58
  declare const _Port: {
@@ -1,3 +1,57 @@
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
+ });
53
+ /* eslint-disable @typescript-eslint/no-explicit-any */
54
+ import { pick } from 'utilium';
1
55
  import { resolveMountConfig } from '../../config.js';
2
56
  import { Errno, ErrnoError } from '../../error.js';
3
57
  import { File } from '../../file.js';
@@ -6,83 +60,6 @@ import { Async } from '../../mixins/async.js';
6
60
  import { Stats } from '../../stats.js';
7
61
  import { InMemory } from '../memory.js';
8
62
  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
63
  /**
87
64
  * PortFS lets you access an FS instance that is running in a port, or the other way around.
88
65
  *
@@ -96,7 +73,7 @@ export class PortFS extends Async(FileSystem) {
96
73
  constructor(options) {
97
74
  super();
98
75
  this.options = options;
99
- /**
76
+ /**`
100
77
  * @hidden
101
78
  */
102
79
  this._sync = InMemory.create({ name: 'port-tmpfs' });
@@ -110,11 +87,7 @@ export class PortFS extends Async(FileSystem) {
110
87
  };
111
88
  }
112
89
  rpc(method, ...args) {
113
- return RPC.request({
114
- scope: 'fs',
115
- method,
116
- args,
117
- }, { ...this.options, fs: this });
90
+ return RPC.request({ method, args }, { ...this.options, fs: this });
118
91
  }
119
92
  async ready() {
120
93
  await this.rpc('ready');
@@ -127,6 +100,7 @@ export class PortFS extends Async(FileSystem) {
127
100
  return new Stats(await this.rpc('stat', path));
128
101
  }
129
102
  sync(path, data, stats) {
103
+ stats = 'toJSON' in stats ? stats.toJSON() : stats;
130
104
  return this.rpc('sync', path, data, stats);
131
105
  }
132
106
  openFile(path, flag) {
@@ -153,51 +127,54 @@ export class PortFS extends Async(FileSystem) {
153
127
  link(srcpath, dstpath) {
154
128
  return this.rpc('link', srcpath, dstpath);
155
129
  }
130
+ read(path, offset, length) {
131
+ return this.rpc('read', path, offset, length);
132
+ }
133
+ write(path, buffer, offset) {
134
+ return this.rpc('write', path, buffer, offset);
135
+ }
156
136
  }
157
- let nextFd = 0;
158
- const descriptors = new Map();
159
137
  /** @internal */
160
138
  export async function handleRequest(port, fs, request) {
161
- if (!RPC.isMessage(request)) {
139
+ if (!RPC.isMessage(request))
162
140
  return;
163
- }
164
- const { method, args, id, scope, stack } = request;
141
+ const { method, args, id, stack } = request;
165
142
  let value, error = false;
143
+ const transfer = [];
166
144
  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
- }
190
- break;
145
+ // @ts-expect-error 2556
146
+ value = await fs[method](...args);
147
+ if (value instanceof File) {
148
+ const env_1 = { stack: [], error: void 0, hasError: false };
149
+ try {
150
+ const file = __addDisposableResource(env_1, await fs.openFile(args[0], 'r+'), true);
151
+ const stats = await file.stat();
152
+ const data = new Uint8Array(stats.size);
153
+ await file.read(data);
154
+ value = {
155
+ path: value.path,
156
+ flag: args[1],
157
+ stats,
158
+ buffer: data.buffer,
159
+ };
160
+ transfer.push(data.buffer);
161
+ }
162
+ catch (e_1) {
163
+ env_1.error = e_1;
164
+ env_1.hasError = true;
165
+ }
166
+ finally {
167
+ const result_1 = __disposeResources(env_1);
168
+ if (result_1)
169
+ await result_1;
191
170
  }
192
- default:
193
- return;
194
171
  }
195
172
  }
196
173
  catch (e) {
197
- value = e instanceof ErrnoError ? e.toJSON() : e.toString();
174
+ value = e instanceof ErrnoError ? e.toJSON() : pick(e, 'message', 'stack');
198
175
  error = true;
199
176
  }
200
- port.postMessage({ _zenfs: true, scope, id, error, method, stack, value });
177
+ port.postMessage({ _zenfs: true, id, error, method, stack, value }, transfer);
201
178
  }
202
179
  export function attachFS(port, fs) {
203
180
  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,7 +39,7 @@ 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;
@@ -45,9 +47,10 @@ interface _ResponseWithValue<T> extends Message {
45
47
  }
46
48
  export type Response<T = unknown> = _ResponseWithError | _ResponseWithValue<T>;
47
49
  export interface FileData {
48
- fd: number;
49
50
  path: string;
50
- position: number;
51
+ flag: string;
52
+ stats: StatsLike<number>;
53
+ buffer: ArrayBuffer;
51
54
  }
52
55
  export { FileData as File };
53
56
  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 { PreloadFile } 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, buffer } = value;
51
+ const file = new PreloadFile(fs, path, flag, new Stats(stats), new Uint8Array(buffer));
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
+ }