@nkzw/fate-indexeddb 1.5.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,7 @@
1
+ import { PersistenceStorage } from "@nkzw/fate/persistence";
2
+ //#region src/index.d.ts
3
+ declare function indexedDB({ name }?: {
4
+ name?: string;
5
+ }): PersistenceStorage;
6
+ //#endregion
7
+ export { indexedDB };
package/lib/index.mjs ADDED
@@ -0,0 +1,137 @@
1
+ import { openDB } from "idb";
2
+ //#region src/index.ts
3
+ function indexedDB({ name = "fate" } = {}) {
4
+ let leases = 0;
5
+ let connection;
6
+ const release = () => {
7
+ if (--leases === 0) {
8
+ const previous = connection;
9
+ connection = void 0;
10
+ previous?.then((db) => db.close(), () => {});
11
+ }
12
+ };
13
+ const database = async () => {
14
+ leases++;
15
+ try {
16
+ return await (connection ??= openDB(name, 1, { upgrade(db) {
17
+ db.createObjectStore("fate");
18
+ } }));
19
+ } catch (error) {
20
+ release();
21
+ throw error;
22
+ }
23
+ };
24
+ const listeners = /* @__PURE__ */ new Map();
25
+ let channel;
26
+ const notify = (key) => {
27
+ for (const listener of listeners.get(key) ?? []) try {
28
+ listener();
29
+ } catch {}
30
+ };
31
+ const broadcast = (keys) => {
32
+ try {
33
+ const sender = channel ?? (typeof BroadcastChannel !== "undefined" ? new BroadcastChannel(`fate:${name}`) : void 0);
34
+ try {
35
+ sender?.postMessage(keys);
36
+ } finally {
37
+ if (sender !== channel) sender?.close();
38
+ }
39
+ } catch {}
40
+ for (const key of keys) notify(key);
41
+ };
42
+ return {
43
+ exclusive(key, run) {
44
+ if (typeof navigator === "undefined" || !navigator.locks) throw new Error("fate(indexeddb): Web Locks are required for safe durable writes across tabs.");
45
+ return navigator.locks.request(`fate:${JSON.stringify([name, key])}`, async () => {
46
+ leases++;
47
+ try {
48
+ return await run();
49
+ } finally {
50
+ release();
51
+ }
52
+ });
53
+ },
54
+ async read(key) {
55
+ const db = await database();
56
+ try {
57
+ return await db.get("fate", key);
58
+ } finally {
59
+ release();
60
+ }
61
+ },
62
+ async scan(prefix, after, limit = 64) {
63
+ const db = await database();
64
+ try {
65
+ const range = IDBKeyRange.lowerBound(after !== void 0 && after >= prefix ? after : prefix, after !== void 0 && after >= prefix);
66
+ const transaction = db.transaction("fate");
67
+ const entries = [];
68
+ let cursor = await transaction.store.openCursor(range);
69
+ while (cursor && String(cursor.key).startsWith(prefix) && entries.length < limit) {
70
+ entries.push({
71
+ key: String(cursor.key),
72
+ value: cursor.value
73
+ });
74
+ cursor = await cursor.continue();
75
+ }
76
+ await transaction.done;
77
+ return entries;
78
+ } finally {
79
+ release();
80
+ }
81
+ },
82
+ subscribe(key, listener) {
83
+ if (!channel && typeof BroadcastChannel !== "undefined") {
84
+ channel = new BroadcastChannel(`fate:${name}`);
85
+ channel.onmessage = ({ data }) => {
86
+ for (const key of Array.isArray(data) ? data : [data]) if (typeof key === "string") notify(key);
87
+ };
88
+ }
89
+ let set = listeners.get(key);
90
+ if (!set) {
91
+ set = /* @__PURE__ */ new Set();
92
+ listeners.set(key, set);
93
+ }
94
+ set.add(listener);
95
+ return () => {
96
+ set.delete(listener);
97
+ if (!set.size) listeners.delete(key);
98
+ if (!listeners.size) {
99
+ channel?.close();
100
+ channel = void 0;
101
+ }
102
+ };
103
+ },
104
+ async write(key, value) {
105
+ const db = await database();
106
+ try {
107
+ const transaction = db.transaction("fate", "readwrite");
108
+ await transaction.store.put(value, key);
109
+ await transaction.done;
110
+ } finally {
111
+ release();
112
+ }
113
+ broadcast([key]);
114
+ },
115
+ async writeBatch(entries) {
116
+ const db = await database();
117
+ try {
118
+ const transaction = db.transaction("fate", "readwrite");
119
+ try {
120
+ await Promise.all(entries.map(async ([key, value]) => value === void 0 ? transaction.store.delete(key) : transaction.store.put(value, key)));
121
+ await transaction.done;
122
+ } catch (error) {
123
+ try {
124
+ transaction.abort();
125
+ } catch {}
126
+ await transaction.done.catch(() => {});
127
+ throw error;
128
+ }
129
+ } finally {
130
+ release();
131
+ }
132
+ broadcast([...new Set(entries.map(([key]) => key))]);
133
+ }
134
+ };
135
+ }
136
+ //#endregion
137
+ export { indexedDB };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@nkzw/fate-indexeddb",
3
+ "version": "1.5.0",
4
+ "description": "Optional IndexedDB persistence adapter for fate.",
5
+ "license": "MIT",
6
+ "files": [
7
+ "lib"
8
+ ],
9
+ "type": "module",
10
+ "sideEffects": false,
11
+ "exports": {
12
+ ".": {
13
+ "@nkzw/source": "./src/index.ts",
14
+ "types": "./lib/index.d.mts",
15
+ "default": "./lib/index.mjs"
16
+ }
17
+ },
18
+ "dependencies": {
19
+ "idb": "^8.0.3"
20
+ },
21
+ "devDependencies": {
22
+ "fake-indexeddb": "^6.2.4",
23
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
24
+ "vite-plus": "0.3.0",
25
+ "@nkzw/fate": "1.5.0"
26
+ },
27
+ "peerDependencies": {
28
+ "@nkzw/fate": "^1.4.0"
29
+ },
30
+ "scripts": {
31
+ "build": "vp pack --tsconfig tsconfig.json -d lib src/index.ts"
32
+ }
33
+ }