@zenfs/dom 0.0.1

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.
@@ -0,0 +1,234 @@
1
+ var _a;
2
+ import { AsyncKeyValueFileSystem } from '@zenfs/core/backends/AsyncStore.js';
3
+ import { ApiError, ErrorCode } from '@zenfs/core/ApiError.js';
4
+ import { Buffer } from 'buffer';
5
+ import { CreateBackend } from '@zenfs/core/backends/backend.js';
6
+ /**
7
+ * Get the indexedDB constructor for the current browser.
8
+ * @hidden
9
+ */
10
+ const indexedDB = (() => {
11
+ try {
12
+ return globalThis.indexedDB || globalThis.mozIndexedDB || globalThis.webkitIndexedDB || globalThis.msIndexedDB;
13
+ }
14
+ catch (_b) {
15
+ return null;
16
+ }
17
+ })();
18
+ /**
19
+ * Converts a DOMException or a DOMError from an IndexedDB event into a
20
+ * standardized ZenFS API error.
21
+ * @hidden
22
+ */
23
+ function convertError(e, message = e.toString()) {
24
+ switch (e.name) {
25
+ case 'NotFoundError':
26
+ return new ApiError(ErrorCode.ENOENT, message);
27
+ case 'QuotaExceededError':
28
+ return new ApiError(ErrorCode.ENOSPC, message);
29
+ default:
30
+ // The rest do not seem to map cleanly to standard error codes.
31
+ return new ApiError(ErrorCode.EIO, message);
32
+ }
33
+ }
34
+ /**
35
+ * Produces a new onerror handler for IDB. Our errors are always fatal, so we
36
+ * handle them generically: Call the user-supplied callback with a translated
37
+ * version of the error, and let the error bubble up.
38
+ * @hidden
39
+ */
40
+ function onErrorHandler(cb, code = ErrorCode.EIO, message = null) {
41
+ return function (e) {
42
+ // Prevent the error from canceling the transaction.
43
+ e.preventDefault();
44
+ cb(new ApiError(code, message !== null ? message : undefined));
45
+ };
46
+ }
47
+ /**
48
+ * @hidden
49
+ */
50
+ export class IndexedDBROTransaction {
51
+ constructor(tx, store) {
52
+ this.tx = tx;
53
+ this.store = store;
54
+ }
55
+ get(key) {
56
+ return new Promise((resolve, reject) => {
57
+ try {
58
+ const r = this.store.get(key);
59
+ r.onerror = onErrorHandler(reject);
60
+ r.onsuccess = event => {
61
+ // IDB returns the value 'undefined' when you try to get keys that
62
+ // don't exist. The caller expects this behavior.
63
+ const result = event.target.result;
64
+ if (result === undefined) {
65
+ resolve(result);
66
+ }
67
+ else {
68
+ // IDB data is stored as an ArrayBuffer
69
+ resolve(Buffer.from(result));
70
+ }
71
+ };
72
+ }
73
+ catch (e) {
74
+ reject(convertError(e));
75
+ }
76
+ });
77
+ }
78
+ }
79
+ /**
80
+ * @hidden
81
+ */
82
+ export class IndexedDBRWTransaction extends IndexedDBROTransaction {
83
+ constructor(tx, store) {
84
+ super(tx, store);
85
+ }
86
+ /**
87
+ * @todo return false when add has a key conflict (no error)
88
+ */
89
+ put(key, data, overwrite) {
90
+ return new Promise((resolve, reject) => {
91
+ try {
92
+ const r = overwrite ? this.store.put(data, key) : this.store.add(data, key);
93
+ r.onerror = onErrorHandler(reject);
94
+ r.onsuccess = () => {
95
+ resolve(true);
96
+ };
97
+ }
98
+ catch (e) {
99
+ reject(convertError(e));
100
+ }
101
+ });
102
+ }
103
+ del(key) {
104
+ return new Promise((resolve, reject) => {
105
+ try {
106
+ const r = this.store.delete(key);
107
+ r.onerror = onErrorHandler(reject);
108
+ r.onsuccess = () => {
109
+ resolve();
110
+ };
111
+ }
112
+ catch (e) {
113
+ reject(convertError(e));
114
+ }
115
+ });
116
+ }
117
+ commit() {
118
+ return new Promise(resolve => {
119
+ // Return to the event loop to commit the transaction.
120
+ setTimeout(resolve, 0);
121
+ });
122
+ }
123
+ abort() {
124
+ return new Promise((resolve, reject) => {
125
+ try {
126
+ this.tx.abort();
127
+ resolve();
128
+ }
129
+ catch (e) {
130
+ reject(convertError(e));
131
+ }
132
+ });
133
+ }
134
+ }
135
+ export class IndexedDBStore {
136
+ static Create(storeName, indexedDB) {
137
+ return new Promise((resolve, reject) => {
138
+ const openReq = indexedDB.open(storeName, 1);
139
+ openReq.onupgradeneeded = event => {
140
+ const db = event.target.result;
141
+ // Huh. This should never happen; we're at version 1. Why does another
142
+ // database exist?
143
+ if (db.objectStoreNames.contains(storeName)) {
144
+ db.deleteObjectStore(storeName);
145
+ }
146
+ db.createObjectStore(storeName);
147
+ };
148
+ openReq.onsuccess = event => {
149
+ resolve(new IndexedDBStore(event.target.result, storeName));
150
+ };
151
+ openReq.onerror = onErrorHandler(reject, ErrorCode.EACCES);
152
+ });
153
+ }
154
+ constructor(db, storeName) {
155
+ this.db = db;
156
+ this.storeName = storeName;
157
+ }
158
+ name() {
159
+ return IndexedDBFileSystem.Name + ' - ' + this.storeName;
160
+ }
161
+ clear() {
162
+ return new Promise((resolve, reject) => {
163
+ try {
164
+ const tx = this.db.transaction(this.storeName, 'readwrite'), objectStore = tx.objectStore(this.storeName), r = objectStore.clear();
165
+ r.onsuccess = () => {
166
+ // Use setTimeout to commit transaction.
167
+ setTimeout(resolve, 0);
168
+ };
169
+ r.onerror = onErrorHandler(reject);
170
+ }
171
+ catch (e) {
172
+ reject(convertError(e));
173
+ }
174
+ });
175
+ }
176
+ beginTransaction(type = 'readonly') {
177
+ const tx = this.db.transaction(this.storeName, type), objectStore = tx.objectStore(this.storeName);
178
+ if (type === 'readwrite') {
179
+ return new IndexedDBRWTransaction(tx, objectStore);
180
+ }
181
+ else if (type === 'readonly') {
182
+ return new IndexedDBROTransaction(tx, objectStore);
183
+ }
184
+ else {
185
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid transaction type.');
186
+ }
187
+ }
188
+ }
189
+ /**
190
+ * A file system that uses the IndexedDB key value file system.
191
+ */
192
+ export class IndexedDBFileSystem extends AsyncKeyValueFileSystem {
193
+ static isAvailable(idbFactory = globalThis.indexedDB) {
194
+ try {
195
+ if (!(idbFactory instanceof IDBFactory)) {
196
+ return false;
197
+ }
198
+ const req = indexedDB.open('__zenfs_test__');
199
+ if (!req) {
200
+ return false;
201
+ }
202
+ }
203
+ catch (e) {
204
+ return false;
205
+ }
206
+ }
207
+ constructor({ cacheSize = 100, storeName = 'zenfs', idbFactory = globalThis.indexedDB }) {
208
+ super(cacheSize);
209
+ this._ready = IndexedDBStore.Create(storeName, idbFactory).then(store => {
210
+ this.init(store);
211
+ return this;
212
+ });
213
+ }
214
+ }
215
+ _a = IndexedDBFileSystem;
216
+ IndexedDBFileSystem.Name = 'IndexedDB';
217
+ IndexedDBFileSystem.Create = CreateBackend.bind(_a);
218
+ IndexedDBFileSystem.Options = {
219
+ storeName: {
220
+ type: 'string',
221
+ optional: true,
222
+ description: 'The name of this file system. You can have multiple IndexedDB file systems operating at once, but each must have a different name.',
223
+ },
224
+ cacheSize: {
225
+ type: 'number',
226
+ optional: true,
227
+ description: 'The size of the inode cache. Defaults to 100. A size of 0 or below disables caching.',
228
+ },
229
+ idbFactory: {
230
+ type: 'object',
231
+ optional: true,
232
+ description: 'The IDBFactory to use. Defaults to globalThis.indexedDB.',
233
+ },
234
+ };
@@ -0,0 +1,40 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { SyncKeyValueStore, SimpleSyncStore, SyncKeyValueFileSystem, SyncKeyValueRWTransaction } from '@zenfs/core/backends/SyncStore.js';
3
+ import { type BackendOptions } from '@zenfs/core/backends/backend.js';
4
+ /**
5
+ * A synchronous key-value store backed by Storage.
6
+ */
7
+ export declare class StorageStore implements SyncKeyValueStore, SimpleSyncStore {
8
+ protected _storage: any;
9
+ name(): string;
10
+ constructor(_storage: any);
11
+ clear(): void;
12
+ beginTransaction(type: string): SyncKeyValueRWTransaction;
13
+ get(key: string): Buffer | undefined;
14
+ put(key: string, data: Buffer, overwrite: boolean): boolean;
15
+ del(key: string): void;
16
+ }
17
+ export declare namespace StorageFileSystem {
18
+ /**
19
+ * Options to pass to the StorageFileSystem
20
+ */
21
+ interface Options {
22
+ /**
23
+ * The Storage to use. Defaults to globalThis.localStorage.
24
+ */
25
+ storage: Storage;
26
+ }
27
+ }
28
+ /**
29
+ * A synchronous file system backed by a `Storage` (e.g. localStorage).
30
+ */
31
+ export declare class StorageFileSystem extends SyncKeyValueFileSystem {
32
+ static readonly Name = "Storage";
33
+ static Create: any;
34
+ static readonly Options: BackendOptions;
35
+ static isAvailable(storage?: Storage): boolean;
36
+ /**
37
+ * Creates a new Storage file system using the contents of `Storage`.
38
+ */
39
+ constructor({ storage }: StorageFileSystem.Options);
40
+ }
@@ -0,0 +1,75 @@
1
+ var _a;
2
+ import { SyncKeyValueFileSystem, SimpleSyncRWTransaction } from '@zenfs/core/backends/SyncStore.js';
3
+ import { ApiError, ErrorCode } from '@zenfs/core/ApiError.js';
4
+ import { CreateBackend } from '@zenfs/core/backends/backend.js';
5
+ import { Buffer } from 'buffer';
6
+ /**
7
+ * A synchronous key-value store backed by Storage.
8
+ */
9
+ export class StorageStore {
10
+ name() {
11
+ return StorageFileSystem.Name;
12
+ }
13
+ constructor(_storage) {
14
+ this._storage = _storage;
15
+ }
16
+ clear() {
17
+ this._storage.clear();
18
+ }
19
+ beginTransaction(type) {
20
+ // No need to differentiate.
21
+ return new SimpleSyncRWTransaction(this);
22
+ }
23
+ get(key) {
24
+ const data = this._storage.getItem(key);
25
+ if (typeof data != 'string') {
26
+ return;
27
+ }
28
+ return Buffer.from(data);
29
+ }
30
+ put(key, data, overwrite) {
31
+ try {
32
+ if (!overwrite && this._storage.getItem(key) !== null) {
33
+ // Don't want to overwrite the key!
34
+ return false;
35
+ }
36
+ this._storage.setItem(key, data.toString());
37
+ return true;
38
+ }
39
+ catch (e) {
40
+ throw new ApiError(ErrorCode.ENOSPC, 'Storage is full.');
41
+ }
42
+ }
43
+ del(key) {
44
+ try {
45
+ this._storage.removeItem(key);
46
+ }
47
+ catch (e) {
48
+ throw new ApiError(ErrorCode.EIO, 'Unable to delete key ' + key + ': ' + e);
49
+ }
50
+ }
51
+ }
52
+ /**
53
+ * A synchronous file system backed by a `Storage` (e.g. localStorage).
54
+ */
55
+ export class StorageFileSystem extends SyncKeyValueFileSystem {
56
+ static isAvailable(storage = globalThis.localStorage) {
57
+ return storage instanceof Storage;
58
+ }
59
+ /**
60
+ * Creates a new Storage file system using the contents of `Storage`.
61
+ */
62
+ constructor({ storage = globalThis.localStorage }) {
63
+ super({ store: new StorageStore(storage) });
64
+ }
65
+ }
66
+ _a = StorageFileSystem;
67
+ StorageFileSystem.Name = 'Storage';
68
+ StorageFileSystem.Create = CreateBackend.bind(_a);
69
+ StorageFileSystem.Options = {
70
+ storage: {
71
+ type: 'object',
72
+ optional: true,
73
+ description: 'The Storage to use. Defaults to globalThis.localStorage.',
74
+ },
75
+ };
@@ -0,0 +1,79 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { BaseFileSystem, FileContents, FileSystemMetadata } from '@zenfs/core/filesystem.js';
3
+ import { File, FileFlag } from '@zenfs/core/file.js';
4
+ import { Stats } from '@zenfs/core/stats.js';
5
+ import { Cred } from '@zenfs/core/cred.js';
6
+ import { type BackendOptions } from '@zenfs/core/backends/backend.js';
7
+ export declare namespace WorkerFS {
8
+ interface Options {
9
+ /**
10
+ * The target worker that you want to connect to, or the current worker if in a worker context.
11
+ */
12
+ worker: Worker;
13
+ }
14
+ }
15
+ /**
16
+ * WorkerFS lets you access a ZenFS instance that is running in a different
17
+ * JavaScript context (e.g. access ZenFS in one of your WebWorkers, or
18
+ * access ZenFS running on the main page from a WebWorker).
19
+ *
20
+ * For example, to have a WebWorker access files in the main browser thread,
21
+ * do the following:
22
+ *
23
+ * MAIN BROWSER THREAD:
24
+ *
25
+ * ```javascript
26
+ * // Listen for remote file system requests.
27
+ * ZenFS.Backend.WorkerFS.attachRemoteListener(webWorkerObject);
28
+ * ```
29
+ *
30
+ * WEBWORKER THREAD:
31
+ *
32
+ * ```javascript
33
+ * // Set the remote file system as the root file system.
34
+ * ZenFS.configure({ fs: "WorkerFS", options: { worker: self }}, function(e) {
35
+ * // Ready!
36
+ * });
37
+ * ```
38
+ *
39
+ * Note that synchronous operations are not permitted on the WorkerFS, regardless
40
+ * of the configuration option of the remote FS.
41
+ */
42
+ export declare class WorkerFS extends BaseFileSystem {
43
+ static readonly Name = "WorkerFS";
44
+ static Create: any;
45
+ static readonly Options: BackendOptions;
46
+ static isAvailable(): boolean;
47
+ private _worker;
48
+ private _currentID;
49
+ private _requests;
50
+ private _isInitialized;
51
+ private _metadata;
52
+ /**
53
+ * Constructs a new WorkerFS instance that connects with ZenFS running on
54
+ * the specified worker.
55
+ */
56
+ constructor({ worker }: WorkerFS.Options);
57
+ get metadata(): FileSystemMetadata;
58
+ private _rpc;
59
+ rename(oldPath: string, newPath: string, cred: Cred): Promise<void>;
60
+ stat(p: string, cred: Cred): Promise<Stats>;
61
+ open(p: string, flag: FileFlag, mode: number, cred: Cred): Promise<File>;
62
+ unlink(p: string, cred: Cred): Promise<void>;
63
+ rmdir(p: string, cred: Cred): Promise<void>;
64
+ mkdir(p: string, mode: number, cred: Cred): Promise<void>;
65
+ readdir(p: string, cred: Cred): Promise<string[]>;
66
+ exists(p: string, cred: Cred): Promise<boolean>;
67
+ realpath(p: string, cred: Cred): Promise<string>;
68
+ truncate(p: string, len: number, cred: Cred): Promise<void>;
69
+ readFile(fname: string, encoding: BufferEncoding, flag: FileFlag, cred: Cred): Promise<FileContents>;
70
+ writeFile(fname: string, data: FileContents, encoding: BufferEncoding, flag: FileFlag, mode: number, cred: Cred): Promise<void>;
71
+ appendFile(fname: string, data: FileContents, encoding: BufferEncoding, flag: FileFlag, mode: number, cred: Cred): Promise<void>;
72
+ chmod(p: string, mode: number, cred: Cred): Promise<void>;
73
+ chown(p: string, new_uid: number, new_gid: number, cred: Cred): Promise<void>;
74
+ utimes(p: string, atime: Date, mtime: Date, cred: Cred): Promise<void>;
75
+ link(srcpath: string, dstpath: string, cred: Cred): Promise<void>;
76
+ symlink(srcpath: string, dstpath: string, type: string, cred: Cred): Promise<void>;
77
+ readlink(p: string, cred: Cred): Promise<string>;
78
+ syncClose(method: string, fd: File): Promise<void>;
79
+ }
@@ -0,0 +1,169 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var _a;
11
+ import { BaseFileSystem } from '@zenfs/core/filesystem.js';
12
+ import { ApiError, ErrorCode } from '@zenfs/core/ApiError.js';
13
+ import { CreateBackend } from '@zenfs/core/backends/backend.js';
14
+ function isRPCMessage(arg) {
15
+ return typeof arg == 'object' && 'isBFS' in arg && !!arg.isBFS;
16
+ }
17
+ /**
18
+ * WorkerFS lets you access a ZenFS instance that is running in a different
19
+ * JavaScript context (e.g. access ZenFS in one of your WebWorkers, or
20
+ * access ZenFS running on the main page from a WebWorker).
21
+ *
22
+ * For example, to have a WebWorker access files in the main browser thread,
23
+ * do the following:
24
+ *
25
+ * MAIN BROWSER THREAD:
26
+ *
27
+ * ```javascript
28
+ * // Listen for remote file system requests.
29
+ * ZenFS.Backend.WorkerFS.attachRemoteListener(webWorkerObject);
30
+ * ```
31
+ *
32
+ * WEBWORKER THREAD:
33
+ *
34
+ * ```javascript
35
+ * // Set the remote file system as the root file system.
36
+ * ZenFS.configure({ fs: "WorkerFS", options: { worker: self }}, function(e) {
37
+ * // Ready!
38
+ * });
39
+ * ```
40
+ *
41
+ * Note that synchronous operations are not permitted on the WorkerFS, regardless
42
+ * of the configuration option of the remote FS.
43
+ */
44
+ export class WorkerFS extends BaseFileSystem {
45
+ static isAvailable() {
46
+ return typeof importScripts !== 'undefined' || typeof Worker !== 'undefined';
47
+ }
48
+ /**
49
+ * Constructs a new WorkerFS instance that connects with ZenFS running on
50
+ * the specified worker.
51
+ */
52
+ constructor({ worker }) {
53
+ super();
54
+ this._currentID = 0;
55
+ this._requests = new Map();
56
+ this._isInitialized = false;
57
+ this._worker = worker;
58
+ this._worker.onmessage = (event) => {
59
+ if (!isRPCMessage(event.data)) {
60
+ return;
61
+ }
62
+ const { id, method, value } = event.data;
63
+ if (method === 'metadata') {
64
+ this._metadata = value;
65
+ this._isInitialized = true;
66
+ return;
67
+ }
68
+ const { resolve, reject } = this._requests.get(id);
69
+ this._requests.delete(id);
70
+ if (value instanceof Error || value instanceof ApiError) {
71
+ reject(value);
72
+ return;
73
+ }
74
+ resolve(value);
75
+ };
76
+ }
77
+ get metadata() {
78
+ return Object.assign(Object.assign(Object.assign({}, super.metadata), this._metadata), { name: _a.Name, synchronous: false });
79
+ }
80
+ _rpc(method, ...args) {
81
+ return __awaiter(this, void 0, void 0, function* () {
82
+ return new Promise((resolve, reject) => {
83
+ const id = this._currentID++;
84
+ this._requests.set(id, { resolve, reject });
85
+ this._worker.postMessage({
86
+ isBFS: true,
87
+ id,
88
+ method,
89
+ args,
90
+ });
91
+ });
92
+ });
93
+ }
94
+ rename(oldPath, newPath, cred) {
95
+ return this._rpc('rename', oldPath, newPath, cred);
96
+ }
97
+ stat(p, cred) {
98
+ return this._rpc('stat', p, cred);
99
+ }
100
+ open(p, flag, mode, cred) {
101
+ return this._rpc('open', p, flag, mode, cred);
102
+ }
103
+ unlink(p, cred) {
104
+ return this._rpc('unlink', p, cred);
105
+ }
106
+ rmdir(p, cred) {
107
+ return this._rpc('rmdir', p, cred);
108
+ }
109
+ mkdir(p, mode, cred) {
110
+ return this._rpc('mkdir', p, mode, cred);
111
+ }
112
+ readdir(p, cred) {
113
+ return this._rpc('readdir', p, cred);
114
+ }
115
+ exists(p, cred) {
116
+ return this._rpc('exists', p, cred);
117
+ }
118
+ realpath(p, cred) {
119
+ return this._rpc('realpath', p, cred);
120
+ }
121
+ truncate(p, len, cred) {
122
+ return this._rpc('truncate', p, len, cred);
123
+ }
124
+ readFile(fname, encoding, flag, cred) {
125
+ return this._rpc('readFile', fname, encoding, flag, cred);
126
+ }
127
+ writeFile(fname, data, encoding, flag, mode, cred) {
128
+ return this._rpc('writeFile', fname, data, encoding, flag, mode, cred);
129
+ }
130
+ appendFile(fname, data, encoding, flag, mode, cred) {
131
+ return this._rpc('appendFile', fname, data, encoding, flag, mode, cred);
132
+ }
133
+ chmod(p, mode, cred) {
134
+ return this._rpc('chmod', p, mode, cred);
135
+ }
136
+ chown(p, new_uid, new_gid, cred) {
137
+ return this._rpc('chown', p, new_uid, new_gid, cred);
138
+ }
139
+ utimes(p, atime, mtime, cred) {
140
+ return this._rpc('utimes', p, atime, mtime, cred);
141
+ }
142
+ link(srcpath, dstpath, cred) {
143
+ return this._rpc('link', srcpath, dstpath, cred);
144
+ }
145
+ symlink(srcpath, dstpath, type, cred) {
146
+ return this._rpc('symlink', srcpath, dstpath, type, cred);
147
+ }
148
+ readlink(p, cred) {
149
+ return this._rpc('readlink', p, cred);
150
+ }
151
+ syncClose(method, fd) {
152
+ return this._rpc('syncClose', method, fd);
153
+ }
154
+ }
155
+ _a = WorkerFS;
156
+ WorkerFS.Name = 'WorkerFS';
157
+ WorkerFS.Create = CreateBackend.bind(_a);
158
+ WorkerFS.Options = {
159
+ worker: {
160
+ type: 'object',
161
+ description: 'The target worker that you want to connect to, or the current worker if in a worker context.',
162
+ validator: (v) => __awaiter(void 0, void 0, void 0, function* () {
163
+ // Check for a `postMessage` function.
164
+ if (typeof (v === null || v === void 0 ? void 0 : v.postMessage) != 'function') {
165
+ throw new ApiError(ErrorCode.EINVAL, `option must be a Web Worker instance.`);
166
+ }
167
+ }),
168
+ },
169
+ };