@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 +147 -7
- package/dist/extensions/element-ext.d.ts +3 -3
- package/dist/extensions/element-ext.d.ts.map +1 -1
- package/dist/extensions/element-ext.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/IndexedDbStore.d.ts +16 -0
- package/dist/utils/IndexedDbStore.d.ts.map +1 -0
- package/dist/utils/IndexedDbStore.js +89 -0
- package/dist/utils/IndexedDbStore.js.map +6 -0
- package/dist/utils/IndexedDbVirtualFs.d.ts +20 -0
- package/dist/utils/IndexedDbVirtualFs.d.ts.map +1 -0
- package/dist/utils/IndexedDbVirtualFs.js +87 -0
- package/dist/utils/IndexedDbVirtualFs.js.map +6 -0
- package/package.json +3 -3
- package/src/extensions/element-ext.ts +9 -9
- package/src/index.ts +2 -0
- package/src/utils/IndexedDbStore.ts +103 -0
- package/src/utils/IndexedDbVirtualFs.ts +99 -0
- package/tests/extensions/element-ext.spec.ts +0 -78
- package/tests/extensions/html-element-ext.spec.ts +0 -15
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,
|
|
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<
|
|
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<
|
|
40
|
+
findAll<TEl extends Element = Element>(selector: string): TEl[]
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
-
### `element.findFirst<
|
|
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<
|
|
60
|
+
findFirst<TEl extends Element = Element>(selector: string): TEl | undefined
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
-
### `element.prependChild<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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,
|
|
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": "AAAA,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAoF7B,QAAQ,UAAU,UAAU,
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
@@ -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.
|
|
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.
|
|
26
|
+
"@simplysm/core-common": "13.0.77"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"happy-dom": "^20.
|
|
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<
|
|
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<
|
|
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<
|
|
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 <
|
|
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<
|
|
89
|
+
return Array.from(this.querySelectorAll<TEl>(trimmed));
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
Element.prototype.findFirst = function <
|
|
92
|
+
Element.prototype.findFirst = function <TEl extends Element = Element>(
|
|
93
93
|
selector: string,
|
|
94
|
-
):
|
|
94
|
+
): TEl | undefined {
|
|
95
95
|
const trimmed = selector.trim();
|
|
96
96
|
if (trimmed === "") return undefined;
|
|
97
|
-
return this.querySelector<
|
|
97
|
+
return this.querySelector<TEl>(trimmed) ?? undefined;
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
Element.prototype.prependChild = function <
|
|
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
|
@@ -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 = `
|