@simplysm/core-browser 13.0.75 → 13.0.77

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Simplysm package - Core module (browser)
4
4
 
5
- Browser-only utilities including DOM element extensions, file download helpers, fetch utilities, and file dialog helpers.
5
+ Browser-only utilities including DOM element extensions, file download helpers, fetch utilities, file dialog helpers, and IndexedDB wrappers.
6
6
 
7
7
  ## Installation
8
8
 
@@ -22,7 +22,7 @@ import "@simplysm/core-browser";
22
22
 
23
23
  Extends the global `Element` interface with utility methods. These are activated as side effects when the package is imported.
24
24
 
25
- ### `element.findAll<T>(selector)`
25
+ ### `element.findAll<TEl>(selector)`
26
26
 
27
27
  Finds all child elements matching a CSS selector.
28
28
 
@@ -37,12 +37,12 @@ const items = containerEl.findAll<HTMLLIElement>("li.active");
37
37
  **Signature:**
38
38
 
39
39
  ```ts
40
- findAll<T extends Element = Element>(selector: string): T[]
40
+ findAll<TEl extends Element = Element>(selector: string): TEl[]
41
41
  ```
42
42
 
43
43
  ---
44
44
 
45
- ### `element.findFirst<T>(selector)`
45
+ ### `element.findFirst<TEl>(selector)`
46
46
 
47
47
  Finds the first child element matching a CSS selector.
48
48
 
@@ -57,12 +57,12 @@ const input = formEl.findFirst<HTMLInputElement>("input[name='email']");
57
57
  **Signature:**
58
58
 
59
59
  ```ts
60
- findFirst<T extends Element = Element>(selector: string): T | undefined
60
+ findFirst<TEl extends Element = Element>(selector: string): TEl | undefined
61
61
  ```
62
62
 
63
63
  ---
64
64
 
65
- ### `element.prependChild<T>(child)`
65
+ ### `element.prependChild<TEl>(child)`
66
66
 
67
67
  Inserts a child element as the first child of the element.
68
68
 
@@ -76,7 +76,7 @@ containerEl.prependChild(newEl);
76
76
  **Signature:**
77
77
 
78
78
  ```ts
79
- prependChild<T extends Element>(child: T): T
79
+ prependChild<TEl extends Element>(child: TEl): TEl
80
80
  ```
81
81
 
82
82
  ---
@@ -429,6 +429,116 @@ function openFileDialog(options?: {
429
429
 
430
430
  ---
431
431
 
432
+ ## IndexedDB Store
433
+
434
+ ### `IndexedDbStore`
435
+
436
+ A thin wrapper around the browser `IndexedDB` API for typed key-value object stores. Opens a versioned database, auto-creates stores on upgrade, and closes the connection after each operation.
437
+
438
+ ```ts
439
+ import { IndexedDbStore } from "@simplysm/core-browser";
440
+
441
+ const db = new IndexedDbStore("my-db", 1, [
442
+ { name: "users", keyPath: "id" },
443
+ ]);
444
+
445
+ await db.put("users", { id: 1, name: "Alice" });
446
+ const user = await db.get<{ id: number; name: string }>("users", 1);
447
+ const all = await db.getAll<{ id: number; name: string }>("users");
448
+ ```
449
+
450
+ **Constructor:**
451
+
452
+ ```ts
453
+ new IndexedDbStore(
454
+ dbName: string,
455
+ dbVersion: number,
456
+ storeConfigs: StoreConfig[],
457
+ )
458
+ ```
459
+
460
+ **Methods:**
461
+
462
+ ```ts
463
+ open(): Promise<IDBDatabase>
464
+
465
+ withStore<TResult>(
466
+ storeName: string,
467
+ mode: IDBTransactionMode,
468
+ fn: (store: IDBObjectStore) => Promise<TResult>,
469
+ ): Promise<TResult>
470
+
471
+ get<TValue>(storeName: string, key: IDBValidKey): Promise<TValue | undefined>
472
+
473
+ put(storeName: string, value: unknown): Promise<void>
474
+
475
+ getAll<TItem>(storeName: string): Promise<TItem[]>
476
+ ```
477
+
478
+ - `open()` — opens the database and returns the raw `IDBDatabase`. Creates missing object stores declared in `storeConfigs`. Rejects if the connection is blocked.
479
+ - `withStore()` — executes `fn` inside a single transaction on `storeName`. Aborts the transaction if `fn` throws. Closes the database when the transaction completes.
480
+ - `get()` — retrieves one record by `key` from `storeName`. Returns `undefined` if not found.
481
+ - `put()` — inserts or updates a record in `storeName`.
482
+ - `getAll()` — retrieves all records from `storeName`.
483
+
484
+ ---
485
+
486
+ ## IndexedDB Virtual Filesystem
487
+
488
+ ### `IndexedDbVirtualFs`
489
+
490
+ A virtual filesystem built on top of `IndexedDbStore`. Each entry is stored as a flat key (slash-separated path string) with a kind (`"file"` or `"dir"`) and optional base64 data payload.
491
+
492
+ ```ts
493
+ import { IndexedDbStore, IndexedDbVirtualFs } from "@simplysm/core-browser";
494
+
495
+ const db = new IndexedDbStore("my-fs-db", 1, [
496
+ { name: "fs", keyPath: "fullKey" },
497
+ ]);
498
+ const vfs = new IndexedDbVirtualFs(db, "fs", "fullKey");
499
+
500
+ await vfs.putEntry("/docs/readme.txt", "file", btoa("hello"));
501
+ const entry = await vfs.getEntry("/docs/readme.txt");
502
+ const children = await vfs.listChildren("/docs/");
503
+ await vfs.deleteByPrefix("/docs");
504
+ ```
505
+
506
+ **Constructor:**
507
+
508
+ ```ts
509
+ new IndexedDbVirtualFs(
510
+ db: IndexedDbStore,
511
+ storeName: string,
512
+ keyField: string,
513
+ )
514
+ ```
515
+
516
+ - `db` — an `IndexedDbStore` instance to use for persistence.
517
+ - `storeName` — the object store name within the database.
518
+ - `keyField` — the field name used as the primary key in stored records.
519
+
520
+ **Methods:**
521
+
522
+ ```ts
523
+ getEntry(fullKey: string): Promise<VirtualFsEntry | undefined>
524
+
525
+ putEntry(fullKey: string, kind: "file" | "dir", dataBase64?: string): Promise<void>
526
+
527
+ deleteByPrefix(keyPrefix: string): Promise<boolean>
528
+
529
+ listChildren(prefix: string): Promise<{ name: string; isDirectory: boolean }[]>
530
+
531
+ ensureDir(fullKeyBuilder: (path: string) => string, dirPath: string): Promise<void>
532
+ ```
533
+
534
+ - `getEntry()` — retrieves the entry for `fullKey`. Returns `undefined` if not found.
535
+ - `putEntry()` — writes or overwrites an entry with the given `kind` and optional base64 data.
536
+ - `deleteByPrefix()` — deletes all entries whose key equals `keyPrefix` or starts with `keyPrefix + "/"`. Returns `true` if at least one entry was deleted.
537
+ - `listChildren()` — lists the immediate children under `prefix`. Each result includes `name` (the path segment) and `isDirectory`.
538
+ - `ensureDir()` — creates directory entries for every path segment in `dirPath` if they do not already exist. `fullKeyBuilder` maps a path string to the full store key.
539
+
540
+ ---
541
+
432
542
  ## Types
433
543
 
434
544
  ### `ElementBounds`
@@ -464,3 +574,33 @@ interface DownloadProgress {
464
574
  contentLength: number;
465
575
  }
466
576
  ```
577
+
578
+ ---
579
+
580
+ ### `StoreConfig`
581
+
582
+ Configuration for a single object store within an `IndexedDbStore`.
583
+
584
+ ```ts
585
+ interface StoreConfig {
586
+ /** Object store name */
587
+ name: string;
588
+ /** Primary key field path */
589
+ keyPath: string;
590
+ }
591
+ ```
592
+
593
+ ---
594
+
595
+ ### `VirtualFsEntry`
596
+
597
+ A single entry stored in the `IndexedDbVirtualFs`.
598
+
599
+ ```ts
600
+ interface VirtualFsEntry {
601
+ /** Entry kind: "file" or "dir" */
602
+ kind: "file" | "dir";
603
+ /** File contents encoded as base64 (only present for file entries) */
604
+ dataBase64?: string;
605
+ }
606
+ ```
@@ -21,21 +21,21 @@ declare global {
21
21
  * @param selector - CSS selector
22
22
  * @returns Array of matching elements (empty selector returns empty array)
23
23
  */
24
- findAll<T extends Element = Element>(selector: string): T[];
24
+ findAll<TEl extends Element = Element>(selector: string): TEl[];
25
25
  /**
26
26
  * Find first element matching selector
27
27
  *
28
28
  * @param selector - CSS selector
29
29
  * @returns First matching element or undefined (empty selector returns undefined)
30
30
  */
31
- findFirst<T extends Element = Element>(selector: string): T | undefined;
31
+ findFirst<TEl extends Element = Element>(selector: string): TEl | undefined;
32
32
  /**
33
33
  * Insert element as first child
34
34
  *
35
35
  * @param child - Child element to insert
36
36
  * @returns Inserted child element
37
37
  */
38
- prependChild<T extends Element>(child: T): T;
38
+ prependChild<TEl extends Element>(child: TEl): TEl;
39
39
  /**
40
40
  * Get all parent elements (in order of proximity)
41
41
  *
@@ -1 +1 @@
1
- {"version":3,"file":"element-ext.d.ts","sourceRoot":"","sources":["..\\..\\src\\extensions\\element-ext.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,6BAA6B;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO;QACf;;;;;WAKG;QACH,OAAO,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;QAE5D;;;;;WAKG;QACH,SAAS,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;QAExE;;;;;WAKG;QACH,YAAY,CAAC,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;QAE7C;;;;WAIG;QACH,UAAU,IAAI,OAAO,EAAE,CAAC;QAExB;;;;WAIG;QACH,mBAAmB,IAAI,WAAW,GAAG,SAAS,CAAC;QAE/C;;;;WAIG;QACH,uBAAuB,IAAI,WAAW,GAAG,SAAS,CAAC;QAEnD;;;;WAIG;QACH,eAAe,IAAI,OAAO,CAAC;QAE3B;;;;;;;WAOG;QACH,SAAS,IAAI,OAAO,CAAC;KACtB;CACF;AAkED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAYvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAa1D;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,OAAO,GAAE,MAAa,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAoDhG"}
1
+ {"version":3,"file":"element-ext.d.ts","sourceRoot":"","sources":["..\\..\\src\\extensions\\element-ext.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,6BAA6B;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO;QACf;;;;;WAKG;QACH,OAAO,CAAC,GAAG,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,EAAE,CAAC;QAEhE;;;;;WAKG;QACH,SAAS,CAAC,GAAG,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC;QAE5E;;;;;WAKG;QACH,YAAY,CAAC,GAAG,SAAS,OAAO,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,CAAC;QAEnD;;;;WAIG;QACH,UAAU,IAAI,OAAO,EAAE,CAAC;QAExB;;;;WAIG;QACH,mBAAmB,IAAI,WAAW,GAAG,SAAS,CAAC;QAE/C;;;;WAIG;QACH,uBAAuB,IAAI,WAAW,GAAG,SAAS,CAAC;QAEnD;;;;WAIG;QACH,eAAe,IAAI,OAAO,CAAC;QAE3B;;;;;;;WAOG;QACH,SAAS,IAAI,OAAO,CAAC;KACtB;CACF;AAkED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAYvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAa1D;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,OAAO,GAAE,MAAa,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAoDhG"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/extensions/element-ext.ts"],
4
- "mappings": "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",
4
+ "mappings": "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",
5
5
  "names": []
6
6
  }
package/dist/index.d.ts CHANGED
@@ -5,4 +5,6 @@ export * from "./extensions/html-element-ext";
5
5
  export * from "./utils/download";
6
6
  export * from "./utils/fetch";
7
7
  export * from "./utils/file-dialog";
8
+ export * from "./utils/IndexedDbStore";
9
+ export * from "./utils/IndexedDbVirtualFs";
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["..\\src\\index.ts"],"names":[],"mappings":"AAGA,OAAO,0BAA0B,CAAC;AAClC,OAAO,+BAA+B,CAAC;AAGvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["..\\src\\index.ts"],"names":[],"mappings":"AAGA,OAAO,0BAA0B,CAAC;AAClC,OAAO,+BAA+B,CAAC;AAGvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -5,4 +5,6 @@ export * from "./extensions/html-element-ext.js";
5
5
  export * from "./utils/download.js";
6
6
  export * from "./utils/fetch.js";
7
7
  export * from "./utils/file-dialog.js";
8
+ export * from "./utils/IndexedDbStore.js";
9
+ export * from "./utils/IndexedDbVirtualFs.js";
8
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "mappings": "AAGA,OAAO;AACP,OAAO;AAGP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
4
+ "mappings": "AAGA,OAAO;AACP,OAAO;AAGP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
5
5
  "names": []
6
6
  }
@@ -0,0 +1,16 @@
1
+ export interface StoreConfig {
2
+ name: string;
3
+ keyPath: string;
4
+ }
5
+ export declare class IndexedDbStore {
6
+ private readonly _dbName;
7
+ private readonly _dbVersion;
8
+ private readonly _storeConfigs;
9
+ constructor(_dbName: string, _dbVersion: number, _storeConfigs: StoreConfig[]);
10
+ open(): Promise<IDBDatabase>;
11
+ withStore<TResult>(storeName: string, mode: IDBTransactionMode, fn: (store: IDBObjectStore) => Promise<TResult>): Promise<TResult>;
12
+ get<TValue>(storeName: string, key: IDBValidKey): Promise<TValue | undefined>;
13
+ put(storeName: string, value: unknown): Promise<void>;
14
+ getAll<TItem>(storeName: string): Promise<TItem[]>;
15
+ }
16
+ //# sourceMappingURL=IndexedDbStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IndexedDbStore.d.ts","sourceRoot":"","sources":["..\\..\\src\\utils\\IndexedDbStore.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAFb,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,WAAW,EAAE;IAGzC,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC;IAiB5B,SAAS,CAAC,OAAO,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,kBAAkB,EACxB,EAAE,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,GAC9C,OAAO,CAAC,OAAO,CAAC;IAwCb,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAU7E,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAUrD,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;CASzD"}
@@ -0,0 +1,89 @@
1
+ class IndexedDbStore {
2
+ constructor(_dbName, _dbVersion, _storeConfigs) {
3
+ this._dbName = _dbName;
4
+ this._dbVersion = _dbVersion;
5
+ this._storeConfigs = _storeConfigs;
6
+ }
7
+ async open() {
8
+ return new Promise((resolve, reject) => {
9
+ const req = indexedDB.open(this._dbName, this._dbVersion);
10
+ req.onupgradeneeded = () => {
11
+ const db = req.result;
12
+ for (const config of this._storeConfigs) {
13
+ if (!db.objectStoreNames.contains(config.name)) {
14
+ db.createObjectStore(config.name, { keyPath: config.keyPath });
15
+ }
16
+ }
17
+ };
18
+ req.onsuccess = () => resolve(req.result);
19
+ req.onerror = () => reject(req.error);
20
+ req.onblocked = () => reject(new Error("Database blocked by another connection"));
21
+ });
22
+ }
23
+ async withStore(storeName, mode, fn) {
24
+ const db = await this.open();
25
+ try {
26
+ const tx = db.transaction(storeName, mode);
27
+ const store = tx.objectStore(storeName);
28
+ let result;
29
+ let fnError;
30
+ try {
31
+ result = await fn(store);
32
+ } catch (err) {
33
+ fnError = err;
34
+ tx.abort();
35
+ }
36
+ return await new Promise((resolve, reject) => {
37
+ if (fnError !== void 0) {
38
+ tx.onerror = () => {
39
+ db.close();
40
+ reject(fnError);
41
+ };
42
+ } else {
43
+ tx.oncomplete = () => {
44
+ db.close();
45
+ resolve(result);
46
+ };
47
+ tx.onerror = () => {
48
+ db.close();
49
+ reject(tx.error);
50
+ };
51
+ }
52
+ });
53
+ } catch (err) {
54
+ db.close();
55
+ throw err;
56
+ }
57
+ }
58
+ async get(storeName, key) {
59
+ return this.withStore(storeName, "readonly", async (store) => {
60
+ return new Promise((resolve, reject) => {
61
+ const req = store.get(key);
62
+ req.onsuccess = () => resolve(req.result);
63
+ req.onerror = () => reject(req.error);
64
+ });
65
+ });
66
+ }
67
+ async put(storeName, value) {
68
+ return this.withStore(storeName, "readwrite", async (store) => {
69
+ return new Promise((resolve, reject) => {
70
+ const req = store.put(value);
71
+ req.onsuccess = () => resolve();
72
+ req.onerror = () => reject(req.error);
73
+ });
74
+ });
75
+ }
76
+ async getAll(storeName) {
77
+ return this.withStore(storeName, "readonly", async (store) => {
78
+ return new Promise((resolve, reject) => {
79
+ const req = store.getAll();
80
+ req.onsuccess = () => resolve(req.result);
81
+ req.onerror = () => reject(req.error);
82
+ });
83
+ });
84
+ }
85
+ }
86
+ export {
87
+ IndexedDbStore
88
+ };
89
+ //# sourceMappingURL=IndexedDbStore.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/IndexedDbStore.ts"],
4
+ "mappings": "AAKO,MAAM,eAAe;AAAA,EAC1B,YACmB,SACA,YACA,eACjB;AAHiB;AACA;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,OAA6B;AACjC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,MAAM,UAAU,KAAK,KAAK,SAAS,KAAK,UAAU;AACxD,UAAI,kBAAkB,MAAM;AAC1B,cAAM,KAAK,IAAI;AACf,mBAAW,UAAU,KAAK,eAAe;AACvC,cAAI,CAAC,GAAG,iBAAiB,SAAS,OAAO,IAAI,GAAG;AAC9C,eAAG,kBAAkB,OAAO,MAAM,EAAE,SAAS,OAAO,QAAQ,CAAC;AAAA,UAC/D;AAAA,QACF;AAAA,MACF;AACA,UAAI,YAAY,MAAM,QAAQ,IAAI,MAAM;AACxC,UAAI,UAAU,MAAM,OAAO,IAAI,KAAK;AACpC,UAAI,YAAY,MAAM,OAAO,IAAI,MAAM,wCAAwC,CAAC;AAAA,IAClF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UACJ,WACA,MACA,IACkB;AAClB,UAAM,KAAK,MAAM,KAAK,KAAK;AAC3B,QAAI;AACF,YAAM,KAAK,GAAG,YAAY,WAAW,IAAI;AACzC,YAAM,QAAQ,GAAG,YAAY,SAAS;AAGtC,UAAI;AACJ,UAAI;AACJ,UAAI;AACF,iBAAS,MAAM,GAAG,KAAK;AAAA,MACzB,SAAS,KAAK;AACZ,kBAAU;AACV,WAAG,MAAM;AAAA,MACX;AAGA,aAAO,MAAM,IAAI,QAAiB,CAAC,SAAS,WAAW;AACrD,YAAI,YAAY,QAAW;AACzB,aAAG,UAAU,MAAM;AACjB,eAAG,MAAM;AACT,mBAAO,OAAO;AAAA,UAChB;AAAA,QACF,OAAO;AACL,aAAG,aAAa,MAAM;AACpB,eAAG,MAAM;AACT,oBAAQ,MAAO;AAAA,UACjB;AACA,aAAG,UAAU,MAAM;AACjB,eAAG,MAAM;AACT,mBAAO,GAAG,KAAK;AAAA,UACjB;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,SAAG,MAAM;AACT,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,IAAY,WAAmB,KAA+C;AAClF,WAAO,KAAK,UAAU,WAAW,YAAY,OAAO,UAAU;AAC5D,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAM,MAAM,MAAM,IAAI,GAAG;AACzB,YAAI,YAAY,MAAM,QAAQ,IAAI,MAA4B;AAC9D,YAAI,UAAU,MAAM,OAAO,IAAI,KAAK;AAAA,MACtC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,IAAI,WAAmB,OAA+B;AAC1D,WAAO,KAAK,UAAU,WAAW,aAAa,OAAO,UAAU;AAC7D,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,YAAI,YAAY,MAAM,QAAQ;AAC9B,YAAI,UAAU,MAAM,OAAO,IAAI,KAAK;AAAA,MACtC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAc,WAAqC;AACvD,WAAO,KAAK,UAAU,WAAW,YAAY,OAAO,UAAU;AAC5D,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAM,MAAM,MAAM,OAAO;AACzB,YAAI,YAAY,MAAM,QAAQ,IAAI,MAAiB;AACnD,YAAI,UAAU,MAAM,OAAO,IAAI,KAAK;AAAA,MACtC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;",
5
+ "names": []
6
+ }
@@ -0,0 +1,20 @@
1
+ import type { IndexedDbStore } from "./IndexedDbStore";
2
+ export interface VirtualFsEntry {
3
+ kind: "file" | "dir";
4
+ dataBase64?: string;
5
+ }
6
+ export declare class IndexedDbVirtualFs {
7
+ private readonly _db;
8
+ private readonly _storeName;
9
+ private readonly _keyField;
10
+ constructor(_db: IndexedDbStore, _storeName: string, _keyField: string);
11
+ getEntry(fullKey: string): Promise<VirtualFsEntry | undefined>;
12
+ putEntry(fullKey: string, kind: "file" | "dir", dataBase64?: string): Promise<void>;
13
+ deleteByPrefix(keyPrefix: string): Promise<boolean>;
14
+ listChildren(prefix: string): Promise<{
15
+ name: string;
16
+ isDirectory: boolean;
17
+ }[]>;
18
+ ensureDir(fullKeyBuilder: (path: string) => string, dirPath: string): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=IndexedDbVirtualFs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IndexedDbVirtualFs.d.ts","sourceRoot":"","sources":["..\\..\\src\\utils\\IndexedDbVirtualFs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,GAAG,EAAE,cAAc,EACnB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM;IAG9B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAI9D,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInF,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAuBnD,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAmC/E,SAAS,CACb,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACxC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;CAejB"}
@@ -0,0 +1,87 @@
1
+ class IndexedDbVirtualFs {
2
+ constructor(_db, _storeName, _keyField) {
3
+ this._db = _db;
4
+ this._storeName = _storeName;
5
+ this._keyField = _keyField;
6
+ }
7
+ async getEntry(fullKey) {
8
+ return this._db.get(this._storeName, fullKey);
9
+ }
10
+ async putEntry(fullKey, kind, dataBase64) {
11
+ await this._db.put(this._storeName, { [this._keyField]: fullKey, kind, dataBase64 });
12
+ }
13
+ async deleteByPrefix(keyPrefix) {
14
+ return this._db.withStore(this._storeName, "readwrite", async (store) => {
15
+ return new Promise((resolve, reject) => {
16
+ const req = store.openCursor();
17
+ let found = false;
18
+ req.onsuccess = () => {
19
+ const cursor = req.result;
20
+ if (!cursor) {
21
+ resolve(found);
22
+ return;
23
+ }
24
+ const key = String(cursor.key);
25
+ if (key === keyPrefix || key.startsWith(keyPrefix + "/")) {
26
+ found = true;
27
+ cursor.delete();
28
+ }
29
+ cursor.continue();
30
+ };
31
+ req.onerror = () => reject(req.error);
32
+ });
33
+ });
34
+ }
35
+ async listChildren(prefix) {
36
+ return this._db.withStore(this._storeName, "readonly", async (store) => {
37
+ return new Promise((resolve, reject) => {
38
+ const req = store.openCursor();
39
+ const map = /* @__PURE__ */ new Map();
40
+ req.onsuccess = () => {
41
+ const cursor = req.result;
42
+ if (!cursor) {
43
+ resolve(
44
+ Array.from(map.entries()).map(([name, isDirectory]) => ({ name, isDirectory }))
45
+ );
46
+ return;
47
+ }
48
+ const key = String(cursor.key);
49
+ if (key.startsWith(prefix)) {
50
+ const rest = key.slice(prefix.length);
51
+ if (rest) {
52
+ const segments = rest.split("/").filter(Boolean);
53
+ if (segments.length > 0) {
54
+ const firstSeg = segments[0];
55
+ if (!map.has(firstSeg)) {
56
+ const isDir = segments.length > 1 || cursor.value.kind === "dir";
57
+ map.set(firstSeg, isDir);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ cursor.continue();
63
+ };
64
+ req.onerror = () => reject(req.error);
65
+ });
66
+ });
67
+ }
68
+ async ensureDir(fullKeyBuilder, dirPath) {
69
+ if (dirPath === "/") {
70
+ await this.putEntry(fullKeyBuilder("/"), "dir");
71
+ return;
72
+ }
73
+ const segments = dirPath.split("/").filter(Boolean);
74
+ let acc = "";
75
+ for (const seg of segments) {
76
+ acc += "/" + seg;
77
+ const existing = await this.getEntry(fullKeyBuilder(acc));
78
+ if (!existing) {
79
+ await this.putEntry(fullKeyBuilder(acc), "dir");
80
+ }
81
+ }
82
+ }
83
+ }
84
+ export {
85
+ IndexedDbVirtualFs
86
+ };
87
+ //# sourceMappingURL=IndexedDbVirtualFs.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/IndexedDbVirtualFs.ts"],
4
+ "mappings": "AAOO,MAAM,mBAAmB;AAAA,EAC9B,YACmB,KACA,YACA,WACjB;AAHiB;AACA;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,SAAS,SAAsD;AACnE,WAAO,KAAK,IAAI,IAAoB,KAAK,YAAY,OAAO;AAAA,EAC9D;AAAA,EAEA,MAAM,SAAS,SAAiB,MAAsB,YAAoC;AACxF,UAAM,KAAK,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC,KAAK,SAAS,GAAG,SAAS,MAAM,WAAW,CAAC;AAAA,EACrF;AAAA,EAEA,MAAM,eAAe,WAAqC;AACxD,WAAO,KAAK,IAAI,UAAU,KAAK,YAAY,aAAa,OAAO,UAAU;AACvE,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAM,MAAM,MAAM,WAAW;AAC7B,YAAI,QAAQ;AACZ,YAAI,YAAY,MAAM;AACpB,gBAAM,SAAS,IAAI;AACnB,cAAI,CAAC,QAAQ;AACX,oBAAQ,KAAK;AACb;AAAA,UACF;AACA,gBAAM,MAAM,OAAO,OAAO,GAAG;AAC7B,cAAI,QAAQ,aAAa,IAAI,WAAW,YAAY,GAAG,GAAG;AACxD,oBAAQ;AACR,mBAAO,OAAO;AAAA,UAChB;AACA,iBAAO,SAAS;AAAA,QAClB;AACA,YAAI,UAAU,MAAM,OAAO,IAAI,KAAK;AAAA,MACtC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,QAAmE;AACpF,WAAO,KAAK,IAAI,UAAU,KAAK,YAAY,YAAY,OAAO,UAAU;AACtE,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAM,MAAM,MAAM,WAAW;AAC7B,cAAM,MAAM,oBAAI,IAAqB;AACrC,YAAI,YAAY,MAAM;AACpB,gBAAM,SAAS,IAAI;AACnB,cAAI,CAAC,QAAQ;AACX;AAAA,cACE,MAAM,KAAK,IAAI,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,WAAW,OAAO,EAAE,MAAM,YAAY,EAAE;AAAA,YAChF;AACA;AAAA,UACF;AACA,gBAAM,MAAM,OAAO,OAAO,GAAG;AAC7B,cAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,kBAAM,OAAO,IAAI,MAAM,OAAO,MAAM;AACpC,gBAAI,MAAM;AACR,oBAAM,WAAW,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAC/C,kBAAI,SAAS,SAAS,GAAG;AACvB,sBAAM,WAAW,SAAS,CAAC;AAC3B,oBAAI,CAAC,IAAI,IAAI,QAAQ,GAAG;AACtB,wBAAM,QACJ,SAAS,SAAS,KAAM,OAAO,MAAyB,SAAS;AACnE,sBAAI,IAAI,UAAU,KAAK;AAAA,gBACzB;AAAA,cACF;AAAA,YACF;AAAA,UACF;AACA,iBAAO,SAAS;AAAA,QAClB;AACA,YAAI,UAAU,MAAM,OAAO,IAAI,KAAK;AAAA,MACtC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UACJ,gBACA,SACe;AACf,QAAI,YAAY,KAAK;AACnB,YAAM,KAAK,SAAS,eAAe,GAAG,GAAG,KAAK;AAC9C;AAAA,IACF;AACA,UAAM,WAAW,QAAQ,MAAM,GAAG,EAAE,OAAO,OAAO;AAClD,QAAI,MAAM;AACV,eAAW,OAAO,UAAU;AAC1B,aAAO,MAAM;AACb,YAAM,WAAW,MAAM,KAAK,SAAS,eAAe,GAAG,CAAC;AACxD,UAAI,CAAC,UAAU;AACb,cAAM,KAAK,SAAS,eAAe,GAAG,GAAG,KAAK;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AACF;",
5
+ "names": []
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/core-browser",
3
- "version": "13.0.75",
3
+ "version": "13.0.77",
4
4
  "description": "Simplysm package - Core module (browser)",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
@@ -23,9 +23,9 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "tabbable": "^6.4.0",
26
- "@simplysm/core-common": "13.0.75"
26
+ "@simplysm/core-common": "13.0.77"
27
27
  },
28
28
  "devDependencies": {
29
- "happy-dom": "^20.7.0"
29
+ "happy-dom": "^20.8.3"
30
30
  }
31
31
  }
@@ -25,7 +25,7 @@ declare global {
25
25
  * @param selector - CSS selector
26
26
  * @returns Array of matching elements (empty selector returns empty array)
27
27
  */
28
- findAll<T extends Element = Element>(selector: string): T[];
28
+ findAll<TEl extends Element = Element>(selector: string): TEl[];
29
29
 
30
30
  /**
31
31
  * Find first element matching selector
@@ -33,7 +33,7 @@ declare global {
33
33
  * @param selector - CSS selector
34
34
  * @returns First matching element or undefined (empty selector returns undefined)
35
35
  */
36
- findFirst<T extends Element = Element>(selector: string): T | undefined;
36
+ findFirst<TEl extends Element = Element>(selector: string): TEl | undefined;
37
37
 
38
38
  /**
39
39
  * Insert element as first child
@@ -41,7 +41,7 @@ declare global {
41
41
  * @param child - Child element to insert
42
42
  * @returns Inserted child element
43
43
  */
44
- prependChild<T extends Element>(child: T): T;
44
+ prependChild<TEl extends Element>(child: TEl): TEl;
45
45
 
46
46
  /**
47
47
  * Get all parent elements (in order of proximity)
@@ -83,21 +83,21 @@ declare global {
83
83
  }
84
84
  }
85
85
 
86
- Element.prototype.findAll = function <T extends Element = Element>(selector: string): T[] {
86
+ Element.prototype.findAll = function <TEl extends Element = Element>(selector: string): TEl[] {
87
87
  const trimmed = selector.trim();
88
88
  if (trimmed === "") return [];
89
- return Array.from(this.querySelectorAll<T>(trimmed));
89
+ return Array.from(this.querySelectorAll<TEl>(trimmed));
90
90
  };
91
91
 
92
- Element.prototype.findFirst = function <T extends Element = Element>(
92
+ Element.prototype.findFirst = function <TEl extends Element = Element>(
93
93
  selector: string,
94
- ): T | undefined {
94
+ ): TEl | undefined {
95
95
  const trimmed = selector.trim();
96
96
  if (trimmed === "") return undefined;
97
- return this.querySelector<T>(trimmed) ?? undefined;
97
+ return this.querySelector<TEl>(trimmed) ?? undefined;
98
98
  };
99
99
 
100
- Element.prototype.prependChild = function <T extends Element>(child: T): T {
100
+ Element.prototype.prependChild = function <TEl extends Element>(child: TEl): TEl {
101
101
  return this.insertBefore(child, this.firstElementChild);
102
102
  };
103
103
 
package/src/index.ts CHANGED
@@ -10,3 +10,5 @@ export * from "./extensions/html-element-ext";
10
10
  export * from "./utils/download";
11
11
  export * from "./utils/fetch";
12
12
  export * from "./utils/file-dialog";
13
+ export * from "./utils/IndexedDbStore";
14
+ export * from "./utils/IndexedDbVirtualFs";
@@ -0,0 +1,103 @@
1
+ export interface StoreConfig {
2
+ name: string;
3
+ keyPath: string;
4
+ }
5
+
6
+ export class IndexedDbStore {
7
+ constructor(
8
+ private readonly _dbName: string,
9
+ private readonly _dbVersion: number,
10
+ private readonly _storeConfigs: StoreConfig[],
11
+ ) {}
12
+
13
+ async open(): Promise<IDBDatabase> {
14
+ return new Promise((resolve, reject) => {
15
+ const req = indexedDB.open(this._dbName, this._dbVersion);
16
+ req.onupgradeneeded = () => {
17
+ const db = req.result;
18
+ for (const config of this._storeConfigs) {
19
+ if (!db.objectStoreNames.contains(config.name)) {
20
+ db.createObjectStore(config.name, { keyPath: config.keyPath });
21
+ }
22
+ }
23
+ };
24
+ req.onsuccess = () => resolve(req.result);
25
+ req.onerror = () => reject(req.error);
26
+ req.onblocked = () => reject(new Error("Database blocked by another connection"));
27
+ });
28
+ }
29
+
30
+ async withStore<TResult>(
31
+ storeName: string,
32
+ mode: IDBTransactionMode,
33
+ fn: (store: IDBObjectStore) => Promise<TResult>,
34
+ ): Promise<TResult> {
35
+ const db = await this.open();
36
+ try {
37
+ const tx = db.transaction(storeName, mode);
38
+ const store = tx.objectStore(storeName);
39
+
40
+ // Await fn result first
41
+ let result: TResult;
42
+ let fnError: unknown;
43
+ try {
44
+ result = await fn(store);
45
+ } catch (err) {
46
+ fnError = err;
47
+ tx.abort();
48
+ }
49
+
50
+ // Wait for transaction completion
51
+ return await new Promise<TResult>((resolve, reject) => {
52
+ if (fnError !== undefined) {
53
+ tx.onerror = () => {
54
+ db.close();
55
+ reject(fnError);
56
+ };
57
+ } else {
58
+ tx.oncomplete = () => {
59
+ db.close();
60
+ resolve(result!);
61
+ };
62
+ tx.onerror = () => {
63
+ db.close();
64
+ reject(tx.error);
65
+ };
66
+ }
67
+ });
68
+ } catch (err) {
69
+ db.close();
70
+ throw err;
71
+ }
72
+ }
73
+
74
+ async get<TValue>(storeName: string, key: IDBValidKey): Promise<TValue | undefined> {
75
+ return this.withStore(storeName, "readonly", async (store) => {
76
+ return new Promise((resolve, reject) => {
77
+ const req = store.get(key);
78
+ req.onsuccess = () => resolve(req.result as TValue | undefined);
79
+ req.onerror = () => reject(req.error);
80
+ });
81
+ });
82
+ }
83
+
84
+ async put(storeName: string, value: unknown): Promise<void> {
85
+ return this.withStore(storeName, "readwrite", async (store) => {
86
+ return new Promise((resolve, reject) => {
87
+ const req = store.put(value);
88
+ req.onsuccess = () => resolve();
89
+ req.onerror = () => reject(req.error);
90
+ });
91
+ });
92
+ }
93
+
94
+ async getAll<TItem>(storeName: string): Promise<TItem[]> {
95
+ return this.withStore(storeName, "readonly", async (store) => {
96
+ return new Promise((resolve, reject) => {
97
+ const req = store.getAll();
98
+ req.onsuccess = () => resolve(req.result as TItem[]);
99
+ req.onerror = () => reject(req.error);
100
+ });
101
+ });
102
+ }
103
+ }
@@ -0,0 +1,99 @@
1
+ import type { IndexedDbStore } from "./IndexedDbStore";
2
+
3
+ export interface VirtualFsEntry {
4
+ kind: "file" | "dir";
5
+ dataBase64?: string;
6
+ }
7
+
8
+ export class IndexedDbVirtualFs {
9
+ constructor(
10
+ private readonly _db: IndexedDbStore,
11
+ private readonly _storeName: string,
12
+ private readonly _keyField: string,
13
+ ) {}
14
+
15
+ async getEntry(fullKey: string): Promise<VirtualFsEntry | undefined> {
16
+ return this._db.get<VirtualFsEntry>(this._storeName, fullKey);
17
+ }
18
+
19
+ async putEntry(fullKey: string, kind: "file" | "dir", dataBase64?: string): Promise<void> {
20
+ await this._db.put(this._storeName, { [this._keyField]: fullKey, kind, dataBase64 });
21
+ }
22
+
23
+ async deleteByPrefix(keyPrefix: string): Promise<boolean> {
24
+ return this._db.withStore(this._storeName, "readwrite", async (store) => {
25
+ return new Promise((resolve, reject) => {
26
+ const req = store.openCursor();
27
+ let found = false;
28
+ req.onsuccess = () => {
29
+ const cursor = req.result;
30
+ if (!cursor) {
31
+ resolve(found);
32
+ return;
33
+ }
34
+ const key = String(cursor.key);
35
+ if (key === keyPrefix || key.startsWith(keyPrefix + "/")) {
36
+ found = true;
37
+ cursor.delete();
38
+ }
39
+ cursor.continue();
40
+ };
41
+ req.onerror = () => reject(req.error);
42
+ });
43
+ });
44
+ }
45
+
46
+ async listChildren(prefix: string): Promise<{ name: string; isDirectory: boolean }[]> {
47
+ return this._db.withStore(this._storeName, "readonly", async (store) => {
48
+ return new Promise((resolve, reject) => {
49
+ const req = store.openCursor();
50
+ const map = new Map<string, boolean>();
51
+ req.onsuccess = () => {
52
+ const cursor = req.result;
53
+ if (!cursor) {
54
+ resolve(
55
+ Array.from(map.entries()).map(([name, isDirectory]) => ({ name, isDirectory })),
56
+ );
57
+ return;
58
+ }
59
+ const key = String(cursor.key);
60
+ if (key.startsWith(prefix)) {
61
+ const rest = key.slice(prefix.length);
62
+ if (rest) {
63
+ const segments = rest.split("/").filter(Boolean);
64
+ if (segments.length > 0) {
65
+ const firstSeg = segments[0];
66
+ if (!map.has(firstSeg)) {
67
+ const isDir =
68
+ segments.length > 1 || (cursor.value as VirtualFsEntry).kind === "dir";
69
+ map.set(firstSeg, isDir);
70
+ }
71
+ }
72
+ }
73
+ }
74
+ cursor.continue();
75
+ };
76
+ req.onerror = () => reject(req.error);
77
+ });
78
+ });
79
+ }
80
+
81
+ async ensureDir(
82
+ fullKeyBuilder: (path: string) => string,
83
+ dirPath: string,
84
+ ): Promise<void> {
85
+ if (dirPath === "/") {
86
+ await this.putEntry(fullKeyBuilder("/"), "dir");
87
+ return;
88
+ }
89
+ const segments = dirPath.split("/").filter(Boolean);
90
+ let acc = "";
91
+ for (const seg of segments) {
92
+ acc += "/" + seg;
93
+ const existing = await this.getEntry(fullKeyBuilder(acc));
94
+ if (!existing) {
95
+ await this.putEntry(fullKeyBuilder(acc), "dir");
96
+ }
97
+ }
98
+ }
99
+ }
@@ -75,26 +75,6 @@ describe("Element prototype extensions", () => {
75
75
  const result = container.findAll("");
76
76
  expect(result).toEqual([]);
77
77
  });
78
-
79
- it("returns empty array for whitespace-only selector", () => {
80
- container.innerHTML = `<div class="item">1</div>`;
81
- const result = container.findAll(" ");
82
- expect(result).toEqual([]);
83
- });
84
-
85
- it("handles comma in attribute selector", () => {
86
- container.innerHTML = `
87
- <div data-values="a,b,c">1</div>
88
- <div class="item">2</div>
89
- `;
90
-
91
- // When comma is in attribute selector
92
- // Current implementation may split incorrectly, single selector recommended
93
- const result = container.findAll('[data-values="a,b,c"]');
94
-
95
- // Edge case behavior verification
96
- expect(result.length).toBeGreaterThanOrEqual(0);
97
- });
98
78
  });
99
79
 
100
80
  describe("findFirst", () => {
@@ -119,12 +99,6 @@ describe("Element prototype extensions", () => {
119
99
  const result = container.findFirst("");
120
100
  expect(result).toBeUndefined();
121
101
  });
122
-
123
- it("returns undefined for whitespace-only selector", () => {
124
- container.innerHTML = `<div class="item">1</div>`;
125
- const result = container.findFirst(" ");
126
- expect(result).toBeUndefined();
127
- });
128
102
  });
129
103
 
130
104
  describe("getParents", () => {
@@ -226,24 +200,6 @@ describe("Element prototype extensions", () => {
226
200
  container.style.display = "none";
227
201
  expect(container.isVisible()).toBe(false);
228
202
  });
229
-
230
- it("determines visibility of element with zero width", () => {
231
- container.style.width = "0";
232
- container.style.height = "100px";
233
- // When getClientRects().length is 0, it is not visible
234
- const isVisible = container.isVisible();
235
- // May vary depending on browser, verify it is boolean type
236
- expect(typeof isVisible).toBe("boolean");
237
- });
238
-
239
- it("determines visibility of element with zero height", () => {
240
- container.style.width = "100px";
241
- container.style.height = "0";
242
- // When getClientRects().length is 0, it is not visible
243
- const isVisible = container.isVisible();
244
- // May vary depending on browser, verify it is boolean type
245
- expect(typeof isVisible).toBe("boolean");
246
- });
247
203
  });
248
204
 
249
205
  describe("copyElement", () => {
@@ -639,40 +595,6 @@ describe("Element prototype extensions", () => {
639
595
  vi.unstubAllGlobals();
640
596
  });
641
597
 
642
- it("supports custom timeout setting", async () => {
643
- const mockObserver = {
644
- observe: vi.fn(),
645
- disconnect: vi.fn(),
646
- };
647
-
648
- const MockIntersectionObserver = vi.fn(function (
649
- this: IntersectionObserver,
650
- callback: IntersectionObserverCallback,
651
- ) {
652
- // Responds after 100ms
653
- setTimeout(() => {
654
- callback(
655
- [
656
- {
657
- target: container,
658
- boundingClientRect: { top: 0, left: 0, width: 10, height: 10 },
659
- },
660
- ] as unknown as IntersectionObserverEntry[],
661
- this,
662
- );
663
- }, 100);
664
- return mockObserver;
665
- });
666
-
667
- vi.stubGlobal("IntersectionObserver", MockIntersectionObserver);
668
-
669
- // Should succeed with 200ms timeout
670
- const result = await getBounds([container], 200);
671
- expect(result.length).toBe(1);
672
-
673
- vi.unstubAllGlobals();
674
- });
675
-
676
598
  it("collects all results even when callback is called multiple times", async () => {
677
599
  const el1 = document.createElement("div");
678
600
  const el2 = document.createElement("div");
@@ -59,21 +59,6 @@ describe("HTMLElement prototype extensions", () => {
59
59
  expect(() => child.getRelativeOffset(".not-exist")).toThrow(ArgumentError);
60
60
  });
61
61
 
62
- it("handles elements with transforms", () => {
63
- container.style.position = "relative";
64
- container.style.transform = "scale(2)";
65
- container.innerHTML = `<div id="child" style="transform: rotate(45deg);"></div>`;
66
-
67
- const child = container.querySelector<HTMLElement>("#child")!;
68
-
69
- // Should return result without error even with transforms
70
- const result = child.getRelativeOffset(container);
71
- expect(result).toHaveProperty("top");
72
- expect(result).toHaveProperty("left");
73
- expect(typeof result.top).toBe("number");
74
- expect(typeof result.left).toBe("number");
75
- });
76
-
77
62
  it("accumulates border width of intermediate elements", () => {
78
63
  container.style.position = "relative";
79
64
  container.innerHTML = `