@zenfs/dom 0.2.0 → 0.2.2

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,5 @@
1
+ import { ApiError } from '@zenfs/core';
2
+ /**
3
+ * Handles converting errors, then rethrowing them
4
+ */
5
+ export declare function convertException(ex: Error | ApiError | DOMException, path?: string, syscall?: string): ApiError;
package/dist/utils.js ADDED
@@ -0,0 +1,67 @@
1
+ import { ApiError, ErrorCode } from '@zenfs/core';
2
+ /**
3
+ * Converts a DOMException into an ErrorCode
4
+ * @see https://developer.mozilla.org/Web/API/DOMException
5
+ */
6
+ function errnoForDOMException(ex) {
7
+ switch (ex.name) {
8
+ case 'IndexSizeError':
9
+ case 'HierarchyRequestError':
10
+ case 'InvalidCharacterError':
11
+ case 'InvalidStateError':
12
+ case 'SyntaxError':
13
+ case 'NamespaceError':
14
+ case 'TypeMismatchError':
15
+ case 'ConstraintError':
16
+ case 'VersionError':
17
+ case 'URLMismatchError':
18
+ case 'InvalidNodeTypeError':
19
+ return 'EINVAL';
20
+ case 'WrongDocumentError':
21
+ return 'EXDEV';
22
+ case 'NoModificationAllowedError':
23
+ case 'InvalidModificationError':
24
+ case 'InvalidAccessError':
25
+ case 'SecurityError':
26
+ case 'NotAllowedError':
27
+ return 'EACCES';
28
+ case 'NotFoundError':
29
+ return 'ENOENT';
30
+ case 'NotSupportedError':
31
+ return 'ENOTSUP';
32
+ case 'InUseAttributeError':
33
+ return 'EBUSY';
34
+ case 'NetworkError':
35
+ return 'ENETDOWN';
36
+ case 'AbortError':
37
+ return 'EINTR';
38
+ case 'QuotaExceededError':
39
+ return 'ENOSPC';
40
+ case 'TimeoutError':
41
+ return 'ETIMEDOUT';
42
+ case 'ReadOnlyError':
43
+ return 'EROFS';
44
+ case 'DataCloneError':
45
+ case 'EncodingError':
46
+ case 'NotReadableError':
47
+ case 'DataError':
48
+ case 'TransactionInactiveError':
49
+ case 'OperationError':
50
+ case 'UnknownError':
51
+ default:
52
+ return 'EIO';
53
+ }
54
+ }
55
+ /**
56
+ * Handles converting errors, then rethrowing them
57
+ */
58
+ export function convertException(ex, path, syscall) {
59
+ if (ex instanceof ApiError) {
60
+ return ex;
61
+ }
62
+ const code = ex instanceof DOMException ? ErrorCode[errnoForDOMException(ex)] : ErrorCode.EIO;
63
+ const error = new ApiError(code, ex.message, path, syscall);
64
+ error.stack = ex.stack;
65
+ error.cause = ex.cause;
66
+ return error;
67
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@zenfs/dom",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "DOM backends for ZenFS",
5
5
  "main": "dist/index.js",
6
- "types": "dist",
6
+ "types": "src/index.ts",
7
7
  "keywords": [
8
8
  "filesystem",
9
9
  "node",
@@ -27,13 +27,6 @@
27
27
  ".": "./dist/index.js",
28
28
  "./*": "./dist/*"
29
29
  },
30
- "typesVersions": {
31
- "*": {
32
- "*": [
33
- "./dist/*"
34
- ]
35
- }
36
- },
37
30
  "scripts": {
38
31
  "format": "prettier --write .",
39
32
  "format:check": "prettier --check .",
@@ -44,8 +37,8 @@
44
37
  },
45
38
  "devDependencies": {
46
39
  "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
47
- "@typescript-eslint/eslint-plugin": "^5.55.0",
48
- "@typescript-eslint/parser": "^5.55.0",
40
+ "@typescript-eslint/eslint-plugin": "^7.7.0",
41
+ "@typescript-eslint/parser": "^7.7.0",
49
42
  "esbuild": "^0.17.18",
50
43
  "eslint": "^8.36.0",
51
44
  "prettier": "^3.2.5",
@@ -53,6 +46,6 @@
53
46
  "typescript": "5.2.2"
54
47
  },
55
48
  "peerDependencies": {
56
- "@zenfs/core": "^0.7.0"
49
+ "@zenfs/core": "^0.8.1"
57
50
  }
58
51
  }
package/readme.md CHANGED
@@ -7,9 +7,9 @@
7
7
 
8
8
  ## Backends
9
9
 
10
- - `Storage`: Stores files in a `Storage` object, like `localStorage` and `sessionStorage`.
10
+ - `WebStorage`: Stores files in a `Storage` object, like `localStorage` and `sessionStorage`.
11
11
  - `IndexedDB`: Stores files into an `IndexedDB` object database.
12
- - `FileSystemAccess`: Store files using the [Web File System API](https://developer.mozilla.org/Web/API/File_System_API).
12
+ - `WebAccess`: Store files using the [Web File System API](https://developer.mozilla.org/Web/API/File_System_API).
13
13
 
14
14
  For more information, see the [API documentation](https://zen-fs.github.io/dom).
15
15
 
@@ -20,9 +20,9 @@ For more information, see the [API documentation](https://zen-fs.github.io/dom).
20
20
 
21
21
  ```js
22
22
  import { configure, fs } from '@zenfs/core';
23
- import { Storage } from '@zenfs/dom';
23
+ import { WebStorage } from '@zenfs/dom';
24
24
 
25
- await configure({ backend: Storage, storage: localStorage });
25
+ await configure({ backend: WebStorage, storage: localStorage });
26
26
 
27
27
  if (!fs.existsSync('/test.txt')) {
28
28
  fs.writeFileSync('/test.txt', 'This will persist across reloads!');
@@ -0,0 +1,149 @@
1
+ import type { AsyncStore, AsyncStoreOptions, AsyncTransaction, Backend, Ino } from '@zenfs/core';
2
+ import { AsyncStoreFS } from '@zenfs/core';
3
+ import { convertException } from './utils.js';
4
+
5
+ function wrap<T>(request: IDBRequest<T>): Promise<T> {
6
+ return new Promise((resolve, reject) => {
7
+ request.onsuccess = () => resolve(request.result);
8
+ request.onerror = e => {
9
+ e.preventDefault();
10
+ reject(convertException(request.error));
11
+ };
12
+ });
13
+ }
14
+
15
+ /**
16
+ * @hidden
17
+ */
18
+ export class IndexedDBTransaction implements AsyncTransaction {
19
+ constructor(
20
+ public tx: IDBTransaction,
21
+ public store: IDBObjectStore
22
+ ) {}
23
+
24
+ public get(key: Ino): Promise<Uint8Array> {
25
+ return wrap<Uint8Array>(this.store.get(key.toString()));
26
+ }
27
+
28
+ /**
29
+ * @todo return false when add has a key conflict (no error)
30
+ */
31
+ public async put(key: Ino, data: Uint8Array, overwrite: boolean): Promise<boolean> {
32
+ await wrap(this.store[overwrite ? 'put' : 'add'](data, key.toString()));
33
+ return true;
34
+ }
35
+
36
+ public remove(key: Ino): Promise<void> {
37
+ return wrap(this.store.delete(key.toString()));
38
+ }
39
+
40
+ public async commit(): Promise<void> {
41
+ return;
42
+ }
43
+
44
+ public async abort(): Promise<void> {
45
+ try {
46
+ this.tx.abort();
47
+ } catch (e) {
48
+ throw convertException(e);
49
+ }
50
+ }
51
+ }
52
+
53
+ export class IndexedDBStore implements AsyncStore {
54
+ public static async create(storeName: string, indexedDB: IDBFactory = globalThis.indexedDB): Promise<IndexedDBStore> {
55
+ const req: IDBOpenDBRequest = indexedDB.open(storeName, 1);
56
+
57
+ req.onupgradeneeded = () => {
58
+ const db: IDBDatabase = req.result;
59
+ // This should never happen; we're at version 1. Why does another database exist?
60
+ if (db.objectStoreNames.contains(storeName)) {
61
+ db.deleteObjectStore(storeName);
62
+ }
63
+ db.createObjectStore(storeName);
64
+ };
65
+
66
+ const result = await wrap(req);
67
+ return new IndexedDBStore(result, storeName);
68
+ }
69
+
70
+ constructor(
71
+ protected db: IDBDatabase,
72
+ protected storeName: string
73
+ ) {}
74
+
75
+ public get name(): string {
76
+ return IndexedDB.name + ':' + this.storeName;
77
+ }
78
+
79
+ public clear(): Promise<void> {
80
+ return wrap(this.db.transaction(this.storeName, 'readwrite').objectStore(this.storeName).clear());
81
+ }
82
+
83
+ public beginTransaction(): IndexedDBTransaction {
84
+ const tx = this.db.transaction(this.storeName, 'readwrite');
85
+ return new IndexedDBTransaction(tx, tx.objectStore(this.storeName));
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Configuration options for the IndexedDB file system.
91
+ */
92
+ export interface IndexedDBOptions extends Omit<AsyncStoreOptions, 'store'> {
93
+ /**
94
+ * The name of this file system. You can have multiple IndexedDB file systems operating at once, but each must have a different name.
95
+ */
96
+ storeName?: string;
97
+
98
+ /**
99
+ * The IDBFactory to use. Defaults to `globalThis.indexedDB`.
100
+ */
101
+ idbFactory?: IDBFactory;
102
+ }
103
+
104
+ /**
105
+ * A file system that uses the IndexedDB key value file system.
106
+ */
107
+
108
+ export const IndexedDB = {
109
+ name: 'IndexedDB',
110
+
111
+ options: {
112
+ storeName: {
113
+ type: 'string',
114
+ required: false,
115
+ description: 'The name of this file system. You can have multiple IndexedDB file systems operating at once, but each must have a different name.',
116
+ },
117
+ cacheSize: {
118
+ type: 'number',
119
+ required: false,
120
+ description: 'The size of the inode cache. Defaults to 100. A size of 0 or below disables caching.',
121
+ },
122
+ idbFactory: {
123
+ type: 'object',
124
+ required: false,
125
+ description: 'The IDBFactory to use. Defaults to globalThis.indexedDB.',
126
+ },
127
+ },
128
+
129
+ async isAvailable(idbFactory: IDBFactory = globalThis.indexedDB): Promise<boolean> {
130
+ try {
131
+ if (!(idbFactory instanceof IDBFactory)) {
132
+ return false;
133
+ }
134
+ const req = idbFactory.open('__zenfs_test');
135
+ await wrap(req);
136
+ idbFactory.deleteDatabase('__zenfs_test');
137
+ return true;
138
+ } catch (e) {
139
+ idbFactory.deleteDatabase('__zenfs_test');
140
+ return false;
141
+ }
142
+ },
143
+
144
+ create(options: IndexedDBOptions) {
145
+ const store = IndexedDBStore.create(options.storeName || 'zenfs', options.idbFactory);
146
+ const fs = new AsyncStoreFS({ ...options, store });
147
+ return fs;
148
+ },
149
+ } as const satisfies Backend;
package/src/Storage.ts ADDED
@@ -0,0 +1,85 @@
1
+ import type { Backend, Ino, SimpleSyncStore, SyncStore } from '@zenfs/core';
2
+ import { ApiError, ErrorCode, SimpleSyncTransaction, SyncStoreFS, decode, encode } from '@zenfs/core';
3
+
4
+ /**
5
+ * A synchronous key-value store backed by Storage.
6
+ */
7
+ export class WebStorageStore implements SyncStore, SimpleSyncStore {
8
+ public get name(): string {
9
+ return WebStorage.name;
10
+ }
11
+
12
+ constructor(protected _storage: Storage) {}
13
+
14
+ public clear(): void {
15
+ this._storage.clear();
16
+ }
17
+
18
+ public beginTransaction(): SimpleSyncTransaction {
19
+ // No need to differentiate.
20
+ return new SimpleSyncTransaction(this);
21
+ }
22
+
23
+ public get(key: Ino): Uint8Array | undefined {
24
+ const data = this._storage.getItem(key.toString());
25
+ if (typeof data != 'string') {
26
+ return;
27
+ }
28
+
29
+ return encode(data);
30
+ }
31
+
32
+ public put(key: Ino, data: Uint8Array, overwrite: boolean): boolean {
33
+ try {
34
+ if (!overwrite && this._storage.getItem(key.toString()) !== null) {
35
+ // Don't want to overwrite the key!
36
+ return false;
37
+ }
38
+ this._storage.setItem(key.toString(), decode(data));
39
+ return true;
40
+ } catch (e) {
41
+ throw new ApiError(ErrorCode.ENOSPC, 'Storage is full.');
42
+ }
43
+ }
44
+
45
+ public remove(key: Ino): void {
46
+ try {
47
+ this._storage.removeItem(key.toString());
48
+ } catch (e) {
49
+ throw new ApiError(ErrorCode.EIO, 'Unable to delete key ' + key + ': ' + e);
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Options to pass to the StorageFileSystem
56
+ */
57
+ export interface WebStorageOptions {
58
+ /**
59
+ * The Storage to use. Defaults to globalThis.localStorage.
60
+ */
61
+ storage?: Storage;
62
+ }
63
+
64
+ /**
65
+ * A synchronous file system backed by a `Storage` (e.g. localStorage).
66
+ */
67
+ export const WebStorage = {
68
+ name: 'WebStorage',
69
+
70
+ options: {
71
+ storage: {
72
+ type: 'object',
73
+ required: false,
74
+ description: 'The Storage to use. Defaults to globalThis.localStorage.',
75
+ },
76
+ },
77
+
78
+ isAvailable(storage: Storage = globalThis.localStorage): boolean {
79
+ return storage instanceof globalThis.Storage;
80
+ },
81
+
82
+ create({ storage = globalThis.localStorage }: WebStorageOptions) {
83
+ return new SyncStoreFS({ store: new WebStorageStore(storage) });
84
+ },
85
+ } as const satisfies Backend;
package/src/access.ts ADDED
@@ -0,0 +1,228 @@
1
+ import type { Backend, FileSystemMetadata } from '@zenfs/core';
2
+ import { ApiError, Async, ErrorCode, FileSystem, FileType, InMemory, PreloadFile, Stats } from '@zenfs/core';
3
+ import { basename, dirname, join } from '@zenfs/core/emulation/path.js';
4
+ import { convertException } from './utils.js';
5
+
6
+ declare global {
7
+ interface FileSystemDirectoryHandle {
8
+ [Symbol.iterator](): IterableIterator<[string, FileSystemHandle]>;
9
+ entries(): IterableIterator<[string, FileSystemHandle]>;
10
+ keys(): IterableIterator<string>;
11
+ values(): IterableIterator<FileSystemHandle>;
12
+ }
13
+ }
14
+
15
+ export interface WebAccessOptions {
16
+ handle: FileSystemDirectoryHandle;
17
+ }
18
+
19
+ export class WebAccessFS extends Async(FileSystem) {
20
+ private _handles: Map<string, FileSystemHandle> = new Map();
21
+
22
+ /**
23
+ * @hidden
24
+ */
25
+ _sync: FileSystem;
26
+
27
+ public async ready(): Promise<this> {
28
+ return this;
29
+ }
30
+
31
+ public constructor({ handle }: WebAccessOptions) {
32
+ super();
33
+ this._handles.set('/', handle);
34
+ this._sync = InMemory.create({ name: 'accessfs-cache' });
35
+ }
36
+
37
+ public metadata(): FileSystemMetadata {
38
+ return {
39
+ ...super.metadata(),
40
+ name: 'WebAccess',
41
+ };
42
+ }
43
+
44
+ public async sync(p: string, data: Uint8Array, stats: Stats): Promise<void> {
45
+ const currentStats = await this.stat(p);
46
+ if (stats.mtime !== currentStats!.mtime) {
47
+ await this.writeFile(p, data);
48
+ }
49
+ }
50
+
51
+ public async rename(oldPath: string, newPath: string): Promise<void> {
52
+ try {
53
+ const handle = await this.getHandle(oldPath);
54
+ if (handle instanceof FileSystemDirectoryHandle) {
55
+ const files = await this.readdir(oldPath);
56
+
57
+ await this.mkdir(newPath);
58
+ if (files.length == 0) {
59
+ await this.unlink(oldPath);
60
+ } else {
61
+ for (const file of files) {
62
+ await this.rename(join(oldPath, file), join(newPath, file));
63
+ await this.unlink(oldPath);
64
+ }
65
+ }
66
+ }
67
+ if (!(handle instanceof FileSystemFileHandle)) {
68
+ return;
69
+ }
70
+ const oldFile = await handle.getFile(),
71
+ destFolder = await this.getHandle(dirname(newPath));
72
+ if (!(destFolder instanceof FileSystemDirectoryHandle)) {
73
+ return;
74
+ }
75
+ const newFile = await destFolder.getFileHandle(basename(newPath), { create: true });
76
+ const writable = await newFile.createWritable();
77
+ const buffer = await oldFile.arrayBuffer();
78
+ await writable.write(buffer);
79
+
80
+ writable.close();
81
+ await this.unlink(oldPath);
82
+ } catch (ex) {
83
+ throw convertException(ex, oldPath, 'rename');
84
+ }
85
+ }
86
+
87
+ public async writeFile(fname: string, data: Uint8Array): Promise<void> {
88
+ const handle = await this.getHandle(dirname(fname));
89
+ if (!(handle instanceof FileSystemDirectoryHandle)) {
90
+ return;
91
+ }
92
+
93
+ const file = await handle.getFileHandle(basename(fname), { create: true });
94
+ const writable = await file.createWritable();
95
+ await writable.write(data);
96
+ await writable.close();
97
+ }
98
+
99
+ public async createFile(path: string, flag: string): Promise<PreloadFile<this>> {
100
+ await this.writeFile(path, new Uint8Array());
101
+ return this.openFile(path, flag);
102
+ }
103
+
104
+ public async stat(path: string): Promise<Stats> {
105
+ const handle = await this.getHandle(path);
106
+ if (!handle) {
107
+ throw ApiError.With('ENOENT', path, 'stat');
108
+ }
109
+ if (handle instanceof FileSystemDirectoryHandle) {
110
+ return new Stats({ mode: 0o777 | FileType.DIRECTORY, size: 4096 });
111
+ }
112
+ if (handle instanceof FileSystemFileHandle) {
113
+ const { lastModified, size } = await handle.getFile();
114
+ return new Stats({ mode: 0o777 | FileType.FILE, size, mtimeMs: lastModified });
115
+ }
116
+ }
117
+
118
+ public async openFile(path: string, flag: string): Promise<PreloadFile<this>> {
119
+ const handle = await this.getHandle(path);
120
+ if (handle instanceof FileSystemFileHandle) {
121
+ const file = await handle.getFile();
122
+ const data = new Uint8Array(await file.arrayBuffer());
123
+ const stats = new Stats({ mode: 0o777 | FileType.FILE, size: file.size, mtimeMs: file.lastModified });
124
+ return new PreloadFile(this, path, flag, stats, data);
125
+ }
126
+ }
127
+
128
+ public async unlink(path: string): Promise<void> {
129
+ const handle = await this.getHandle(dirname(path));
130
+ if (handle instanceof FileSystemDirectoryHandle) {
131
+ try {
132
+ await handle.removeEntry(basename(path), { recursive: true });
133
+ } catch (ex) {
134
+ throw convertException(ex, path, 'unlink');
135
+ }
136
+ }
137
+ }
138
+
139
+ public async link(srcpath: string): Promise<void> {
140
+ throw ApiError.With('ENOSYS', srcpath, 'WebAccessFS.link');
141
+ }
142
+
143
+ public async rmdir(path: string): Promise<void> {
144
+ return this.unlink(path);
145
+ }
146
+
147
+ public async mkdir(path: string): Promise<void> {
148
+ const existingHandle = await this.getHandle(path);
149
+ if (existingHandle) {
150
+ throw ApiError.With('EEXIST', path, 'mkdir');
151
+ }
152
+
153
+ const handle = await this.getHandle(dirname(path));
154
+ if (handle instanceof FileSystemDirectoryHandle) {
155
+ await handle.getDirectoryHandle(basename(path), { create: true });
156
+ }
157
+ }
158
+
159
+ public async readdir(path: string): Promise<string[]> {
160
+ const handle = await this.getHandle(path);
161
+ if (!(handle instanceof FileSystemDirectoryHandle)) {
162
+ throw ApiError.With('ENOTDIR', path, 'readdir');
163
+ }
164
+ const _keys: string[] = [];
165
+ for await (const key of handle.keys()) {
166
+ _keys.push(join(path, key));
167
+ }
168
+ return _keys;
169
+ }
170
+
171
+ protected async getHandle(path: string): Promise<FileSystemHandle> {
172
+ if (this._handles.has(path)) {
173
+ return this._handles.get(path);
174
+ }
175
+
176
+ let walked = '/';
177
+
178
+ for (const part of path.split('/').slice(1)) {
179
+ const handle = this._handles.get(walked);
180
+ if (!(handle instanceof FileSystemDirectoryHandle)) {
181
+ throw ApiError.With('ENOTDIR', walked, 'getHandle');
182
+ }
183
+ walked = join(walked, part);
184
+
185
+ try {
186
+ const dirHandle = await handle.getDirectoryHandle(part);
187
+ this._handles.set(walked, dirHandle);
188
+ } catch (ex) {
189
+ if (ex.name == 'TypeMismatchError') {
190
+ try {
191
+ const fileHandle = await handle.getFileHandle(part);
192
+ this._handles.set(walked, fileHandle);
193
+ } catch (ex) {
194
+ convertException(ex, walked, 'getHandle');
195
+ }
196
+ }
197
+
198
+ if (ex.name === 'TypeError') {
199
+ throw new ApiError(ErrorCode.ENOENT, ex.message, walked, 'getHandle');
200
+ }
201
+
202
+ convertException(ex, walked, 'getHandle');
203
+ }
204
+ }
205
+
206
+ return this._handles.get(path);
207
+ }
208
+ }
209
+
210
+ export const WebAccess = {
211
+ name: 'WebAccess',
212
+
213
+ options: {
214
+ handle: {
215
+ type: 'object',
216
+ required: true,
217
+ description: 'The directory handle to use for the root',
218
+ },
219
+ },
220
+
221
+ isAvailable(): boolean {
222
+ return typeof FileSystemHandle == 'function';
223
+ },
224
+
225
+ create(options: WebAccessOptions) {
226
+ return new WebAccessFS(options);
227
+ },
228
+ } as const satisfies Backend;
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './access.js';
2
+ export * from './IndexedDB.js';
3
+ export * from './Storage.js';
package/src/utils.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { ApiError, ErrorCode } from '@zenfs/core';
2
+
3
+ /**
4
+ * Converts a DOMException into an ErrorCode
5
+ * @see https://developer.mozilla.org/Web/API/DOMException
6
+ */
7
+ function errnoForDOMException(ex: DOMException): keyof typeof ErrorCode {
8
+ switch (ex.name) {
9
+ case 'IndexSizeError':
10
+ case 'HierarchyRequestError':
11
+ case 'InvalidCharacterError':
12
+ case 'InvalidStateError':
13
+ case 'SyntaxError':
14
+ case 'NamespaceError':
15
+ case 'TypeMismatchError':
16
+ case 'ConstraintError':
17
+ case 'VersionError':
18
+ case 'URLMismatchError':
19
+ case 'InvalidNodeTypeError':
20
+ return 'EINVAL';
21
+ case 'WrongDocumentError':
22
+ return 'EXDEV';
23
+ case 'NoModificationAllowedError':
24
+ case 'InvalidModificationError':
25
+ case 'InvalidAccessError':
26
+ case 'SecurityError':
27
+ case 'NotAllowedError':
28
+ return 'EACCES';
29
+ case 'NotFoundError':
30
+ return 'ENOENT';
31
+ case 'NotSupportedError':
32
+ return 'ENOTSUP';
33
+ case 'InUseAttributeError':
34
+ return 'EBUSY';
35
+ case 'NetworkError':
36
+ return 'ENETDOWN';
37
+ case 'AbortError':
38
+ return 'EINTR';
39
+ case 'QuotaExceededError':
40
+ return 'ENOSPC';
41
+ case 'TimeoutError':
42
+ return 'ETIMEDOUT';
43
+ case 'ReadOnlyError':
44
+ return 'EROFS';
45
+ case 'DataCloneError':
46
+ case 'EncodingError':
47
+ case 'NotReadableError':
48
+ case 'DataError':
49
+ case 'TransactionInactiveError':
50
+ case 'OperationError':
51
+ case 'UnknownError':
52
+ default:
53
+ return 'EIO';
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Handles converting errors, then rethrowing them
59
+ */
60
+ export function convertException(ex: Error | ApiError | DOMException, path?: string, syscall?: string): ApiError {
61
+ if (ex instanceof ApiError) {
62
+ return ex;
63
+ }
64
+
65
+ const code = ex instanceof DOMException ? ErrorCode[errnoForDOMException(ex)] : ErrorCode.EIO;
66
+ const error = new ApiError(code, ex.message, path, syscall);
67
+ error.stack = ex.stack;
68
+ error.cause = ex.cause;
69
+ return error;
70
+ }