@zenfs/core 0.9.6 → 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 (82) hide show
  1. package/dist/backends/AsyncStore.d.ts +3 -2
  2. package/dist/backends/AsyncStore.js +40 -33
  3. package/dist/backends/Fetch.d.ts +84 -0
  4. package/dist/backends/Fetch.js +171 -0
  5. package/dist/backends/InMemory.d.ts +1 -1
  6. package/dist/backends/Index.d.ts +7 -10
  7. package/dist/backends/Index.js +26 -24
  8. package/dist/backends/Locked.d.ts +11 -11
  9. package/dist/backends/Locked.js +50 -49
  10. package/dist/backends/Overlay.js +22 -22
  11. package/dist/backends/SyncStore.d.ts +6 -6
  12. package/dist/backends/SyncStore.js +30 -30
  13. package/dist/backends/backend.d.ts +5 -4
  14. package/dist/backends/backend.js +6 -6
  15. package/dist/backends/port/fs.d.ts +124 -0
  16. package/dist/backends/port/fs.js +241 -0
  17. package/dist/backends/port/rpc.d.ts +60 -0
  18. package/dist/backends/port/rpc.js +71 -0
  19. package/dist/backends/port/store.d.ts +30 -0
  20. package/dist/backends/port/store.js +142 -0
  21. package/dist/browser.min.js +4 -4
  22. package/dist/browser.min.js.map +4 -4
  23. package/dist/config.d.ts +9 -11
  24. package/dist/config.js +13 -13
  25. package/dist/emulation/async.d.ts +76 -77
  26. package/dist/emulation/async.js +45 -45
  27. package/dist/emulation/dir.js +8 -7
  28. package/dist/emulation/index.d.ts +1 -1
  29. package/dist/emulation/index.js +1 -1
  30. package/dist/emulation/path.d.ts +3 -2
  31. package/dist/emulation/path.js +19 -45
  32. package/dist/emulation/promises.d.ts +112 -113
  33. package/dist/emulation/promises.js +167 -173
  34. package/dist/emulation/shared.d.ts +6 -17
  35. package/dist/emulation/shared.js +9 -9
  36. package/dist/emulation/streams.js +3 -2
  37. package/dist/emulation/sync.d.ts +71 -64
  38. package/dist/emulation/sync.js +62 -63
  39. package/dist/{ApiError.d.ts → error.d.ts} +15 -15
  40. package/dist/error.js +292 -0
  41. package/dist/file.d.ts +6 -4
  42. package/dist/file.js +17 -9
  43. package/dist/filesystem.d.ts +1 -1
  44. package/dist/filesystem.js +18 -15
  45. package/dist/index.d.ts +4 -1
  46. package/dist/index.js +4 -1
  47. package/dist/mutex.js +4 -3
  48. package/dist/stats.d.ts +7 -7
  49. package/dist/stats.js +50 -10
  50. package/dist/utils.d.ts +10 -9
  51. package/dist/utils.js +15 -15
  52. package/package.json +5 -5
  53. package/readme.md +19 -11
  54. package/src/backends/AsyncStore.ts +42 -36
  55. package/src/backends/Fetch.ts +230 -0
  56. package/src/backends/Index.ts +33 -29
  57. package/src/backends/Locked.ts +50 -49
  58. package/src/backends/Overlay.ts +24 -24
  59. package/src/backends/SyncStore.ts +34 -34
  60. package/src/backends/backend.ts +13 -11
  61. package/src/backends/port/fs.ts +308 -0
  62. package/src/backends/port/readme.md +59 -0
  63. package/src/backends/port/rpc.ts +144 -0
  64. package/src/backends/port/store.ts +187 -0
  65. package/src/config.ts +25 -29
  66. package/src/emulation/async.ts +191 -199
  67. package/src/emulation/dir.ts +8 -8
  68. package/src/emulation/index.ts +1 -1
  69. package/src/emulation/path.ts +25 -49
  70. package/src/emulation/promises.ts +286 -287
  71. package/src/emulation/shared.ts +14 -23
  72. package/src/emulation/streams.ts +9 -8
  73. package/src/emulation/sync.ts +182 -182
  74. package/src/{ApiError.ts → error.ts} +91 -89
  75. package/src/file.ts +23 -13
  76. package/src/filesystem.ts +26 -22
  77. package/src/index.ts +4 -1
  78. package/src/mutex.ts +6 -4
  79. package/src/stats.ts +32 -23
  80. package/src/utils.ts +23 -24
  81. package/tsconfig.json +4 -3
  82. package/dist/ApiError.js +0 -292
@@ -11,7 +11,7 @@ declare class LRUCache<K, V> {
11
11
  private cache;
12
12
  constructor(limit: number);
13
13
  set(key: K, value: V): void;
14
- get(key: K): V | null;
14
+ get(key: K): V | void;
15
15
  remove(key: K): void;
16
16
  reset(): void;
17
17
  }
@@ -113,7 +113,8 @@ declare const AsyncStoreFS_base: (abstract new (...args: any[]) => {
113
113
  */
114
114
  export declare class AsyncStoreFS extends AsyncStoreFS_base {
115
115
  protected _options: AsyncStoreOptions;
116
- protected store: AsyncStore;
116
+ protected _store?: AsyncStore;
117
+ protected get store(): AsyncStore;
117
118
  protected _cache?: LRUCache<string, Ino>;
118
119
  private _initialized;
119
120
  _sync: FileSystem;
@@ -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';
@@ -51,16 +51,22 @@ class LRUCache {
51
51
  * @internal
52
52
  */
53
53
  export class AsyncStoreFS extends Async(FileSystem) {
54
+ get store() {
55
+ if (!this._store) {
56
+ throw new ErrnoError(Errno.ENODATA, 'No store attached');
57
+ }
58
+ return this._store;
59
+ }
54
60
  async ready() {
55
61
  if (this._initialized) {
56
62
  return this;
57
63
  }
58
64
  this._initialized = true;
59
- if (this._options.lruCacheSize > 0) {
65
+ if (this._options.lruCacheSize) {
60
66
  this._cache = new LRUCache(this._options.lruCacheSize);
61
67
  }
62
- this.store = await this._options.store;
63
- this._sync = this._options.sync || InMemory.create({ name: 'test' });
68
+ this._store = await this._options.store;
69
+ this._sync = this._options.sync || this._sync;
64
70
  await this.makeRootDirectory();
65
71
  await super.ready();
66
72
  return this;
@@ -75,6 +81,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
75
81
  super();
76
82
  this._options = _options;
77
83
  this._initialized = false;
84
+ this._sync = InMemory.create({ name: 'test' });
78
85
  }
79
86
  /**
80
87
  * Delete all contents stored in the file system.
@@ -95,18 +102,18 @@ export class AsyncStoreFS extends Async(FileSystem) {
95
102
  const c = this._cache;
96
103
  if (this._cache) {
97
104
  // Clear and disable cache during renaming process.
98
- this._cache = null;
99
- c.reset();
105
+ delete this._cache;
106
+ c?.reset();
100
107
  }
101
108
  try {
102
109
  const tx = this.store.beginTransaction(), oldParent = dirname(oldPath), oldName = basename(oldPath), newParent = dirname(newPath), newName = basename(newPath),
103
110
  // Remove oldPath from parent's directory listing.
104
111
  oldDirNode = await this.findINode(tx, oldParent), oldDirList = await this.getDirListing(tx, oldDirNode, oldParent);
105
112
  if (!oldDirNode.toStats().hasAccess(W_OK, cred)) {
106
- throw ApiError.With('EACCES', oldPath, 'rename');
113
+ throw ErrnoError.With('EACCES', oldPath, 'rename');
107
114
  }
108
115
  if (!oldDirList[oldName]) {
109
- throw ApiError.With('ENOENT', oldPath, 'rename');
116
+ throw ErrnoError.With('ENOENT', oldPath, 'rename');
110
117
  }
111
118
  const nodeId = oldDirList[oldName];
112
119
  delete oldDirList[oldName];
@@ -115,7 +122,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
115
122
  // is a subpath of newParent. We append '/' to avoid matching folders that
116
123
  // are a substring of the bottom-most folder in the path.
117
124
  if ((newParent + '/').indexOf(oldPath + '/') === 0) {
118
- throw new ApiError(ErrorCode.EBUSY, oldParent);
125
+ throw new ErrnoError(Errno.EBUSY, oldParent);
119
126
  }
120
127
  // Add newPath to parent's directory listing.
121
128
  let newDirNode, newDirList;
@@ -144,7 +151,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
144
151
  }
145
152
  else {
146
153
  // If it's a directory, throw a permissions error.
147
- throw ApiError.With('EPERM', newPath, 'rename');
154
+ throw ErrnoError.With('EPERM', newPath, 'rename');
148
155
  }
149
156
  }
150
157
  newDirList[newName] = nodeId;
@@ -170,11 +177,11 @@ export class AsyncStoreFS extends Async(FileSystem) {
170
177
  const tx = this.store.beginTransaction();
171
178
  const inode = await this.findINode(tx, p);
172
179
  if (!inode) {
173
- throw ApiError.With('ENOENT', p, 'stat');
180
+ throw ErrnoError.With('ENOENT', p, 'stat');
174
181
  }
175
182
  const stats = inode.toStats();
176
183
  if (!stats.hasAccess(R_OK, cred)) {
177
- throw ApiError.With('EACCES', p, 'stat');
184
+ throw ErrnoError.With('EACCES', p, 'stat');
178
185
  }
179
186
  return stats;
180
187
  }
@@ -188,10 +195,10 @@ export class AsyncStoreFS extends Async(FileSystem) {
188
195
  await this.queueDone();
189
196
  const tx = this.store.beginTransaction(), node = await this.findINode(tx, p), data = await tx.get(node.ino);
190
197
  if (!node.toStats().hasAccess(flagToMode(flag), cred)) {
191
- throw ApiError.With('EACCES', p, 'openFile');
198
+ throw ErrnoError.With('EACCES', p, 'openFile');
192
199
  }
193
200
  if (!data) {
194
- throw ApiError.With('ENOENT', p, 'openFile');
201
+ throw ErrnoError.With('ENOENT', p, 'openFile');
195
202
  }
196
203
  return new PreloadFile(this, p, flag, node.toStats(), data);
197
204
  }
@@ -204,7 +211,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
204
211
  // Check first if directory is empty.
205
212
  const list = await this.readdir(p, cred);
206
213
  if (list.length > 0) {
207
- throw ApiError.With('ENOTEMPTY', p, 'rmdir');
214
+ throw ErrnoError.With('ENOTEMPTY', p, 'rmdir');
208
215
  }
209
216
  await this.removeEntry(p, true, cred);
210
217
  }
@@ -218,7 +225,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
218
225
  const tx = this.store.beginTransaction();
219
226
  const node = await this.findINode(tx, p);
220
227
  if (!node.toStats().hasAccess(R_OK, cred)) {
221
- throw ApiError.With('EACCES', p, 'readdur');
228
+ throw ErrnoError.With('EACCES', p, 'readdur');
222
229
  }
223
230
  return Object.keys(await this.getDirListing(tx, node, p));
224
231
  }
@@ -249,16 +256,16 @@ export class AsyncStoreFS extends Async(FileSystem) {
249
256
  await this.queueDone();
250
257
  const tx = this.store.beginTransaction(), existingDir = dirname(existing), existingDirNode = await this.findINode(tx, existingDir);
251
258
  if (!existingDirNode.toStats().hasAccess(R_OK, cred)) {
252
- throw ApiError.With('EACCES', existingDir, 'link');
259
+ throw ErrnoError.With('EACCES', existingDir, 'link');
253
260
  }
254
261
  const newDir = dirname(newpath), newDirNode = await this.findINode(tx, newDir), newListing = await this.getDirListing(tx, newDirNode, newDir);
255
262
  if (!newDirNode.toStats().hasAccess(W_OK, cred)) {
256
- throw ApiError.With('EACCES', newDir, 'link');
263
+ throw ErrnoError.With('EACCES', newDir, 'link');
257
264
  }
258
265
  const ino = await this._findINode(tx, existingDir, basename(existing));
259
266
  const node = await this.getINode(tx, ino, existing);
260
267
  if (!node.toStats().hasAccess(W_OK, cred)) {
261
- throw ApiError.With('EACCES', newpath, 'link');
268
+ throw ErrnoError.With('EACCES', newpath, 'link');
262
269
  }
263
270
  node.nlink++;
264
271
  newListing[basename(newpath)] = ino;
@@ -296,7 +303,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
296
303
  async _findINode(tx, parent, filename, visited = new Set()) {
297
304
  const currentPath = join(parent, filename);
298
305
  if (visited.has(currentPath)) {
299
- 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);
300
307
  }
301
308
  visited.add(currentPath);
302
309
  if (this._cache) {
@@ -325,7 +332,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
325
332
  return id;
326
333
  }
327
334
  else {
328
- throw ApiError.With('ENOENT', resolve(parent, filename), '_findINode');
335
+ throw ErrnoError.With('ENOENT', resolve(parent, filename), '_findINode');
329
336
  }
330
337
  }
331
338
  }
@@ -342,7 +349,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
342
349
  return id;
343
350
  }
344
351
  else {
345
- throw ApiError.With('ENOENT', resolve(parent, filename), '_findINode');
352
+ throw ErrnoError.With('ENOENT', resolve(parent, filename), '_findINode');
346
353
  }
347
354
  }
348
355
  }
@@ -364,7 +371,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
364
371
  async getINode(tx, id, p) {
365
372
  const data = await tx.get(id);
366
373
  if (!data) {
367
- throw ApiError.With('ENOENT', p, 'getINode');
374
+ throw ErrnoError.With('ENOENT', p, 'getINode');
368
375
  }
369
376
  return new Inode(data.buffer);
370
377
  }
@@ -374,7 +381,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
374
381
  */
375
382
  async getDirListing(tx, inode, p) {
376
383
  if (!inode.toStats().isDirectory()) {
377
- throw ApiError.With('ENOTDIR', p, 'getDirListing');
384
+ throw ErrnoError.With('ENOTDIR', p, 'getDirListing');
378
385
  }
379
386
  const data = await tx.get(inode.ino);
380
387
  if (!data) {
@@ -383,7 +390,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
383
390
  than a directory listing. The latter should never occur unless
384
391
  the file system is corrupted.
385
392
  */
386
- throw ApiError.With('ENOENT', p, 'getDirListing');
393
+ throw ErrnoError.With('ENOENT', p, 'getDirListing');
387
394
  }
388
395
  return decodeDirListing(data);
389
396
  }
@@ -394,7 +401,7 @@ export class AsyncStoreFS extends Async(FileSystem) {
394
401
  async addNewNode(tx, data, _maxAttempts = 5) {
395
402
  if (_maxAttempts <= 0) {
396
403
  // Max retries hit. Return with an error.
397
- 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.');
398
405
  }
399
406
  // Make an attempt
400
407
  const ino = randomIno();
@@ -418,18 +425,18 @@ export class AsyncStoreFS extends Async(FileSystem) {
418
425
  const parentDir = dirname(p), fname = basename(p), parentNode = await this.findINode(tx, parentDir), dirListing = await this.getDirListing(tx, parentNode, parentDir);
419
426
  //Check that the creater has correct access
420
427
  if (!parentNode.toStats().hasAccess(W_OK, cred)) {
421
- throw ApiError.With('EACCES', p, 'commitNewFile');
428
+ throw ErrnoError.With('EACCES', p, 'commitNewFile');
422
429
  }
423
430
  // Invariant: The root always exists.
424
431
  // If we don't check this prior to taking steps below, we will create a
425
432
  // file with name '' in root should p == '/'.
426
433
  if (p === '/') {
427
- throw ApiError.With('EEXIST', p, 'commitNewFile');
434
+ throw ErrnoError.With('EEXIST', p, 'commitNewFile');
428
435
  }
429
436
  // Check if file already exists.
430
437
  if (dirListing[fname]) {
431
438
  await tx.abort();
432
- throw ApiError.With('EEXIST', p, 'commitNewFile');
439
+ throw ErrnoError.With('EEXIST', p, 'commitNewFile');
433
440
  }
434
441
  try {
435
442
  // Commit data.
@@ -468,21 +475,21 @@ export class AsyncStoreFS extends Async(FileSystem) {
468
475
  }
469
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);
470
477
  if (!parentListing[fileName]) {
471
- throw ApiError.With('ENOENT', p, 'removeEntry');
478
+ throw ErrnoError.With('ENOENT', p, 'removeEntry');
472
479
  }
473
480
  const fileIno = parentListing[fileName];
474
481
  // Get file inode.
475
482
  const fileNode = await this.getINode(tx, fileIno, p);
476
483
  if (!fileNode.toStats().hasAccess(W_OK, cred)) {
477
- throw ApiError.With('EACCES', p, 'removeEntry');
484
+ throw ErrnoError.With('EACCES', p, 'removeEntry');
478
485
  }
479
486
  // Remove from directory listing of parent.
480
487
  delete parentListing[fileName];
481
488
  if (!isDir && fileNode.toStats().isDirectory()) {
482
- throw ApiError.With('EISDIR', p, 'removeEntry');
489
+ throw ErrnoError.With('EISDIR', p, 'removeEntry');
483
490
  }
484
491
  if (isDir && !fileNode.toStats().isDirectory()) {
485
- throw ApiError.With('ENOTDIR', p, 'removeEntry');
492
+ throw ErrnoError.With('ENOTDIR', p, 'removeEntry');
486
493
  }
487
494
  try {
488
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
+ };
@@ -9,7 +9,7 @@ export declare class InMemoryStore implements SyncStore, SimpleSyncStore {
9
9
  constructor(name?: string);
10
10
  clear(): void;
11
11
  beginTransaction(): SyncTransaction;
12
- get(key: Ino): Uint8Array;
12
+ get(key: Ino): Uint8Array | undefined;
13
13
  put(key: Ino, data: Uint8Array, overwrite: boolean): boolean;
14
14
  remove(key: Ino): void;
15
15
  }
@@ -68,25 +68,25 @@ export declare class FileIndex<TData> {
68
68
  * @return The removed item,
69
69
  * or null if it did not exist.
70
70
  */
71
- remove(path: string): IndexInode<TData> | null;
71
+ remove(path: string): IndexInode<TData> | void;
72
72
  /**
73
73
  * Retrieves the directory listing of the given path.
74
74
  * @return An array of files in the given path, or 'null' if it does not exist.
75
75
  */
76
- ls(path: string): string[] | null;
76
+ ls(path: string): string[] | void;
77
77
  /**
78
78
  * Returns the inode of the given item.
79
79
  * @return Returns null if the item does not exist.
80
80
  */
81
- get(path: string): IndexInode<TData> | null;
81
+ get(path: string): IndexInode<TData> | void | null;
82
82
  }
83
83
  /**
84
84
  * Generic interface for file/directory inodes.
85
85
  * Note that Stats objects are what we use for file inodes.
86
86
  */
87
87
  export declare abstract class IndexInode<TData> {
88
- data?: TData;
89
- constructor(data?: TData);
88
+ data?: TData | undefined;
89
+ constructor(data?: TData | undefined);
90
90
  /**
91
91
  * Whether this inode is for a file
92
92
  */
@@ -135,7 +135,7 @@ export declare class IndexDirInode<TData> extends IndexInode<TData> {
135
135
  * Returns the inode for the indicated item, or null if it does not exist.
136
136
  * @param path Name of item in this directory.
137
137
  */
138
- get(path: string): IndexInode<TData> | null;
138
+ get(path: string): IndexInode<TData> | void;
139
139
  /**
140
140
  * Add the given item to the directory listing. Note that the given inode is
141
141
  * not copied, and will be mutated by the DirInode if it is a DirInode.
@@ -151,7 +151,7 @@ export declare class IndexDirInode<TData> extends IndexInode<TData> {
151
151
  * @return Returns the item
152
152
  * removed, or null if the item did not exist.
153
153
  */
154
- remove(p: string): IndexInode<TData> | null;
154
+ remove(p: string): IndexInode<TData> | void;
155
155
  }
156
156
  declare const IndexFS_base: (abstract new (...args: any[]) => {
157
157
  metadata(): import("../filesystem.js").FileSystemMetadata;
@@ -173,9 +173,6 @@ declare const IndexFS_base: (abstract new (...args: any[]) => {
173
173
  stat(path: string, cred: Cred): Promise<Stats>;
174
174
  statSync(path: string, cred: Cred): Stats;
175
175
  openFile(path: string, flag: string, cred: Cred): Promise<import("../file.js").File>;
176
- /**
177
- * Constructs a new FileIndex.
178
- */
179
176
  openFileSync(path: string, flag: string, cred: Cred): import("../file.js").File;
180
177
  readdir(path: string, cred: Cred): Promise<string[]>;
181
178
  readdirSync(path: string, cred: Cred): string[];