@zenfs/core 0.9.7 → 0.11.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.
- package/dist/backends/Index.d.ts +3 -3
- package/dist/backends/Index.js +23 -23
- package/dist/backends/backend.js +6 -5
- package/dist/backends/fetch.d.ts +84 -0
- package/dist/backends/fetch.js +170 -0
- package/dist/backends/{Locked.d.ts → locked.d.ts} +13 -13
- package/dist/backends/{Locked.js → locked.js} +54 -54
- package/dist/backends/{InMemory.d.ts → memory.d.ts} +7 -9
- package/dist/backends/memory.js +38 -0
- package/dist/backends/{Overlay.d.ts → overlay.d.ts} +12 -13
- package/dist/backends/{Overlay.js → overlay.js} +105 -110
- package/dist/backends/port/fs.d.ts +123 -0
- package/dist/backends/port/fs.js +239 -0
- package/dist/backends/port/rpc.d.ts +60 -0
- package/dist/backends/port/rpc.js +71 -0
- package/dist/backends/store/fs.d.ts +169 -0
- package/dist/backends/store/fs.js +743 -0
- package/dist/backends/store/simple.d.ts +64 -0
- package/dist/backends/store/simple.js +111 -0
- package/dist/backends/store/store.d.ts +111 -0
- package/dist/backends/store/store.js +62 -0
- package/dist/browser.min.js +4 -4
- package/dist/browser.min.js.map +4 -4
- package/dist/config.d.ts +8 -10
- package/dist/config.js +11 -11
- package/dist/emulation/async.js +6 -6
- package/dist/emulation/dir.js +2 -2
- package/dist/emulation/index.d.ts +1 -1
- package/dist/emulation/index.js +1 -1
- package/dist/emulation/path.d.ts +3 -2
- package/dist/emulation/path.js +19 -45
- package/dist/emulation/promises.d.ts +7 -12
- package/dist/emulation/promises.js +144 -146
- package/dist/emulation/shared.d.ts +5 -10
- package/dist/emulation/shared.js +9 -9
- package/dist/emulation/streams.js +3 -3
- package/dist/emulation/sync.js +25 -25
- package/dist/{ApiError.d.ts → error.d.ts} +13 -15
- package/dist/error.js +291 -0
- package/dist/file.d.ts +2 -0
- package/dist/file.js +11 -5
- package/dist/filesystem.d.ts +3 -3
- package/dist/filesystem.js +41 -44
- package/dist/index.d.ts +7 -6
- package/dist/index.js +7 -6
- package/dist/inode.d.ts +1 -1
- package/dist/mutex.js +2 -1
- package/dist/utils.d.ts +8 -7
- package/dist/utils.js +11 -12
- package/package.json +3 -3
- package/readme.md +17 -9
- package/src/backends/Index.ts +23 -23
- package/src/backends/backend.ts +8 -7
- package/src/backends/fetch.ts +229 -0
- package/src/backends/{Locked.ts → locked.ts} +55 -55
- package/src/backends/memory.ts +44 -0
- package/src/backends/{Overlay.ts → overlay.ts} +108 -114
- package/src/backends/port/fs.ts +306 -0
- package/src/backends/port/readme.md +59 -0
- package/src/backends/port/rpc.ts +144 -0
- package/src/backends/store/fs.ts +881 -0
- package/src/backends/store/readme.md +9 -0
- package/src/backends/store/simple.ts +144 -0
- package/src/backends/store/store.ts +164 -0
- package/src/config.ts +21 -25
- package/src/emulation/async.ts +6 -6
- package/src/emulation/dir.ts +2 -2
- package/src/emulation/index.ts +1 -1
- package/src/emulation/path.ts +25 -49
- package/src/emulation/promises.ts +150 -159
- package/src/emulation/shared.ts +13 -15
- package/src/emulation/streams.ts +3 -3
- package/src/emulation/sync.ts +28 -28
- package/src/{ApiError.ts → error.ts} +89 -90
- package/src/file.ts +13 -5
- package/src/filesystem.ts +44 -47
- package/src/index.ts +7 -6
- package/src/inode.ts +1 -1
- package/src/mutex.ts +3 -1
- package/src/utils.ts +16 -18
- package/tsconfig.json +2 -2
- package/dist/ApiError.js +0 -292
- package/dist/backends/AsyncStore.d.ts +0 -204
- package/dist/backends/AsyncStore.js +0 -509
- package/dist/backends/InMemory.js +0 -49
- package/dist/backends/SyncStore.d.ts +0 -213
- package/dist/backends/SyncStore.js +0 -445
- package/src/backends/AsyncStore.ts +0 -655
- package/src/backends/InMemory.ts +0 -56
- package/src/backends/SyncStore.ts +0 -589
package/dist/backends/Index.d.ts
CHANGED
|
@@ -147,11 +147,11 @@ export declare class IndexDirInode<TData> extends IndexInode<TData> {
|
|
|
147
147
|
add(path: string, inode: IndexInode<TData>): boolean;
|
|
148
148
|
/**
|
|
149
149
|
* Removes the given item from the directory listing.
|
|
150
|
-
* @param
|
|
150
|
+
* @param path Name of item to remove from the directory listing.
|
|
151
151
|
* @return Returns the item
|
|
152
152
|
* removed, or null if the item did not exist.
|
|
153
153
|
*/
|
|
154
|
-
remove(
|
|
154
|
+
remove(path: string): IndexInode<TData> | void;
|
|
155
155
|
}
|
|
156
156
|
declare const IndexFS_base: (abstract new (...args: any[]) => {
|
|
157
157
|
metadata(): import("../filesystem.js").FileSystemMetadata;
|
|
@@ -169,7 +169,7 @@ declare const IndexFS_base: (abstract new (...args: any[]) => {
|
|
|
169
169
|
linkSync(srcpath: string, dstpath: string, cred: Cred): void;
|
|
170
170
|
sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
|
|
171
171
|
syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
|
|
172
|
-
ready(): Promise<
|
|
172
|
+
ready(): Promise<void>;
|
|
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>;
|
package/dist/backends/Index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
94
|
+
throw new ErrnoError(Errno.EINVAL, 'Inode must be specified', path, 'FileIndex.add');
|
|
95
95
|
}
|
|
96
96
|
if (!path.startsWith('/')) {
|
|
97
|
-
throw new
|
|
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)) {
|
|
@@ -287,16 +287,16 @@ export class IndexDirInode extends IndexInode {
|
|
|
287
287
|
}
|
|
288
288
|
/**
|
|
289
289
|
* Removes the given item from the directory listing.
|
|
290
|
-
* @param
|
|
290
|
+
* @param path Name of item to remove from the directory listing.
|
|
291
291
|
* @return Returns the item
|
|
292
292
|
* removed, or null if the item did not exist.
|
|
293
293
|
*/
|
|
294
|
-
remove(
|
|
295
|
-
const item = this._listing.get(
|
|
294
|
+
remove(path) {
|
|
295
|
+
const item = this._listing.get(path);
|
|
296
296
|
if (!item) {
|
|
297
297
|
return;
|
|
298
298
|
}
|
|
299
|
-
this._listing.delete(
|
|
299
|
+
this._listing.delete(path);
|
|
300
300
|
return item;
|
|
301
301
|
}
|
|
302
302
|
}
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
342
|
+
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
343
343
|
}
|
|
344
344
|
if (!inode.toStats().hasAccess(flagToMode(flag), cred)) {
|
|
345
|
-
throw
|
|
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
|
|
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
|
|
361
|
+
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
362
362
|
}
|
|
363
363
|
if (!inode.toStats().hasAccess(flagToMode(flag), cred)) {
|
|
364
|
-
throw
|
|
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
|
|
376
|
+
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
377
377
|
}
|
|
378
378
|
if (inode.isDirectory()) {
|
|
379
379
|
return inode.listing;
|
|
380
380
|
}
|
|
381
|
-
throw
|
|
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
|
|
387
|
+
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
388
388
|
}
|
|
389
389
|
if (inode.isDirectory()) {
|
|
390
390
|
return inode.listing;
|
|
391
391
|
}
|
|
392
|
-
throw
|
|
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
|
|
405
|
+
throw ErrnoError.With('ENOSYS', path, 'AsyncIndexFS.statFileInodeSync');
|
|
406
406
|
}
|
|
407
407
|
openFileInodeSync(inode, path, flag) {
|
|
408
|
-
throw
|
|
408
|
+
throw ErrnoError.With('ENOSYS', path, 'AsyncIndexFS.openFileInodeSync');
|
|
409
409
|
}
|
|
410
410
|
}
|
package/dist/backends/backend.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ErrnoError, Errno } from '../error.js';
|
|
2
2
|
import { levenshtein } from '../utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
@@ -12,7 +12,7 @@ export function isBackend(arg) {
|
|
|
12
12
|
*/
|
|
13
13
|
export async function checkOptions(backend, opts) {
|
|
14
14
|
if (typeof opts != 'object' || opts === null) {
|
|
15
|
-
throw new
|
|
15
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid options');
|
|
16
16
|
}
|
|
17
17
|
// Check for required options.
|
|
18
18
|
for (const [optName, opt] of Object.entries(backend.options)) {
|
|
@@ -31,12 +31,12 @@ export async function checkOptions(backend, opts) {
|
|
|
31
31
|
})
|
|
32
32
|
.filter(o => o.distance < 5)
|
|
33
33
|
.sort((a, b) => a.distance - b.distance);
|
|
34
|
-
throw new
|
|
34
|
+
throw new ErrnoError(Errno.EINVAL, `${backend.name}: Required option '${optName}' not provided.${incorrectOptions.length > 0 ? ` You provided '${incorrectOptions[0].str}', did you mean '${optName}'.` : ''}`);
|
|
35
35
|
}
|
|
36
36
|
// Option provided, check type.
|
|
37
37
|
const typeMatches = Array.isArray(opt.type) ? opt.type.indexOf(typeof providedValue) != -1 : typeof providedValue == opt.type;
|
|
38
38
|
if (!typeMatches) {
|
|
39
|
-
throw new
|
|
39
|
+
throw new ErrnoError(Errno.EINVAL, `${backend.name}: Value provided for option ${optName} is not the proper type. Expected ${Array.isArray(opt.type) ? `one of {${opt.type.join(', ')}}` : opt.type}, but received ${typeof providedValue}`);
|
|
40
40
|
}
|
|
41
41
|
if (opt.validator) {
|
|
42
42
|
await opt.validator(providedValue);
|
|
@@ -47,7 +47,8 @@ export async function checkOptions(backend, opts) {
|
|
|
47
47
|
export async function createBackend(backend, options = {}) {
|
|
48
48
|
await checkOptions(backend, options);
|
|
49
49
|
const fs = backend.create(options);
|
|
50
|
-
|
|
50
|
+
await fs.ready();
|
|
51
|
+
return fs;
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
53
54
|
* @internal
|
|
@@ -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<void>;
|
|
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(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>;
|
|
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,170 @@
|
|
|
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(path, type) {
|
|
12
|
+
const response = await fetch(path).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(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.
|
|
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
|
+
}
|
|
75
|
+
constructor({ index = 'index.json', baseUrl = '' }) {
|
|
76
|
+
super({});
|
|
77
|
+
// prefix url must end in a directory separator.
|
|
78
|
+
if (baseUrl.at(-1) != '/') {
|
|
79
|
+
baseUrl += '/';
|
|
80
|
+
}
|
|
81
|
+
this.prefixUrl = baseUrl;
|
|
82
|
+
this._init = this._initialize(index);
|
|
83
|
+
}
|
|
84
|
+
metadata() {
|
|
85
|
+
return {
|
|
86
|
+
...super.metadata(),
|
|
87
|
+
name: FetchFS.name,
|
|
88
|
+
readonly: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
empty() {
|
|
92
|
+
for (const file of this._index.files()) {
|
|
93
|
+
delete file.data.fileData;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Special function: Preload the given file into the index.
|
|
98
|
+
* @param path
|
|
99
|
+
* @param buffer
|
|
100
|
+
*/
|
|
101
|
+
preloadFile(path, buffer) {
|
|
102
|
+
const inode = this._index.get(path);
|
|
103
|
+
if (!inode) {
|
|
104
|
+
throw ErrnoError.With('ENOENT', path, 'preloadFile');
|
|
105
|
+
}
|
|
106
|
+
if (!inode.isFile()) {
|
|
107
|
+
throw ErrnoError.With('EISDIR', path, 'preloadFile');
|
|
108
|
+
}
|
|
109
|
+
const stats = inode.data;
|
|
110
|
+
stats.size = buffer.length;
|
|
111
|
+
stats.fileData = buffer;
|
|
112
|
+
}
|
|
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.
|
|
124
|
+
if (stats.fileData) {
|
|
125
|
+
return new NoSyncFile(this, path, flag, new Stats(stats), stats.fileData);
|
|
126
|
+
}
|
|
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;
|
|
131
|
+
stats.fileData = data;
|
|
132
|
+
return new NoSyncFile(this, path, flag, new Stats(stats), data);
|
|
133
|
+
}
|
|
134
|
+
_getRemotePath(filePath) {
|
|
135
|
+
if (filePath.charAt(0) === '/') {
|
|
136
|
+
filePath = filePath.slice(1);
|
|
137
|
+
}
|
|
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));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export const Fetch = {
|
|
151
|
+
name: 'Fetch',
|
|
152
|
+
options: {
|
|
153
|
+
index: {
|
|
154
|
+
type: ['string', 'object'],
|
|
155
|
+
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`.',
|
|
157
|
+
},
|
|
158
|
+
baseUrl: {
|
|
159
|
+
type: 'string',
|
|
160
|
+
required: false,
|
|
161
|
+
description: 'Used as the URL prefix for fetched files. Default: Fetch files relative to the index.',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
isAvailable() {
|
|
165
|
+
return typeof globalThis.fetch == 'function';
|
|
166
|
+
},
|
|
167
|
+
create(options) {
|
|
168
|
+
return new FetchFS(options);
|
|
169
|
+
},
|
|
170
|
+
};
|
|
@@ -16,26 +16,26 @@ export declare class LockedFS<FS extends FileSystem> implements FileSystem {
|
|
|
16
16
|
readonly fs: FS;
|
|
17
17
|
private _mu;
|
|
18
18
|
constructor(fs: FS);
|
|
19
|
-
ready(): Promise<
|
|
19
|
+
ready(): Promise<void>;
|
|
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(
|
|
24
|
-
statSync(
|
|
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
|
-
unlink(
|
|
30
|
-
unlinkSync(
|
|
31
|
-
rmdir(
|
|
32
|
-
rmdirSync(
|
|
33
|
-
mkdir(
|
|
34
|
-
mkdirSync(
|
|
35
|
-
readdir(
|
|
36
|
-
readdirSync(
|
|
37
|
-
exists(
|
|
38
|
-
existsSync(
|
|
29
|
+
unlink(path: string, cred: Cred): Promise<void>;
|
|
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>;
|