@zenfs/core 0.9.7 → 0.10.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 (71) hide show
  1. package/dist/backends/AsyncStore.js +29 -29
  2. package/dist/backends/Fetch.d.ts +84 -0
  3. package/dist/backends/Fetch.js +171 -0
  4. package/dist/backends/Index.js +19 -19
  5. package/dist/backends/Locked.d.ts +11 -11
  6. package/dist/backends/Locked.js +50 -49
  7. package/dist/backends/Overlay.js +21 -21
  8. package/dist/backends/SyncStore.js +27 -27
  9. package/dist/backends/backend.js +4 -4
  10. package/dist/backends/port/fs.d.ts +124 -0
  11. package/dist/backends/port/fs.js +241 -0
  12. package/dist/backends/port/rpc.d.ts +60 -0
  13. package/dist/backends/port/rpc.js +71 -0
  14. package/dist/backends/port/store.d.ts +30 -0
  15. package/dist/backends/port/store.js +142 -0
  16. package/dist/browser.min.js +4 -4
  17. package/dist/browser.min.js.map +4 -4
  18. package/dist/config.d.ts +8 -10
  19. package/dist/config.js +11 -11
  20. package/dist/emulation/async.js +6 -6
  21. package/dist/emulation/dir.js +2 -2
  22. package/dist/emulation/index.d.ts +1 -1
  23. package/dist/emulation/index.js +1 -1
  24. package/dist/emulation/path.d.ts +3 -2
  25. package/dist/emulation/path.js +19 -45
  26. package/dist/emulation/promises.d.ts +7 -12
  27. package/dist/emulation/promises.js +144 -146
  28. package/dist/emulation/shared.d.ts +5 -10
  29. package/dist/emulation/shared.js +8 -8
  30. package/dist/emulation/streams.js +3 -3
  31. package/dist/emulation/sync.js +25 -25
  32. package/dist/{ApiError.d.ts → error.d.ts} +13 -14
  33. package/dist/error.js +292 -0
  34. package/dist/file.d.ts +2 -0
  35. package/dist/file.js +10 -4
  36. package/dist/filesystem.js +15 -15
  37. package/dist/index.d.ts +4 -1
  38. package/dist/index.js +4 -1
  39. package/dist/mutex.js +2 -1
  40. package/dist/utils.d.ts +8 -7
  41. package/dist/utils.js +11 -12
  42. package/package.json +3 -3
  43. package/readme.md +17 -9
  44. package/src/backends/AsyncStore.ts +29 -29
  45. package/src/backends/Fetch.ts +230 -0
  46. package/src/backends/Index.ts +19 -19
  47. package/src/backends/Locked.ts +50 -49
  48. package/src/backends/Overlay.ts +23 -23
  49. package/src/backends/SyncStore.ts +27 -27
  50. package/src/backends/backend.ts +6 -6
  51. package/src/backends/port/fs.ts +308 -0
  52. package/src/backends/port/readme.md +59 -0
  53. package/src/backends/port/rpc.ts +144 -0
  54. package/src/backends/port/store.ts +187 -0
  55. package/src/config.ts +20 -24
  56. package/src/emulation/async.ts +6 -6
  57. package/src/emulation/dir.ts +2 -2
  58. package/src/emulation/index.ts +1 -1
  59. package/src/emulation/path.ts +25 -49
  60. package/src/emulation/promises.ts +150 -159
  61. package/src/emulation/shared.ts +12 -14
  62. package/src/emulation/streams.ts +3 -3
  63. package/src/emulation/sync.ts +28 -28
  64. package/src/{ApiError.ts → error.ts} +89 -89
  65. package/src/file.ts +12 -4
  66. package/src/filesystem.ts +15 -15
  67. package/src/index.ts +4 -1
  68. package/src/mutex.ts +3 -1
  69. package/src/utils.ts +16 -18
  70. package/tsconfig.json +2 -2
  71. package/dist/ApiError.js +0 -292
@@ -1,5 +1,5 @@
1
1
  import { dirname, basename, join, resolve } from '../emulation/path.js';
2
- import { ApiError, ErrorCode } from '../ApiError.js';
2
+ import { ErrnoError, Errno } from '../error.js';
3
3
  import { W_OK, R_OK } from '../emulation/constants.js';
4
4
  import { PreloadFile, flagToMode } from '../file.js';
5
5
  import { Async, FileSystem } from '../filesystem.js';
@@ -53,7 +53,7 @@ class LRUCache {
53
53
  export class AsyncStoreFS extends Async(FileSystem) {
54
54
  get store() {
55
55
  if (!this._store) {
56
- throw new ReferenceError('AsyncStoreFS not attached to a store');
56
+ throw new ErrnoError(Errno.ENODATA, 'No store attached');
57
57
  }
58
58
  return this._store;
59
59
  }
@@ -110,10 +110,10 @@ export class AsyncStoreFS extends Async(FileSystem) {
110
110
  // Remove oldPath from parent's directory listing.
111
111
  oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
112
112
  if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
113
- throw ApiError.With('EACCES', oldPath, 'rename');
113
+ throw ErrnoError.With('EACCES', oldPath, 'rename');
114
114
  }
115
115
  if (!oldDirList[oldName]) {
116
- throw ApiError.With('ENOENT', oldPath, 'rename');
116
+ throw ErrnoError.With('ENOENT', oldPath, 'rename');
117
117
  }
118
118
  const nodeId = oldDirList[oldName];
119
119
  delete oldDirList[oldName];
@@ -122,7 +122,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
122
122
  // is a subpath of newParent. We append '/' to avoid matching folders that
123
123
  // are a substring of the bottom-most folder in the path.
124
124
  if ((newParent + '/').indexOf(oldPath + '/') === 0) {
125
- throw new ApiError(ErrorCode.EBUSY, oldParent);
125
+ throw new ErrnoError(Errno.EBUSY, oldParent);
126
126
  }
127
127
  // Add newPath to parent's directory listing.
128
128
  let newDirNode, newDirList;
@@ -151,7 +151,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
151
151
  }
152
152
  else {
153
153
  // If it's a directory, throw a permissions error.
154
- throw ApiError.With('EPERM', newPath, 'rename');
154
+ throw ErrnoError.With('EPERM', newPath, 'rename');
155
155
  }
156
156
  }
157
157
  newDirList[newName] = nodeId;
@@ -177,11 +177,11 @@ export class AsyncStoreFS extends Async(FileSystem) {
177
177
  const tx = this.store.beginTransaction();
178
178
  const inode = await this.findINode(tx, p);
179
179
  if (!inode) {
180
- throw ApiError.With('ENOENT', p, 'stat');
180
+ throw ErrnoError.With('ENOENT', p, 'stat');
181
181
  }
182
182
  const stats = inode.toStats();
183
183
  if (!stats.hasAccess(R_OK, cred)) {
184
- throw ApiError.With('EACCES', p, 'stat');
184
+ throw ErrnoError.With('EACCES', p, 'stat');
185
185
  }
186
186
  return stats;
187
187
  }
@@ -195,10 +195,10 @@ export class AsyncStoreFS extends Async(FileSystem) {
195
195
  await this.queueDone();
196
196
  const tx = this.store.beginTransaction(), node = await this.findINode(tx, p), data = await tx.get(node.ino);
197
197
  if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
198
- throw ApiError.With('EACCES', p, 'openFile');
198
+ throw ErrnoError.With('EACCES', p, 'openFile');
199
199
  }
200
200
  if (!data) {
201
- throw ApiError.With('ENOENT', p, 'openFile');
201
+ throw ErrnoError.With('ENOENT', p, 'openFile');
202
202
  }
203
203
  return new PreloadFile(this, p, flag, node.toStats(), data);
204
204
  }
@@ -211,7 +211,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
211
211
  // Check first if directory is empty.
212
212
  const list = await this.readdir(p, cred);
213
213
  if (list.length > 0) {
214
- throw ApiError.With('ENOTEMPTY', p, 'rmdir');
214
+ throw ErrnoError.With('ENOTEMPTY', p, 'rmdir');
215
215
  }
216
216
  await this.removeEntry(p, true, cred);
217
217
  }
@@ -225,7 +225,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
225
225
  const tx = this.store.beginTransaction();
226
226
  const node = await this.findINode(tx, p);
227
227
  if (!node.toStats().hasAccess(R_OK, cred)) {
228
- throw ApiError.With('EACCES', p, 'readdur');
228
+ throw ErrnoError.With('EACCES', p, 'readdur');
229
229
  }
230
230
  return Object.keys(await this.getDirListing(tx, node, p));
231
231
  }
@@ -256,16 +256,16 @@ export class AsyncStoreFS extends Async(FileSystem) {
256
256
  await this.queueDone();
257
257
  const tx = this.store.beginTransaction(), existingDir = dirname(existing), existingDirNode = await this.findINode(tx, existingDir);
258
258
  if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
259
- throw ApiError.With('EACCES', existingDir, 'link');
259
+ throw ErrnoError.With('EACCES', existingDir, 'link');
260
260
  }
261
261
  const newDir = dirname(newpath), newDirNode = await this.findINode(tx, newDir), newListing = await this.getDirListing(tx, newDirNode, newDir);
262
262
  if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
263
- throw ApiError.With('EACCES', newDir, 'link');
263
+ throw ErrnoError.With('EACCES', newDir, 'link');
264
264
  }
265
265
  const ino = await this._findINode(tx, existingDir, basename(existing));
266
266
  const node = await this.getINode(tx, ino, existing);
267
267
  if (!node.toStats().hasAccess(W_OK, cred)) {
268
- throw ApiError.With('EACCES', newpath, 'link');
268
+ throw ErrnoError.With('EACCES', newpath, 'link');
269
269
  }
270
270
  node.nlink++;
271
271
  newListing[basename(newpath)] = ino;
@@ -303,7 +303,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
303
303
  async _findINode(tx, parent, filename, visited = new Set()) {
304
304
  const currentPath = join(parent, filename);
305
305
  if (visited.has(currentPath)) {
306
- throw new ApiError(ErrorCode.EIO, 'Infinite loop detected while finding inode', currentPath);
306
+ throw new ErrnoError(Errno.EIO, 'Infinite loop detected while finding inode', currentPath);
307
307
  }
308
308
  visited.add(currentPath);
309
309
  if (this._cache) {
@@ -332,7 +332,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
332
332
  return id;
333
333
  }
334
334
  else {
335
- throw ApiError.With('ENOENT', resolve(parent, filename), '_findINode');
335
+ throw ErrnoError.With('ENOENT', resolve(parent, filename), '_findINode');
336
336
  }
337
337
  }
338
338
  }
@@ -349,7 +349,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
349
349
  return id;
350
350
  }
351
351
  else {
352
- throw ApiError.With('ENOENT', resolve(parent, filename), '_findINode');
352
+ throw ErrnoError.With('ENOENT', resolve(parent, filename), '_findINode');
353
353
  }
354
354
  }
355
355
  }
@@ -371,7 +371,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
371
371
  async getINode(tx, id, p) {
372
372
  const data = await tx.get(id);
373
373
  if (!data) {
374
- throw ApiError.With('ENOENT', p, 'getINode');
374
+ throw ErrnoError.With('ENOENT', p, 'getINode');
375
375
  }
376
376
  return new Inode(data.buffer);
377
377
  }
@@ -381,7 +381,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
381
381
  */
382
382
  async getDirListing(tx, inode, p) {
383
383
  if (!inode.toStats().isDirectory()) {
384
- throw ApiError.With('ENOTDIR', p, 'getDirListing');
384
+ throw ErrnoError.With('ENOTDIR', p, 'getDirListing');
385
385
  }
386
386
  const data = await tx.get(inode.ino);
387
387
  if (!data) {
@@ -390,7 +390,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
390
390
  than a directory listing. The latter should never occur unless
391
391
  the file system is corrupted.
392
392
  */
393
- throw ApiError.With('ENOENT', p, 'getDirListing');
393
+ throw ErrnoError.With('ENOENT', p, 'getDirListing');
394
394
  }
395
395
  return decodeDirListing(data);
396
396
  }
@@ -401,7 +401,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
401
401
  async addNewNode(tx, data, _maxAttempts = 5) {
402
402
  if (_maxAttempts <= 0) {
403
403
  // Max retries hit. Return with an error.
404
- throw new ApiError(ErrorCode.EIO, 'Unable to commit data to key-value store.');
404
+ throw new ErrnoError(Errno.EIO, 'Unable to commit data to key-value store.');
405
405
  }
406
406
  // Make an attempt
407
407
  const ino = randomIno();
@@ -425,18 +425,18 @@ export class AsyncStoreFS extends Async(FileSystem) {
425
425
  const parentDir = dirname(p), fname = basename(p), parentNode = await this.findINode(tx, parentDir), dirListing = await this.getDirListing(tx, parentNode, parentDir);
426
426
  //Check that the creater has correct access
427
427
  if (!parentNode.toStats().hasAccess(W_OK, cred)) {
428
- throw ApiError.With('EACCES', p, 'commitNewFile');
428
+ throw ErrnoError.With('EACCES', p, 'commitNewFile');
429
429
  }
430
430
  // Invariant: The root always exists.
431
431
  // If we don't check this prior to taking steps below, we will create a
432
432
  // file with name '' in root should p == '/'.
433
433
  if (p === '/') {
434
- throw ApiError.With('EEXIST', p, 'commitNewFile');
434
+ throw ErrnoError.With('EEXIST', p, 'commitNewFile');
435
435
  }
436
436
  // Check if file already exists.
437
437
  if (dirListing[fname]) {
438
438
  await tx.abort();
439
- throw ApiError.With('EEXIST', p, 'commitNewFile');
439
+ throw ErrnoError.With('EEXIST', p, 'commitNewFile');
440
440
  }
441
441
  try {
442
442
  // Commit data.
@@ -475,21 +475,21 @@ export class AsyncStoreFS extends Async(FileSystem) {
475
475
  }
476
476
  const tx = this.store.beginTransaction(), parent = dirname(p), parentNode = await this.findINode(tx, parent), parentListing = await this.getDirListing(tx, parentNode, parent), fileName = basename(p);
477
477
  if (!parentListing[fileName]) {
478
- throw ApiError.With('ENOENT', p, 'removeEntry');
478
+ throw ErrnoError.With('ENOENT', p, 'removeEntry');
479
479
  }
480
480
  const fileIno = parentListing[fileName];
481
481
  // Get file inode.
482
482
  const fileNode = await this.getINode(tx, fileIno, p);
483
483
  if (!fileNode.toStats().hasAccess(W_OK, cred)) {
484
- throw ApiError.With('EACCES', p, 'removeEntry');
484
+ throw ErrnoError.With('EACCES', p, 'removeEntry');
485
485
  }
486
486
  // Remove from directory listing of parent.
487
487
  delete parentListing[fileName];
488
488
  if (!isDir && fileNode.toStats().isDirectory()) {
489
- throw ApiError.With('EISDIR', p, 'removeEntry');
489
+ throw ErrnoError.With('EISDIR', p, 'removeEntry');
490
490
  }
491
491
  if (isDir && !fileNode.toStats().isDirectory()) {
492
- throw ApiError.With('ENOTDIR', p, 'removeEntry');
492
+ throw ErrnoError.With('ENOTDIR', p, 'removeEntry');
493
493
  }
494
494
  try {
495
495
  await tx.put(parentNode.ino, encodeDirListing(parentListing), true);
@@ -0,0 +1,84 @@
1
+ import { NoSyncFile } from '../file.js';
2
+ import type { FileSystemMetadata } from '../filesystem.js';
3
+ import { Stats } from '../stats.js';
4
+ import { type ListingTree, type IndexFileInode, AsyncIndexFS } from './Index.js';
5
+ /**
6
+ * Configuration options for FetchFS.
7
+ */
8
+ export interface FetchOptions {
9
+ /**
10
+ * URL to a file index as a JSON file or the file index object itself.
11
+ * Defaults to `index.json`.
12
+ */
13
+ index?: string | ListingTree;
14
+ /** Used as the URL prefix for fetched files.
15
+ * Default: Fetch files relative to the index.
16
+ */
17
+ baseUrl?: string;
18
+ }
19
+ /**
20
+ * A simple filesystem backed by HTTP using the fetch API.
21
+ *
22
+ *
23
+ * Listings objects look like the following:
24
+ *
25
+ * ```json
26
+ * {
27
+ * "home": {
28
+ * "jvilk": {
29
+ * "someFile.txt": null,
30
+ * "someDir": {
31
+ * // Empty directory
32
+ * }
33
+ * }
34
+ * }
35
+ * }
36
+ * ```
37
+ *
38
+ * This example has the folder `/home/jvilk` with subfile `someFile.txt` and subfolder `someDir`.
39
+ */
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>;
44
+ ready(): Promise<this>;
45
+ constructor({ index, baseUrl }: FetchOptions);
46
+ metadata(): FileSystemMetadata;
47
+ empty(): void;
48
+ /**
49
+ * Special function: Preload the given file into the index.
50
+ * @param path
51
+ * @param buffer
52
+ */
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;
57
+ /**
58
+ * Asynchronously download the given file.
59
+ */
60
+ protected _fetchFile(p: string, type: 'buffer'): Promise<Uint8Array>;
61
+ protected _fetchFile(p: string, type: 'json'): Promise<object>;
62
+ protected _fetchFile(p: 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>;
67
+ }
68
+ export declare const Fetch: {
69
+ readonly name: "Fetch";
70
+ readonly options: {
71
+ readonly index: {
72
+ readonly type: readonly ["string", "object"];
73
+ 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`.";
75
+ };
76
+ readonly baseUrl: {
77
+ readonly type: "string";
78
+ readonly required: false;
79
+ readonly description: "Used as the URL prefix for fetched files. Default: Fetch files relative to the index.";
80
+ };
81
+ };
82
+ readonly isAvailable: () => boolean;
83
+ readonly create: (options: FetchOptions) => FetchFS;
84
+ };
@@ -0,0 +1,171 @@
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
+ }
11
+ async function fetchFile(p, type) {
12
+ const response = await fetch(p).catch(convertError);
13
+ if (!response.ok) {
14
+ throw new ErrnoError(Errno.EIO, 'fetch failed: response returned code ' + response.status);
15
+ }
16
+ switch (type) {
17
+ case 'buffer':
18
+ const arrayBuffer = await response.arrayBuffer().catch(convertError);
19
+ return new Uint8Array(arrayBuffer);
20
+ case 'json':
21
+ return response.json().catch(convertError);
22
+ default:
23
+ throw new ErrnoError(Errno.EINVAL, 'Invalid download type: ' + type);
24
+ }
25
+ }
26
+ /**
27
+ * Asynchronously retrieves the size of the given file in bytes.
28
+ * @hidden
29
+ */
30
+ async function fetchSize(p) {
31
+ const response = await fetch(p, { 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.
39
+ *
40
+ *
41
+ * Listings objects look like the following:
42
+ *
43
+ * ```json
44
+ * {
45
+ * "home": {
46
+ * "jvilk": {
47
+ * "someFile.txt": null,
48
+ * "someDir": {
49
+ * // Empty directory
50
+ * }
51
+ * }
52
+ * }
53
+ * }
54
+ * ```
55
+ *
56
+ * This example has the folder `/home/jvilk` with subfile `someFile.txt` and subfolder `someDir`.
57
+ */
58
+ export class FetchFS extends AsyncIndexFS {
59
+ async _initialize(index) {
60
+ if (typeof index != 'string') {
61
+ this._index = FileIndex.FromListing(index);
62
+ return;
63
+ }
64
+ try {
65
+ const response = await fetch(index);
66
+ this._index = FileIndex.FromListing((await response.json()));
67
+ }
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
+ return this;
75
+ }
76
+ constructor({ index = 'index.json', baseUrl = '' }) {
77
+ super({});
78
+ // prefix url must end in a directory separator.
79
+ if (baseUrl.at(-1) != '/') {
80
+ baseUrl += '/';
81
+ }
82
+ this.prefixUrl = baseUrl;
83
+ this._init = this._initialize(index);
84
+ }
85
+ metadata() {
86
+ return {
87
+ ...super.metadata(),
88
+ name: FetchFS.name,
89
+ readonly: true,
90
+ };
91
+ }
92
+ empty() {
93
+ for (const file of this._index.files()) {
94
+ delete file.data.fileData;
95
+ }
96
+ }
97
+ /**
98
+ * Special function: Preload the given file into the index.
99
+ * @param path
100
+ * @param buffer
101
+ */
102
+ preloadFile(path, buffer) {
103
+ const inode = this._index.get(path);
104
+ if (!inode) {
105
+ throw ErrnoError.With('ENOENT', path, 'preloadFile');
106
+ }
107
+ if (!inode.isFile()) {
108
+ throw ErrnoError.With('EISDIR', path, 'preloadFile');
109
+ }
110
+ const stats = inode.data;
111
+ stats.size = buffer.length;
112
+ stats.fileData = buffer;
113
+ }
114
+ async statFileInode(inode, path) {
115
+ const stats = inode.data;
116
+ // At this point, a non-opened file will still have default stats from the listing.
117
+ if (stats.size < 0) {
118
+ stats.size = await this._fetchSize(path);
119
+ }
120
+ return stats;
121
+ }
122
+ async openFileInode(inode, path, flag) {
123
+ const stats = inode.data;
124
+ // Use existing file contents. This maintains the previously-used flag.
125
+ if (stats.fileData) {
126
+ return new NoSyncFile(this, path, flag, new Stats(stats), stats.fileData);
127
+ }
128
+ // @todo be lazier about actually requesting the file
129
+ const data = await this._fetchFile(path, 'buffer');
130
+ // we don't initially have file sizes
131
+ stats.size = data.length;
132
+ stats.fileData = data;
133
+ return new NoSyncFile(this, path, flag, new Stats(stats), data);
134
+ }
135
+ _getRemotePath(filePath) {
136
+ if (filePath.charAt(0) === '/') {
137
+ filePath = filePath.slice(1);
138
+ }
139
+ return this.prefixUrl + filePath;
140
+ }
141
+ _fetchFile(p, type) {
142
+ return fetchFile(this._getRemotePath(p), type);
143
+ }
144
+ /**
145
+ * Only requests the HEAD content, for the file size.
146
+ */
147
+ _fetchSize(path) {
148
+ return fetchSize(this._getRemotePath(path));
149
+ }
150
+ }
151
+ export const Fetch = {
152
+ name: 'Fetch',
153
+ options: {
154
+ index: {
155
+ type: ['string', 'object'],
156
+ required: false,
157
+ 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`.',
158
+ },
159
+ baseUrl: {
160
+ type: 'string',
161
+ required: false,
162
+ description: 'Used as the URL prefix for fetched files. Default: Fetch files relative to the index.',
163
+ },
164
+ },
165
+ isAvailable() {
166
+ return typeof globalThis.fetch == 'function';
167
+ },
168
+ create(options) {
169
+ return new FetchFS(options);
170
+ },
171
+ };
@@ -1,4 +1,4 @@
1
- import { ApiError, ErrorCode } from '../ApiError.js';
1
+ import { ErrnoError, Errno } from '../error.js';
2
2
  import { basename, dirname, join } from '../emulation/path.js';
3
3
  import { NoSyncFile, flagToMode, isWriteable } from '../file.js';
4
4
  import { FileSystem, Readonly } from '../filesystem.js';
@@ -91,10 +91,10 @@ export class FileIndex {
91
91
  */
92
92
  add(path, inode) {
93
93
  if (!inode) {
94
- throw new Error('Inode must be specified');
94
+ throw new ErrnoError(Errno.EINVAL, 'Inode must be specified', path, 'FileIndex.add');
95
95
  }
96
96
  if (!path.startsWith('/')) {
97
- throw new Error('Path must be absolute, got: ' + path);
97
+ throw new ErrnoError(Errno.EINVAL, 'Path not absolute', path, 'FileIndex.add');
98
98
  }
99
99
  // Check if it already exists.
100
100
  if (this._index.has(path)) {
@@ -308,7 +308,7 @@ export class IndexFS extends Readonly(FileSystem) {
308
308
  async stat(path) {
309
309
  const inode = this._index.get(path);
310
310
  if (!inode) {
311
- throw ApiError.With('ENOENT', path, 'stat');
311
+ throw ErrnoError.With('ENOENT', path, 'stat');
312
312
  }
313
313
  if (inode.isDirectory()) {
314
314
  return inode.stats;
@@ -316,12 +316,12 @@ export class IndexFS extends Readonly(FileSystem) {
316
316
  if (inode.isFile()) {
317
317
  return this.statFileInode(inode, path);
318
318
  }
319
- throw new ApiError(ErrorCode.EINVAL, 'Invalid inode.');
319
+ throw new ErrnoError(Errno.EINVAL, 'Invalid inode.');
320
320
  }
321
321
  statSync(path) {
322
322
  const inode = this._index.get(path);
323
323
  if (!inode) {
324
- throw ApiError.With('ENOENT', path, 'stat');
324
+ throw ErrnoError.With('ENOENT', path, 'stat');
325
325
  }
326
326
  if (inode.isDirectory()) {
327
327
  return inode.stats;
@@ -329,20 +329,20 @@ export class IndexFS extends Readonly(FileSystem) {
329
329
  if (inode.isFile()) {
330
330
  return this.statFileInodeSync(inode, path);
331
331
  }
332
- throw new ApiError(ErrorCode.EINVAL, 'Invalid inode.');
332
+ throw new ErrnoError(Errno.EINVAL, 'Invalid inode.');
333
333
  }
334
334
  async openFile(path, flag, cred) {
335
335
  if (isWriteable(flag)) {
336
336
  // You can't write to files on this file system.
337
- throw new ApiError(ErrorCode.EPERM, path);
337
+ throw new ErrnoError(Errno.EPERM, path);
338
338
  }
339
339
  // Check if the path exists, and is a file.
340
340
  const inode = this._index.get(path);
341
341
  if (!inode) {
342
- throw ApiError.With('ENOENT', path, 'openFile');
342
+ throw ErrnoError.With('ENOENT', path, 'openFile');
343
343
  }
344
344
  if (!inode.toStats().hasAccess(flagToMode(flag), cred)) {
345
- throw ApiError.With('EACCES', path, 'openFile');
345
+ throw ErrnoError.With('EACCES', path, 'openFile');
346
346
  }
347
347
  if (inode.isDirectory()) {
348
348
  const stats = inode.stats;
@@ -353,15 +353,15 @@ export class IndexFS extends Readonly(FileSystem) {
353
353
  openFileSync(path, flag, cred) {
354
354
  if (isWriteable(flag)) {
355
355
  // You can't write to files on this file system.
356
- throw new ApiError(ErrorCode.EPERM, path);
356
+ throw new ErrnoError(Errno.EPERM, path);
357
357
  }
358
358
  // Check if the path exists, and is a file.
359
359
  const inode = this._index.get(path);
360
360
  if (!inode) {
361
- throw ApiError.With('ENOENT', path, 'openFile');
361
+ throw ErrnoError.With('ENOENT', path, 'openFile');
362
362
  }
363
363
  if (!inode.toStats().hasAccess(flagToMode(flag), cred)) {
364
- throw ApiError.With('EACCES', path, 'openFile');
364
+ throw ErrnoError.With('EACCES', path, 'openFile');
365
365
  }
366
366
  if (inode.isDirectory()) {
367
367
  const stats = inode.stats;
@@ -373,23 +373,23 @@ export class IndexFS extends Readonly(FileSystem) {
373
373
  // Check if it exists.
374
374
  const inode = this._index.get(path);
375
375
  if (!inode) {
376
- throw ApiError.With('ENOENT', path, 'readdir');
376
+ throw ErrnoError.With('ENOENT', path, 'readdir');
377
377
  }
378
378
  if (inode.isDirectory()) {
379
379
  return inode.listing;
380
380
  }
381
- throw ApiError.With('ENOTDIR', path, 'readdir');
381
+ throw ErrnoError.With('ENOTDIR', path, 'readdir');
382
382
  }
383
383
  readdirSync(path) {
384
384
  // Check if it exists.
385
385
  const inode = this._index.get(path);
386
386
  if (!inode) {
387
- throw ApiError.With('ENOENT', path, 'readdir');
387
+ throw ErrnoError.With('ENOENT', path, 'readdir');
388
388
  }
389
389
  if (inode.isDirectory()) {
390
390
  return inode.listing;
391
391
  }
392
- throw ApiError.With('ENOTDIR', path, 'readdir');
392
+ throw ErrnoError.With('ENOTDIR', path, 'readdir');
393
393
  }
394
394
  }
395
395
  export class SyncIndexFS extends IndexFS {
@@ -402,9 +402,9 @@ export class SyncIndexFS extends IndexFS {
402
402
  }
403
403
  export class AsyncIndexFS extends IndexFS {
404
404
  statFileInodeSync(inode, path) {
405
- throw ApiError.With('ENOSYS', path, 'AsyncIndexFS.statFileInodeSync');
405
+ throw ErrnoError.With('ENOSYS', path, 'AsyncIndexFS.statFileInodeSync');
406
406
  }
407
407
  openFileInodeSync(inode, path, flag) {
408
- throw ApiError.With('ENOSYS', path, 'AsyncIndexFS.openFileInodeSync');
408
+ throw ErrnoError.With('ENOSYS', path, 'AsyncIndexFS.openFileInodeSync');
409
409
  }
410
410
  }
@@ -20,22 +20,22 @@ export declare class LockedFS<FS extends FileSystem> implements FileSystem {
20
20
  metadata(): FileSystemMetadata;
21
21
  rename(oldPath: string, newPath: string, cred: Cred): Promise<void>;
22
22
  renameSync(oldPath: string, newPath: string, cred: Cred): void;
23
- stat(p: string, cred: Cred): Promise<Stats>;
24
- statSync(p: string, cred: Cred): Stats;
23
+ stat(path: string, cred: Cred): Promise<Stats>;
24
+ statSync(path: string, cred: Cred): Stats;
25
25
  openFile(path: string, flag: string, cred: Cred): Promise<File>;
26
26
  openFileSync(path: string, flag: string, cred: Cred): File;
27
27
  createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File>;
28
28
  createFileSync(path: string, flag: string, mode: number, cred: Cred): File;
29
29
  unlink(p: string, cred: Cred): Promise<void>;
30
- unlinkSync(p: string, cred: Cred): void;
31
- rmdir(p: string, cred: Cred): Promise<void>;
32
- rmdirSync(p: string, cred: Cred): void;
33
- mkdir(p: string, mode: number, cred: Cred): Promise<void>;
34
- mkdirSync(p: string, mode: number, cred: Cred): void;
35
- readdir(p: string, cred: Cred): Promise<string[]>;
36
- readdirSync(p: string, cred: Cred): string[];
37
- exists(p: string, cred: Cred): Promise<boolean>;
38
- existsSync(p: string, cred: Cred): boolean;
30
+ unlinkSync(path: string, cred: Cred): void;
31
+ rmdir(path: string, cred: Cred): Promise<void>;
32
+ rmdirSync(path: string, cred: Cred): void;
33
+ mkdir(path: string, mode: number, cred: Cred): Promise<void>;
34
+ mkdirSync(path: string, mode: number, cred: Cred): void;
35
+ readdir(path: string, cred: Cred): Promise<string[]>;
36
+ readdirSync(path: string, cred: Cred): string[];
37
+ exists(path: string, cred: Cred): Promise<boolean>;
38
+ existsSync(path: string, cred: Cred): boolean;
39
39
  link(srcpath: string, dstpath: string, cred: Cred): Promise<void>;
40
40
  linkSync(srcpath: string, dstpath: string, cred: Cred): void;
41
41
  sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;