@zenfs/core 0.11.1 → 0.12.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 (48) hide show
  1. package/dist/backends/fetch.d.ts +20 -33
  2. package/dist/backends/fetch.js +48 -93
  3. package/dist/backends/index/fs.d.ts +49 -0
  4. package/dist/backends/index/fs.js +86 -0
  5. package/dist/backends/index/index.d.ts +37 -0
  6. package/dist/backends/index/index.js +82 -0
  7. package/dist/backends/locked.d.ts +1 -1
  8. package/dist/backends/locked.js +34 -34
  9. package/dist/backends/memory.d.ts +1 -1
  10. package/dist/backends/port/fs.d.ts +1 -1
  11. package/dist/backends/port/fs.js +2 -1
  12. package/dist/backends/port/rpc.js +3 -1
  13. package/dist/backends/store/fs.d.ts +5 -5
  14. package/dist/backends/store/fs.js +1 -1
  15. package/dist/browser.min.js +4 -4
  16. package/dist/browser.min.js.map +4 -4
  17. package/dist/emulation/promises.js +1 -2
  18. package/dist/emulation/sync.js +24 -40
  19. package/dist/file.d.ts +1 -1
  20. package/dist/file.js +2 -2
  21. package/dist/filesystem.d.ts +6 -6
  22. package/dist/filesystem.js +11 -10
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/mutex.d.ts +1 -1
  26. package/dist/mutex.js +11 -11
  27. package/dist/stats.d.ts +10 -10
  28. package/package.json +1 -1
  29. package/scripts/make-index.js +39 -24
  30. package/src/backends/fetch.ts +52 -110
  31. package/src/backends/index/fs.ts +113 -0
  32. package/src/backends/index/index.ts +98 -0
  33. package/src/backends/index/readme.md +3 -0
  34. package/src/backends/locked.ts +34 -34
  35. package/src/backends/memory.ts +1 -1
  36. package/src/backends/port/fs.ts +2 -1
  37. package/src/backends/port/rpc.ts +2 -1
  38. package/src/backends/store/fs.ts +5 -5
  39. package/src/emulation/promises.ts +1 -2
  40. package/src/emulation/sync.ts +23 -41
  41. package/src/file.ts +2 -2
  42. package/src/filesystem.ts +18 -17
  43. package/src/index.ts +1 -1
  44. package/src/mutex.ts +11 -11
  45. package/src/stats.ts +10 -10
  46. package/dist/backends/Index.d.ts +0 -204
  47. package/dist/backends/Index.js +0 -410
  48. package/src/backends/Index.ts +0 -504
@@ -1,7 +1,7 @@
1
- import { NoSyncFile } from '../file.js';
2
1
  import type { FileSystemMetadata } from '../filesystem.js';
3
2
  import { Stats } from '../stats.js';
4
- import { type ListingTree, type IndexFileInode, AsyncIndexFS } from './Index.js';
3
+ import { IndexFS } from './index/fs.js';
4
+ import type { IndexData } from './index/index.js';
5
5
  /**
6
6
  * Configuration options for FetchFS.
7
7
  */
@@ -10,60 +10,47 @@ export interface FetchOptions {
10
10
  * URL to a file index as a JSON file or the file index object itself.
11
11
  * Defaults to `index.json`.
12
12
  */
13
- index?: string | ListingTree;
13
+ index?: string | IndexData;
14
14
  /** Used as the URL prefix for fetched files.
15
15
  * Default: Fetch files relative to the index.
16
16
  */
17
17
  baseUrl?: string;
18
18
  }
19
19
  /**
20
- * A simple filesystem backed by HTTP using the fetch API.
20
+ * A simple filesystem backed by HTTP using the `fetch` API.
21
21
  *
22
22
  *
23
- * Listings objects look like the following:
23
+ * Index objects look like the following:
24
24
  *
25
25
  * ```json
26
26
  * {
27
- * "home": {
28
- * "jvilk": {
29
- * "someFile.txt": null,
30
- * "someDir": {
31
- * // Empty directory
32
- * }
33
- * }
34
- * }
27
+ * "version": 1,
28
+ * "entries": {
29
+ * "/home": { ... },
30
+ * "/home/jvilk": { ... },
31
+ * "/home/james": { ... }
32
+ * }
35
33
  * }
36
34
  * ```
37
35
  *
38
- * This example has the folder `/home/jvilk` with subfile `someFile.txt` and subfolder `someDir`.
36
+ * Each entry contains the stats associated with the file.
39
37
  */
40
- export declare class FetchFS extends AsyncIndexFS<Stats> {
41
- readonly prefixUrl: string;
42
- protected _init: Promise<void>;
43
- protected _initialize(index: string | ListingTree): Promise<void>;
38
+ export declare class FetchFS extends IndexFS {
39
+ readonly baseUrl: string;
44
40
  ready(): Promise<void>;
45
41
  constructor({ index, baseUrl }: FetchOptions);
46
42
  metadata(): FileSystemMetadata;
47
- empty(): void;
48
43
  /**
49
- * Special function: Preload the given file into the index.
44
+ * Preload the given file into the index.
50
45
  * @param path
51
46
  * @param buffer
52
47
  */
53
- preloadFile(path: string, buffer: Uint8Array): void;
54
- protected statFileInode(inode: IndexFileInode<Stats>, path: string): Promise<Stats>;
55
- protected openFileInode(inode: IndexFileInode<Stats>, path: string, flag: string): Promise<NoSyncFile<this>>;
56
- private _getRemotePath;
48
+ preload(path: string, buffer: Uint8Array): void;
57
49
  /**
58
- * Asynchronously download the given file.
50
+ * @todo Be lazier about actually requesting the data?
59
51
  */
60
- protected _fetchFile(path: string, type: 'buffer'): Promise<Uint8Array>;
61
- protected _fetchFile(path: string, type: 'json'): Promise<object>;
62
- protected _fetchFile(path: string, type: 'buffer' | 'json'): Promise<object>;
63
- /**
64
- * Only requests the HEAD content, for the file size.
65
- */
66
- protected _fetchSize(path: string): Promise<number>;
52
+ protected getData(path: string, stats: Stats): Promise<Uint8Array>;
53
+ protected getDataSync(path: string, stats: Stats): Uint8Array;
67
54
  }
68
55
  export declare const Fetch: {
69
56
  readonly name: "Fetch";
@@ -71,7 +58,7 @@ export declare const Fetch: {
71
58
  readonly index: {
72
59
  readonly type: readonly ["string", "object"];
73
60
  readonly required: false;
74
- readonly description: "URL to a file index as a JSON file or the file index object itself, generated with the make_http_index script. Defaults to `index.json`.";
61
+ 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`.";
75
62
  };
76
63
  readonly baseUrl: {
77
64
  readonly type: "string";
@@ -1,85 +1,65 @@
1
- import { ErrnoError, Errno } from '../error.js';
2
- import { NoSyncFile } from '../file.js';
3
- import { Stats } from '../stats.js';
4
- import { FileIndex, AsyncIndexFS } from './Index.js';
5
- /**
6
- * @hidden
7
- */
8
- function convertError(e) {
9
- throw new ErrnoError(Errno.EIO, e.message);
10
- }
1
+ import { Errno, ErrnoError } from '../error.js';
2
+ import { IndexFS } from './index/fs.js';
11
3
  async function fetchFile(path, type) {
12
- const response = await fetch(path).catch(convertError);
4
+ const response = await fetch(path).catch(e => {
5
+ throw new ErrnoError(Errno.EIO, e.message);
6
+ });
13
7
  if (!response.ok) {
14
8
  throw new ErrnoError(Errno.EIO, 'fetch failed: response returned code ' + response.status);
15
9
  }
16
10
  switch (type) {
17
11
  case 'buffer':
18
- const arrayBuffer = await response.arrayBuffer().catch(convertError);
12
+ const arrayBuffer = await response.arrayBuffer().catch(e => {
13
+ throw new ErrnoError(Errno.EIO, e.message);
14
+ });
19
15
  return new Uint8Array(arrayBuffer);
20
16
  case 'json':
21
- return response.json().catch(convertError);
17
+ return response.json().catch(e => {
18
+ throw new ErrnoError(Errno.EIO, e.message);
19
+ });
22
20
  default:
23
21
  throw new ErrnoError(Errno.EINVAL, 'Invalid download type: ' + type);
24
22
  }
25
23
  }
26
24
  /**
27
- * Asynchronously retrieves the size of the given file in bytes.
28
- * @hidden
29
- */
30
- async function fetchSize(path) {
31
- const response = await fetch(path, { method: 'HEAD' }).catch(convertError);
32
- if (!response.ok) {
33
- throw new ErrnoError(Errno.EIO, 'fetch failed: HEAD response returned code ' + response.status);
34
- }
35
- return parseInt(response.headers.get('Content-Length') || '-1', 10);
36
- }
37
- /**
38
- * A simple filesystem backed by HTTP using the fetch API.
25
+ * A simple filesystem backed by HTTP using the `fetch` API.
39
26
  *
40
27
  *
41
- * Listings objects look like the following:
28
+ * Index objects look like the following:
42
29
  *
43
30
  * ```json
44
31
  * {
45
- * "home": {
46
- * "jvilk": {
47
- * "someFile.txt": null,
48
- * "someDir": {
49
- * // Empty directory
50
- * }
51
- * }
52
- * }
32
+ * "version": 1,
33
+ * "entries": {
34
+ * "/home": { ... },
35
+ * "/home/jvilk": { ... },
36
+ * "/home/james": { ... }
37
+ * }
53
38
  * }
54
39
  * ```
55
40
  *
56
- * This example has the folder `/home/jvilk` with subfile `someFile.txt` and subfolder `someDir`.
41
+ * Each entry contains the stats associated with the file.
57
42
  */
58
- export class FetchFS extends AsyncIndexFS {
59
- async _initialize(index) {
60
- if (typeof index != 'string') {
61
- this._index = FileIndex.FromListing(index);
43
+ export class FetchFS extends IndexFS {
44
+ async ready() {
45
+ if (this._isInitialized) {
62
46
  return;
63
47
  }
64
- try {
65
- const response = await fetch(index);
66
- this._index = FileIndex.FromListing((await response.json()));
48
+ await super.ready();
49
+ /**
50
+ * Iterate over all of the files and cache their contents
51
+ */
52
+ for (const [path, stats] of this.index.files()) {
53
+ await this.getData(path, stats);
67
54
  }
68
- catch (e) {
69
- throw new ErrnoError(Errno.EINVAL, 'Invalid or unavailable file listing tree');
70
- }
71
- }
72
- async ready() {
73
- await this._init;
74
55
  }
75
56
  constructor({ index = 'index.json', baseUrl = '' }) {
76
- super({});
57
+ super(typeof index != 'string' ? index : fetchFile(index, 'json'));
77
58
  // prefix url must end in a directory separator.
78
59
  if (baseUrl.at(-1) != '/') {
79
60
  baseUrl += '/';
80
61
  }
81
- this.prefixUrl = baseUrl;
82
- this._init = this._initialize(index);
62
+ this.baseUrl = baseUrl;
83
63
  }
84
64
  metadata() {
85
65
  return {
@@ -88,63 +68,38 @@ export class FetchFS extends AsyncIndexFS {
88
68
  readonly: true,
89
69
  };
90
70
  }
91
- empty() {
92
- for (const file of this._index.files()) {
93
- delete file.data.fileData;
94
- }
95
- }
96
71
  /**
97
- * Special function: Preload the given file into the index.
72
+ * Preload the given file into the index.
98
73
  * @param path
99
74
  * @param buffer
100
75
  */
101
- preloadFile(path, buffer) {
102
- const inode = this._index.get(path);
103
- if (!inode) {
76
+ preload(path, buffer) {
77
+ const stats = this.index.get(path);
78
+ if (!stats) {
104
79
  throw ErrnoError.With('ENOENT', path, 'preloadFile');
105
80
  }
106
- if (!inode.isFile()) {
81
+ if (!stats.isFile()) {
107
82
  throw ErrnoError.With('EISDIR', path, 'preloadFile');
108
83
  }
109
- const stats = inode.data;
110
84
  stats.size = buffer.length;
111
85
  stats.fileData = buffer;
112
86
  }
113
- async statFileInode(inode, path) {
114
- const stats = inode.data;
115
- // At this point, a non-opened file will still have default stats from the listing.
116
- if (stats.size < 0) {
117
- stats.size = await this._fetchSize(path);
118
- }
119
- return stats;
120
- }
121
- async openFileInode(inode, path, flag) {
122
- const stats = inode.data;
123
- // Use existing file contents. This maintains the previously-used flag.
87
+ /**
88
+ * @todo Be lazier about actually requesting the data?
89
+ */
90
+ async getData(path, stats) {
124
91
  if (stats.fileData) {
125
- return new NoSyncFile(this, path, flag, new Stats(stats), stats.fileData);
92
+ return stats.fileData;
126
93
  }
127
- // @todo be lazier about actually requesting the file
128
- const data = await this._fetchFile(path, 'buffer');
129
- // we don't initially have file sizes
130
- stats.size = data.length;
94
+ const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer');
131
95
  stats.fileData = data;
132
- return new NoSyncFile(this, path, flag, new Stats(stats), data);
96
+ return data;
133
97
  }
134
- _getRemotePath(filePath) {
135
- if (filePath.charAt(0) === '/') {
136
- filePath = filePath.slice(1);
98
+ getDataSync(path, stats) {
99
+ if (stats.fileData) {
100
+ return stats.fileData;
137
101
  }
138
- return this.prefixUrl + filePath;
139
- }
140
- _fetchFile(path, type) {
141
- return fetchFile(this._getRemotePath(path), type);
142
- }
143
- /**
144
- * Only requests the HEAD content, for the file size.
145
- */
146
- _fetchSize(path) {
147
- return fetchSize(this._getRemotePath(path));
102
+ throw new ErrnoError(Errno.ENODATA, '', path, 'getData');
148
103
  }
149
104
  }
150
105
  export const Fetch = {
@@ -153,7 +108,7 @@ export const Fetch = {
153
108
  index: {
154
109
  type: ['string', 'object'],
155
110
  required: false,
156
- description: 'URL to a file index as a JSON file or the file index object itself, generated with the make_http_index script. Defaults to `index.json`.',
111
+ 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`.',
157
112
  },
158
113
  baseUrl: {
159
114
  type: 'string',
@@ -0,0 +1,49 @@
1
+ import type { Cred } from '../../cred.js';
2
+ import { NoSyncFile } from '../../file.js';
3
+ import { FileSystem } from '../../filesystem.js';
4
+ import type { Stats } from '../../stats.js';
5
+ import { Index, IndexData } from './index.js';
6
+ declare const IndexFS_base: (abstract new (...args: any[]) => {
7
+ metadata(): import("../../filesystem.js").FileSystemMetadata;
8
+ rename(oldPath: string, newPath: string, cred: Cred): Promise<void>;
9
+ renameSync(oldPath: string, newPath: string, cred: Cred): void;
10
+ createFile(path: string, flag: string, mode: number, cred: Cred): Promise<import("../../file.js").File>;
11
+ createFileSync(path: string, flag: string, mode: number, cred: Cred): import("../../file.js").File;
12
+ unlink(path: string, cred: Cred): Promise<void>;
13
+ unlinkSync(path: string, cred: Cred): void;
14
+ rmdir(path: string, cred: Cred): Promise<void>;
15
+ rmdirSync(path: string, cred: Cred): void;
16
+ mkdir(path: string, mode: number, cred: Cred): Promise<void>;
17
+ mkdirSync(path: string, mode: number, cred: Cred): void;
18
+ link(srcpath: string, dstpath: string, cred: Cred): Promise<void>;
19
+ linkSync(srcpath: string, dstpath: string, cred: Cred): void;
20
+ sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
21
+ syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
22
+ ready(): Promise<void>;
23
+ stat(path: string, cred: Cred): Promise<Stats>;
24
+ statSync(path: string, cred: Cred): Stats;
25
+ openFile(path: string, flag: string, cred: Cred): Promise<import("../../file.js").File>;
26
+ openFileSync(path: string, flag: string, cred: Cred): import("../../file.js").File;
27
+ readdir(path: string, cred: Cred): Promise<string[]>;
28
+ readdirSync(path: string, cred: Cred): string[];
29
+ exists(path: string, cred: Cred): Promise<boolean>;
30
+ existsSync(path: string, cred: Cred): boolean;
31
+ }) & typeof FileSystem;
32
+ export declare abstract class IndexFS extends IndexFS_base {
33
+ private indexData;
34
+ protected index: Index;
35
+ protected _isInitialized: boolean;
36
+ ready(): Promise<void>;
37
+ constructor(indexData: IndexData | Promise<IndexData>);
38
+ reloadFiles(): Promise<void>;
39
+ reloadFilesSync(): void;
40
+ stat(path: string): Promise<Stats>;
41
+ statSync(path: string): Stats;
42
+ openFile(path: string, flag: string, cred: Cred): Promise<NoSyncFile<this>>;
43
+ openFileSync(path: string, flag: string, cred: Cred): NoSyncFile<this>;
44
+ readdir(path: string): Promise<string[]>;
45
+ readdirSync(path: string): string[];
46
+ protected abstract getData(path: string, stats: Stats): Promise<Uint8Array>;
47
+ protected abstract getDataSync(path: string, stats: Stats): Uint8Array;
48
+ }
49
+ export {};
@@ -0,0 +1,86 @@
1
+ import { ErrnoError, Errno } from '../../error.js';
2
+ import { NoSyncFile, isWriteable, flagToMode } from '../../file.js';
3
+ import { Readonly, FileSystem } from '../../filesystem.js';
4
+ import { decode } from '../../utils.js';
5
+ import { Index } from './index.js';
6
+ export class IndexFS extends Readonly(FileSystem) {
7
+ async ready() {
8
+ await super.ready();
9
+ if (this._isInitialized) {
10
+ return;
11
+ }
12
+ this.index.fromJSON(await this.indexData);
13
+ this._isInitialized = true;
14
+ }
15
+ constructor(indexData) {
16
+ super();
17
+ this.indexData = indexData;
18
+ this.index = new Index();
19
+ this._isInitialized = false;
20
+ }
21
+ async reloadFiles() {
22
+ for (const [path, stats] of this.index.files()) {
23
+ delete stats.fileData;
24
+ stats.fileData = await this.getData(path, stats);
25
+ }
26
+ }
27
+ reloadFilesSync() {
28
+ for (const [path, stats] of this.index.files()) {
29
+ delete stats.fileData;
30
+ stats.fileData = this.getDataSync(path, stats);
31
+ }
32
+ }
33
+ async stat(path) {
34
+ return this.statSync(path);
35
+ }
36
+ statSync(path) {
37
+ if (!this.index.has(path)) {
38
+ throw ErrnoError.With('ENOENT', path, 'stat');
39
+ }
40
+ return this.index.get(path);
41
+ }
42
+ async openFile(path, flag, cred) {
43
+ if (isWriteable(flag)) {
44
+ // You can't write to files on this file system.
45
+ throw new ErrnoError(Errno.EPERM, path);
46
+ }
47
+ // Check if the path exists, and is a file.
48
+ const stats = this.index.get(path);
49
+ if (!stats) {
50
+ throw ErrnoError.With('ENOENT', path, 'openFile');
51
+ }
52
+ if (!stats.hasAccess(flagToMode(flag), cred)) {
53
+ throw ErrnoError.With('EACCES', path, 'openFile');
54
+ }
55
+ return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : await this.getData(path, stats));
56
+ }
57
+ openFileSync(path, flag, cred) {
58
+ if (isWriteable(flag)) {
59
+ // You can't write to files on this file system.
60
+ throw new ErrnoError(Errno.EPERM, path);
61
+ }
62
+ // Check if the path exists, and is a file.
63
+ const stats = this.index.get(path);
64
+ if (!stats) {
65
+ throw ErrnoError.With('ENOENT', path, 'openFile');
66
+ }
67
+ if (!stats.hasAccess(flagToMode(flag), cred)) {
68
+ throw ErrnoError.With('EACCES', path, 'openFile');
69
+ }
70
+ return new NoSyncFile(this, path, flag, stats, stats.isDirectory() ? stats.fileData : this.getDataSync(path, stats));
71
+ }
72
+ async readdir(path) {
73
+ return this.readdirSync(path);
74
+ }
75
+ readdirSync(path) {
76
+ // Check if it exists.
77
+ const stats = this.index.get(path);
78
+ if (!stats) {
79
+ throw ErrnoError.With('ENOENT', path, 'readdir');
80
+ }
81
+ if (!stats.isDirectory()) {
82
+ throw ErrnoError.With('ENOTDIR', path, 'readdir');
83
+ }
84
+ return JSON.parse(decode(stats.fileData));
85
+ }
86
+ }
@@ -0,0 +1,37 @@
1
+ import { Stats, StatsLike } from '../../stats.js';
2
+ export interface IndexData {
3
+ version: 1;
4
+ entries: Record<string, StatsLike<number>>;
5
+ }
6
+ export declare const version = 1;
7
+ /**
8
+ * An index of files
9
+ */
10
+ export declare class Index extends Map<string, Stats> {
11
+ constructor();
12
+ /**
13
+ * Convience method
14
+ */
15
+ files(): Map<string, Stats>;
16
+ /**
17
+ * Converts the index to JSON
18
+ */
19
+ toJSON(): IndexData;
20
+ /**
21
+ * Converts the index to a string
22
+ */
23
+ toString(): string;
24
+ /**
25
+ * Returns the files in the directory `dir`.
26
+ * This is expensive so it is only called once per directory.
27
+ */
28
+ protected dirEntries(dir: string): string[];
29
+ /**
30
+ * Loads the index from JSON data
31
+ */
32
+ fromJSON(json: IndexData): void;
33
+ /**
34
+ * Parses an index from a string
35
+ */
36
+ static parse(data: string): Index;
37
+ }
@@ -0,0 +1,82 @@
1
+ import { isJSON } from 'utilium';
2
+ import { Errno, ErrnoError } from '../../error.js';
3
+ import { Stats } from '../../stats.js';
4
+ import { encode } from '../../utils.js';
5
+ import { basename, dirname } from '../../emulation/path.js';
6
+ export const version = 1;
7
+ /**
8
+ * An index of files
9
+ */
10
+ export class Index extends Map {
11
+ constructor() {
12
+ super();
13
+ }
14
+ /**
15
+ * Convience method
16
+ */
17
+ files() {
18
+ const files = new Map();
19
+ for (const [path, stats] of this) {
20
+ if (stats.isFile()) {
21
+ files.set(path, stats);
22
+ }
23
+ }
24
+ return files;
25
+ }
26
+ /**
27
+ * Converts the index to JSON
28
+ */
29
+ toJSON() {
30
+ return {
31
+ version,
32
+ entries: Object.fromEntries(this),
33
+ };
34
+ }
35
+ /**
36
+ * Converts the index to a string
37
+ */
38
+ toString() {
39
+ return JSON.stringify(this.toJSON());
40
+ }
41
+ /**
42
+ * Returns the files in the directory `dir`.
43
+ * This is expensive so it is only called once per directory.
44
+ */
45
+ dirEntries(dir) {
46
+ const entries = [];
47
+ for (const entry of this.keys()) {
48
+ if (dirname(entry) == dir) {
49
+ entries.push(basename(entry));
50
+ }
51
+ }
52
+ return entries;
53
+ }
54
+ /**
55
+ * Loads the index from JSON data
56
+ */
57
+ fromJSON(json) {
58
+ if (json.version != version) {
59
+ throw new ErrnoError(Errno.EINVAL, 'Index version mismatch');
60
+ }
61
+ this.clear();
62
+ for (const [path, data] of Object.entries(json.entries)) {
63
+ const stats = new Stats(data);
64
+ if (stats.isDirectory()) {
65
+ stats.fileData = encode(JSON.stringify(this.dirEntries(path)));
66
+ }
67
+ this.set(path, stats);
68
+ }
69
+ }
70
+ /**
71
+ * Parses an index from a string
72
+ */
73
+ static parse(data) {
74
+ if (!isJSON(data)) {
75
+ throw new ErrnoError(Errno.EINVAL, 'Invalid JSON');
76
+ }
77
+ const json = JSON.parse(data);
78
+ const index = new Index();
79
+ index.fromJSON(json);
80
+ return index;
81
+ }
82
+ }
@@ -14,7 +14,7 @@ import type { Stats } from '../stats.js';
14
14
  */
15
15
  export declare class LockedFS<FS extends FileSystem> implements FileSystem {
16
16
  readonly fs: FS;
17
- private _mu;
17
+ private mutex;
18
18
  constructor(fs: FS);
19
19
  ready(): Promise<void>;
20
20
  metadata(): FileSystemMetadata;