@zenfs/dom 0.0.6 → 0.1.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.
@@ -0,0 +1,55 @@
1
+ import { FileFlag, PreloadFile } from '@zenfs/core/file.js';
2
+ import { FileSystem, type FileSystemMetadata } from '@zenfs/core/filesystem.js';
3
+ import { Stats } from '@zenfs/core/stats.js';
4
+ import type { Backend } from '@zenfs/core/backends/backend.js';
5
+ declare global {
6
+ interface FileSystemDirectoryHandle {
7
+ [Symbol.iterator](): IterableIterator<[string, FileSystemHandle]>;
8
+ entries(): IterableIterator<[string, FileSystemHandle]>;
9
+ keys(): IterableIterator<string>;
10
+ values(): IterableIterator<FileSystemHandle>;
11
+ }
12
+ }
13
+ interface FileSystemAccessOptions {
14
+ handle: FileSystemDirectoryHandle;
15
+ }
16
+ export declare class FileSystemAccessFile extends PreloadFile<FileSystemAccessFS> {
17
+ constructor(_fs: FileSystemAccessFS, _path: string, _flag: FileFlag, _stat: Stats, contents?: Uint8Array);
18
+ syncSync(): void;
19
+ sync(): Promise<void>;
20
+ close(): Promise<void>;
21
+ closeSync(): void;
22
+ }
23
+ declare const FileSystemAccessFS_base: (abstract new (...args: any[]) => {
24
+ metadata(): FileSystemMetadata;
25
+ renameSync(oldPath: string, newPath: string, cred: import("@zenfs/core/cred.js").Cred): void;
26
+ statSync(path: string, cred: import("@zenfs/core/cred.js").Cred): Stats;
27
+ createFileSync(path: string, flag: FileFlag, mode: number, cred: import("@zenfs/core/cred.js").Cred): import("@zenfs/core/file.js").File;
28
+ openFileSync(path: string, flag: FileFlag, cred: import("@zenfs/core/cred.js").Cred): import("@zenfs/core/file.js").File;
29
+ unlinkSync(path: string, cred: import("@zenfs/core/cred.js").Cred): void;
30
+ rmdirSync(path: string, cred: import("@zenfs/core/cred.js").Cred): void;
31
+ mkdirSync(path: string, mode: number, cred: import("@zenfs/core/cred.js").Cred): void;
32
+ readdirSync(path: string, cred: import("@zenfs/core/cred.js").Cred): string[];
33
+ linkSync(srcpath: string, dstpath: string, cred: import("@zenfs/core/cred.js").Cred): void;
34
+ syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
35
+ }) & typeof FileSystem;
36
+ export declare class FileSystemAccessFS extends FileSystemAccessFS_base {
37
+ private _handles;
38
+ ready(): Promise<this>;
39
+ constructor({ handle }: FileSystemAccessOptions);
40
+ metadata(): FileSystemMetadata;
41
+ sync(p: string, data: Uint8Array, stats: Stats): Promise<void>;
42
+ rename(oldPath: string, newPath: string): Promise<void>;
43
+ writeFile(fname: string, data: Uint8Array): Promise<void>;
44
+ createFile(path: string, flag: FileFlag): Promise<FileSystemAccessFile>;
45
+ stat(path: string): Promise<Stats>;
46
+ openFile(path: string, flag: FileFlag): Promise<FileSystemAccessFile>;
47
+ unlink(path: string): Promise<void>;
48
+ link(): Promise<void>;
49
+ rmdir(path: string): Promise<void>;
50
+ mkdir(path: string): Promise<void>;
51
+ readdir(path: string): Promise<string[]>;
52
+ protected getHandle(path: string): Promise<FileSystemHandle>;
53
+ }
54
+ export declare const FileSystemAccess: Backend;
55
+ export {};
@@ -0,0 +1,216 @@
1
+ import { basename, dirname, join } from '@zenfs/core/emulation/path.js';
2
+ import { ApiError, ErrorCode } from '@zenfs/core/ApiError.js';
3
+ import { PreloadFile } from '@zenfs/core/file.js';
4
+ import { FileSystem, Async } from '@zenfs/core/filesystem.js';
5
+ import { Stats, FileType } from '@zenfs/core/stats.js';
6
+ const handleError = (path = '', error) => {
7
+ if (error.name === 'NotFoundError') {
8
+ throw ApiError.ENOENT(path);
9
+ }
10
+ throw error;
11
+ };
12
+ export class FileSystemAccessFile extends PreloadFile {
13
+ constructor(_fs, _path, _flag, _stat, contents) {
14
+ super(_fs, _path, _flag, _stat, contents);
15
+ }
16
+ syncSync() {
17
+ throw new ApiError(ErrorCode.ENOTSUP);
18
+ }
19
+ async sync() {
20
+ if (this.isDirty()) {
21
+ await this.fs.sync(this.path, this.buffer, this.stats);
22
+ this.resetDirty();
23
+ }
24
+ }
25
+ async close() {
26
+ await this.sync();
27
+ }
28
+ closeSync() {
29
+ throw new ApiError(ErrorCode.ENOTSUP);
30
+ }
31
+ }
32
+ export class FileSystemAccessFS extends Async(FileSystem) {
33
+ async ready() {
34
+ return this;
35
+ }
36
+ constructor({ handle }) {
37
+ super();
38
+ this._handles = new Map();
39
+ this._handles.set('/', handle);
40
+ }
41
+ metadata() {
42
+ return {
43
+ ...super.metadata(),
44
+ name: 'FileSystemAccess',
45
+ };
46
+ }
47
+ async sync(p, data, stats) {
48
+ const currentStats = await this.stat(p);
49
+ if (stats.mtime !== currentStats.mtime) {
50
+ await this.writeFile(p, data);
51
+ }
52
+ }
53
+ async rename(oldPath, newPath) {
54
+ try {
55
+ const handle = await this.getHandle(oldPath);
56
+ if (handle instanceof FileSystemDirectoryHandle) {
57
+ const files = await this.readdir(oldPath);
58
+ await this.mkdir(newPath);
59
+ if (files.length == 0) {
60
+ await this.unlink(oldPath);
61
+ }
62
+ else {
63
+ for (const file of files) {
64
+ await this.rename(join(oldPath, file), join(newPath, file));
65
+ await this.unlink(oldPath);
66
+ }
67
+ }
68
+ }
69
+ if (!(handle instanceof FileSystemFileHandle)) {
70
+ return;
71
+ }
72
+ const oldFile = await handle.getFile(), destFolder = await this.getHandle(dirname(newPath));
73
+ if (!(destFolder instanceof FileSystemDirectoryHandle)) {
74
+ return;
75
+ }
76
+ const newFile = await destFolder.getFileHandle(basename(newPath), { create: true });
77
+ const writable = await newFile.createWritable();
78
+ const buffer = await oldFile.arrayBuffer();
79
+ await writable.write(buffer);
80
+ writable.close();
81
+ await this.unlink(oldPath);
82
+ }
83
+ catch (err) {
84
+ handleError(oldPath, err);
85
+ }
86
+ }
87
+ async writeFile(fname, data) {
88
+ const handle = await this.getHandle(dirname(fname));
89
+ if (!(handle instanceof FileSystemDirectoryHandle)) {
90
+ return;
91
+ }
92
+ const file = await handle.getFileHandle(basename(fname), { create: true });
93
+ const writable = await file.createWritable();
94
+ await writable.write(data);
95
+ await writable.close();
96
+ }
97
+ async createFile(path, flag) {
98
+ await this.writeFile(path, new Uint8Array());
99
+ return this.openFile(path, flag);
100
+ }
101
+ async stat(path) {
102
+ const handle = await this.getHandle(path);
103
+ if (!handle) {
104
+ throw ApiError.OnPath(ErrorCode.ENOENT, path);
105
+ }
106
+ if (handle instanceof FileSystemDirectoryHandle) {
107
+ return new Stats(FileType.DIRECTORY, 4096);
108
+ }
109
+ if (handle instanceof FileSystemFileHandle) {
110
+ const { lastModified, size } = await handle.getFile();
111
+ return new Stats(FileType.FILE, size, null, Date.now(), lastModified);
112
+ }
113
+ }
114
+ async openFile(path, flag) {
115
+ const handle = await this.getHandle(path);
116
+ if (handle instanceof FileSystemFileHandle) {
117
+ const file = await handle.getFile();
118
+ const data = new Uint8Array(await file.arrayBuffer());
119
+ const stats = new Stats(FileType.FILE, file.size, null, Date.now(), file.lastModified);
120
+ return new FileSystemAccessFile(this, path, flag, stats, data);
121
+ }
122
+ }
123
+ async unlink(path) {
124
+ const handle = await this.getHandle(dirname(path));
125
+ if (handle instanceof FileSystemDirectoryHandle) {
126
+ try {
127
+ await handle.removeEntry(basename(path), { recursive: true });
128
+ }
129
+ catch (e) {
130
+ handleError(path, e);
131
+ }
132
+ }
133
+ }
134
+ async link() {
135
+ throw new ApiError(ErrorCode.ENOTSUP);
136
+ }
137
+ async rmdir(path) {
138
+ return this.unlink(path);
139
+ }
140
+ async mkdir(path) {
141
+ const existingHandle = await this.getHandle(path);
142
+ if (existingHandle) {
143
+ throw ApiError.EEXIST(path);
144
+ }
145
+ const handle = await this.getHandle(dirname(path));
146
+ if (handle instanceof FileSystemDirectoryHandle) {
147
+ await handle.getDirectoryHandle(basename(path), { create: true });
148
+ }
149
+ }
150
+ async readdir(path) {
151
+ const handle = await this.getHandle(path);
152
+ if (!(handle instanceof FileSystemDirectoryHandle)) {
153
+ throw ApiError.ENOTDIR(path);
154
+ }
155
+ const _keys = [];
156
+ for await (const key of handle.keys()) {
157
+ _keys.push(join(path, key));
158
+ }
159
+ return _keys;
160
+ }
161
+ async getHandle(path) {
162
+ if (this._handles.has(path)) {
163
+ return this._handles.get(path);
164
+ }
165
+ let walkedPath = '/';
166
+ const [, ...pathParts] = path.split('/');
167
+ const getHandleParts = async ([pathPart, ...remainingPathParts]) => {
168
+ const walkingPath = join(walkedPath, pathPart);
169
+ const continueWalk = (handle) => {
170
+ walkedPath = walkingPath;
171
+ this._handles.set(walkedPath, handle);
172
+ if (remainingPathParts.length === 0) {
173
+ return this._handles.get(path);
174
+ }
175
+ getHandleParts(remainingPathParts);
176
+ };
177
+ const handle = this._handles.get(walkedPath);
178
+ try {
179
+ return continueWalk(await handle.getDirectoryHandle(pathPart));
180
+ }
181
+ catch (error) {
182
+ if (error.name === 'TypeMismatchError') {
183
+ try {
184
+ return continueWalk(await handle.getFileHandle(pathPart));
185
+ }
186
+ catch (err) {
187
+ handleError(walkingPath, err);
188
+ }
189
+ }
190
+ else if (error.message === 'Name is not allowed.') {
191
+ throw new ApiError(ErrorCode.ENOENT, error.message, walkingPath);
192
+ }
193
+ else {
194
+ handleError(walkingPath, error);
195
+ }
196
+ }
197
+ };
198
+ return await getHandleParts(pathParts);
199
+ }
200
+ }
201
+ export const FileSystemAccess = {
202
+ name: 'FileSystemAccess',
203
+ options: {
204
+ handle: {
205
+ type: 'object',
206
+ required: true,
207
+ description: 'The directory handle to use for the root',
208
+ },
209
+ },
210
+ isAvailable() {
211
+ return typeof FileSystemHandle == 'function';
212
+ },
213
+ create(options) {
214
+ return new FileSystemAccessFS(options);
215
+ },
216
+ };
@@ -0,0 +1,56 @@
1
+ import { AsyncROTransaction, AsyncRWTransaction, AsyncStore } from '@zenfs/core/backends/AsyncStore.js';
2
+ import type { Backend } from '@zenfs/core/backends/backend.js';
3
+ import type { Ino } from '@zenfs/core/inode.js';
4
+ /**
5
+ * @hidden
6
+ */
7
+ export declare class IndexedDBROTransaction implements AsyncROTransaction {
8
+ tx: IDBTransaction;
9
+ store: IDBObjectStore;
10
+ constructor(tx: IDBTransaction, store: IDBObjectStore);
11
+ get(key: Ino): Promise<Uint8Array>;
12
+ }
13
+ /**
14
+ * @hidden
15
+ */
16
+ export declare class IndexedDBRWTransaction extends IndexedDBROTransaction implements AsyncRWTransaction, AsyncROTransaction {
17
+ constructor(tx: IDBTransaction, store: IDBObjectStore);
18
+ /**
19
+ * @todo return false when add has a key conflict (no error)
20
+ */
21
+ put(key: Ino, data: Uint8Array, overwrite: boolean): Promise<boolean>;
22
+ remove(key: Ino): Promise<void>;
23
+ commit(): Promise<void>;
24
+ abort(): Promise<void>;
25
+ }
26
+ export declare class IndexedDBStore implements AsyncStore {
27
+ protected db: IDBDatabase;
28
+ protected storeName: string;
29
+ static create(storeName: string, indexedDB: IDBFactory): Promise<IndexedDBStore>;
30
+ constructor(db: IDBDatabase, storeName: string);
31
+ get name(): string;
32
+ clear(): Promise<void>;
33
+ beginTransaction(type: 'readonly'): AsyncROTransaction;
34
+ beginTransaction(type: 'readwrite'): AsyncRWTransaction;
35
+ }
36
+ /**
37
+ * Configuration options for the IndexedDB file system.
38
+ */
39
+ export interface IndexedDBOptions {
40
+ /**
41
+ * The name of this file system. You can have multiple IndexedDB file systems operating at once, but each must have a different name.
42
+ */
43
+ storeName?: string;
44
+ /**
45
+ * The size of the inode cache. Defaults to 100. A size of 0 or below disables caching.
46
+ */
47
+ cacheSize?: number;
48
+ /**
49
+ * The IDBFactory to use. Defaults to `globalThis.indexedDB`.
50
+ */
51
+ idbFactory?: IDBFactory;
52
+ }
53
+ /**
54
+ * A file system that uses the IndexedDB key value file system.
55
+ */
56
+ export declare const IndexedDB: Backend;
@@ -0,0 +1,200 @@
1
+ import { AsyncStoreFS } from '@zenfs/core/backends/AsyncStore.js';
2
+ import { ApiError, ErrorCode } from '@zenfs/core/ApiError.js';
3
+ /**
4
+ * Converts a DOMException or a DOMError from an IndexedDB event into a
5
+ * standardized ZenFS API error.
6
+ * @hidden
7
+ */
8
+ function convertError(e, message = e.toString()) {
9
+ switch (e.name) {
10
+ case 'NotFoundError':
11
+ return new ApiError(ErrorCode.ENOENT, message);
12
+ case 'QuotaExceededError':
13
+ return new ApiError(ErrorCode.ENOSPC, message);
14
+ default:
15
+ // The rest do not seem to map cleanly to standard error codes.
16
+ return new ApiError(ErrorCode.EIO, message);
17
+ }
18
+ }
19
+ /**
20
+ * @hidden
21
+ */
22
+ export class IndexedDBROTransaction {
23
+ constructor(tx, store) {
24
+ this.tx = tx;
25
+ this.store = store;
26
+ }
27
+ get(key) {
28
+ return new Promise((resolve, reject) => {
29
+ try {
30
+ const req = this.store.get(key.toString());
31
+ req.onerror = e => {
32
+ e.preventDefault();
33
+ reject(new ApiError(ErrorCode.EIO));
34
+ };
35
+ req.onsuccess = () => {
36
+ // IDB returns the value 'undefined' when you try to get keys that
37
+ // don't exist. The caller expects this behavior.
38
+ const result = req.result;
39
+ if (result === undefined) {
40
+ resolve(result);
41
+ }
42
+ else {
43
+ // IDB data is stored as an ArrayUint8Array
44
+ resolve(Uint8Array.from(result));
45
+ }
46
+ };
47
+ }
48
+ catch (e) {
49
+ reject(convertError(e));
50
+ }
51
+ });
52
+ }
53
+ }
54
+ /**
55
+ * @hidden
56
+ */
57
+ export class IndexedDBRWTransaction extends IndexedDBROTransaction {
58
+ constructor(tx, store) {
59
+ super(tx, store);
60
+ }
61
+ /**
62
+ * @todo return false when add has a key conflict (no error)
63
+ */
64
+ put(key, data, overwrite) {
65
+ return new Promise((resolve, reject) => {
66
+ try {
67
+ const req = overwrite ? this.store.put(data, key.toString()) : this.store.add(data, key.toString());
68
+ req.onerror = e => {
69
+ e.preventDefault();
70
+ reject(new ApiError(ErrorCode.EIO));
71
+ };
72
+ req.onsuccess = () => resolve(true);
73
+ }
74
+ catch (e) {
75
+ reject(convertError(e));
76
+ }
77
+ });
78
+ }
79
+ remove(key) {
80
+ return new Promise((resolve, reject) => {
81
+ try {
82
+ const req = this.store.delete(key.toString());
83
+ req.onerror = e => {
84
+ e.preventDefault();
85
+ reject(new ApiError(ErrorCode.EIO));
86
+ };
87
+ req.onsuccess = () => resolve;
88
+ }
89
+ catch (e) {
90
+ reject(convertError(e));
91
+ }
92
+ });
93
+ }
94
+ commit() {
95
+ return new Promise(resolve => setTimeout(resolve, 0));
96
+ }
97
+ async abort() {
98
+ try {
99
+ this.tx.abort();
100
+ }
101
+ catch (e) {
102
+ throw convertError(e);
103
+ }
104
+ }
105
+ }
106
+ export class IndexedDBStore {
107
+ static create(storeName, indexedDB) {
108
+ return new Promise((resolve, reject) => {
109
+ const req = indexedDB.open(storeName, 1);
110
+ req.onupgradeneeded = () => {
111
+ const db = req.result;
112
+ // This should never happen; we're at version 1. Why does another database exist?
113
+ if (db.objectStoreNames.contains(storeName)) {
114
+ db.deleteObjectStore(storeName);
115
+ }
116
+ db.createObjectStore(storeName);
117
+ };
118
+ req.onsuccess = () => resolve(new IndexedDBStore(req.result, storeName));
119
+ req.onerror = e => {
120
+ e.preventDefault();
121
+ reject(new ApiError(ErrorCode.EACCES));
122
+ };
123
+ });
124
+ }
125
+ constructor(db, storeName) {
126
+ this.db = db;
127
+ this.storeName = storeName;
128
+ }
129
+ get name() {
130
+ return IndexedDB.name + ':' + this.storeName;
131
+ }
132
+ clear() {
133
+ return new Promise((resolve, reject) => {
134
+ try {
135
+ const req = this.db.transaction(this.storeName, 'readwrite').objectStore(this.storeName).clear();
136
+ req.onsuccess = () => setTimeout(resolve, 0);
137
+ req.onerror = e => {
138
+ e.preventDefault();
139
+ reject(new ApiError(ErrorCode.EIO));
140
+ };
141
+ }
142
+ catch (e) {
143
+ reject(convertError(e));
144
+ }
145
+ });
146
+ }
147
+ beginTransaction(type = 'readonly') {
148
+ const tx = this.db.transaction(this.storeName, type), objectStore = tx.objectStore(this.storeName);
149
+ if (type === 'readwrite') {
150
+ return new IndexedDBRWTransaction(tx, objectStore);
151
+ }
152
+ if (type === 'readonly') {
153
+ return new IndexedDBROTransaction(tx, objectStore);
154
+ }
155
+ throw new ApiError(ErrorCode.EINVAL, 'Invalid transaction type.');
156
+ }
157
+ }
158
+ /**
159
+ * A file system that uses the IndexedDB key value file system.
160
+ */
161
+ export const IndexedDB = {
162
+ name: 'IndexedDB',
163
+ options: {
164
+ storeName: {
165
+ type: 'string',
166
+ required: false,
167
+ description: 'The name of this file system. You can have multiple IndexedDB file systems operating at once, but each must have a different name.',
168
+ },
169
+ cacheSize: {
170
+ type: 'number',
171
+ required: false,
172
+ description: 'The size of the inode cache. Defaults to 100. A size of 0 or below disables caching.',
173
+ },
174
+ idbFactory: {
175
+ type: 'object',
176
+ required: false,
177
+ description: 'The IDBFactory to use. Defaults to globalThis.indexedDB.',
178
+ },
179
+ },
180
+ isAvailable(idbFactory = globalThis.indexedDB) {
181
+ try {
182
+ if (!(idbFactory instanceof IDBFactory)) {
183
+ return false;
184
+ }
185
+ const req = idbFactory.open('__zenfs_test');
186
+ if (!req) {
187
+ return false;
188
+ }
189
+ req.onsuccess = () => idbFactory.deleteDatabase('__zenfs_test');
190
+ }
191
+ catch (e) {
192
+ return false;
193
+ }
194
+ },
195
+ create({ cacheSize = 100, storeName = 'zenfs', idbFactory = globalThis.indexedDB }) {
196
+ const store = IndexedDBStore.create(storeName, idbFactory);
197
+ const fs = new AsyncStoreFS({ cacheSize, store });
198
+ return fs;
199
+ },
200
+ };
@@ -0,0 +1,29 @@
1
+ import { SyncStore, SimpleSyncStore, SyncRWTransaction } from '@zenfs/core/backends/SyncStore.js';
2
+ import { type Backend } from '@zenfs/core/backends/backend.js';
3
+ import type { Ino } from '@zenfs/core/inode.js';
4
+ /**
5
+ * A synchronous key-value store backed by Storage.
6
+ */
7
+ export declare class StorageStore implements SyncStore, SimpleSyncStore {
8
+ protected _storage: Storage;
9
+ get name(): string;
10
+ constructor(_storage: Storage);
11
+ clear(): void;
12
+ beginTransaction(): SyncRWTransaction;
13
+ get(key: Ino): Uint8Array | undefined;
14
+ put(key: Ino, data: Uint8Array, overwrite: boolean): boolean;
15
+ remove(key: Ino): void;
16
+ }
17
+ /**
18
+ * Options to pass to the StorageFileSystem
19
+ */
20
+ export interface StorageOptions {
21
+ /**
22
+ * The Storage to use. Defaults to globalThis.localStorage.
23
+ */
24
+ storage: Storage;
25
+ }
26
+ /**
27
+ * A synchronous file system backed by a `Storage` (e.g. localStorage).
28
+ */
29
+ export declare const Storage: Backend;
@@ -0,0 +1,68 @@
1
+ import { SimpleSyncRWTransaction, SyncStoreFS } from '@zenfs/core/backends/SyncStore.js';
2
+ import { ApiError, ErrorCode } from '@zenfs/core/ApiError.js';
3
+ import { decode, encode } from '@zenfs/core/utils.js';
4
+ /**
5
+ * A synchronous key-value store backed by Storage.
6
+ */
7
+ export class StorageStore {
8
+ get name() {
9
+ return Storage.name;
10
+ }
11
+ constructor(_storage) {
12
+ this._storage = _storage;
13
+ }
14
+ clear() {
15
+ this._storage.clear();
16
+ }
17
+ beginTransaction() {
18
+ // No need to differentiate.
19
+ return new SimpleSyncRWTransaction(this);
20
+ }
21
+ get(key) {
22
+ const data = this._storage.getItem(key.toString());
23
+ if (typeof data != 'string') {
24
+ return;
25
+ }
26
+ return encode(data);
27
+ }
28
+ put(key, data, overwrite) {
29
+ try {
30
+ if (!overwrite && this._storage.getItem(key.toString()) !== null) {
31
+ // Don't want to overwrite the key!
32
+ return false;
33
+ }
34
+ this._storage.setItem(key.toString(), decode(data));
35
+ return true;
36
+ }
37
+ catch (e) {
38
+ throw new ApiError(ErrorCode.ENOSPC, 'Storage is full.');
39
+ }
40
+ }
41
+ remove(key) {
42
+ try {
43
+ this._storage.removeItem(key.toString());
44
+ }
45
+ catch (e) {
46
+ throw new ApiError(ErrorCode.EIO, 'Unable to delete key ' + key + ': ' + e);
47
+ }
48
+ }
49
+ }
50
+ /**
51
+ * A synchronous file system backed by a `Storage` (e.g. localStorage).
52
+ */
53
+ export const Storage = {
54
+ name: 'Storage',
55
+ options: {
56
+ storage: {
57
+ type: 'object',
58
+ required: false,
59
+ description: 'The Storage to use. Defaults to globalThis.localStorage.',
60
+ },
61
+ },
62
+ isAvailable(storage = globalThis.localStorage) {
63
+ return storage instanceof globalThis.Storage;
64
+ },
65
+ create({ storage = globalThis.localStorage }) {
66
+ return new SyncStoreFS({ store: new StorageStore(storage) });
67
+ },
68
+ };