@vezham/hooks 26.2.0 → 26.3.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.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/lib/use-indexed-db-store/database.d.ts +6 -0
- package/dist/lib/use-indexed-db-store/database.d.ts.map +1 -0
- package/dist/lib/use-indexed-db-store/database.js +83 -0
- package/dist/lib/use-indexed-db-store/index.d.ts +13 -0
- package/dist/lib/use-indexed-db-store/index.d.ts.map +1 -0
- package/dist/lib/use-indexed-db-store/index.js +20 -0
- package/dist/lib/use-indexed-db-store/indexed-db-store.d.ts +23 -0
- package/dist/lib/use-indexed-db-store/indexed-db-store.d.ts.map +1 -0
- package/dist/lib/use-indexed-db-store/indexed-db-store.js +131 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { AppContext, useApp, type AppIdentity } from './lib/use-app';
|
|
|
2
2
|
export { CommandContext, useCommand } from './lib/use-command';
|
|
3
3
|
export { useLocalStore, type LocalStoreOptions } from './lib/use-local-store';
|
|
4
4
|
export { useCopyToClipboard, type CopyToClipboardStatus } from './lib/use-copy-to-clipboard';
|
|
5
|
+
export { useIndexedDBStore, type IndexedDBMutationOptions, type IndexedDBStoreOptions } from './lib/use-indexed-db-store';
|
|
5
6
|
//# 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":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AACpE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC7E,OAAO,EACL,kBAAkB,EAClB,KAAK,qBAAqB,EAC3B,MAAM,6BAA6B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AACpE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC7E,OAAO,EACL,kBAAkB,EAClB,KAAK,qBAAqB,EAC3B,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EACL,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC3B,MAAM,4BAA4B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -2,4 +2,5 @@ import { AppContext, useApp } from "./lib/use-app/index.js";
|
|
|
2
2
|
import { CommandContext, useCommand } from "./lib/use-command/index.js";
|
|
3
3
|
import { useLocalStore } from "./lib/use-local-store/index.js";
|
|
4
4
|
import { useCopyToClipboard } from "./lib/use-copy-to-clipboard/index.js";
|
|
5
|
-
|
|
5
|
+
import { useIndexedDBStore } from "./lib/use-indexed-db-store/index.js";
|
|
6
|
+
export { AppContext, CommandContext, useApp, useCommand, useCopyToClipboard, useIndexedDBStore, useLocalStore };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface IndexedDBMutationOptions {
|
|
2
|
+
optimistic?: boolean;
|
|
3
|
+
deletePrefixes?: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
export declare const updateIndexedDBValue: <T>(factory: IDBFactory | undefined, key: string, initialValue: T, parse?: (value: unknown) => T, update?: (current: T) => T, options?: IndexedDBMutationOptions) => Promise<T>;
|
|
6
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/lib/use-indexed-db-store/database.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACnC;AA+BD,eAAO,MAAM,oBAAoB,GAAU,CAAC,EAC1C,SAAS,UAAU,GAAG,SAAS,EAC/B,KAAK,MAAM,EACX,cAAc,CAAC,EACf,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,EAC7B,SAAS,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,EAC1B,UAAS,wBAA6B,KACrC,OAAO,CAAC,CAAC,CAwEX,CAAA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
//#region src/lib/use-indexed-db-store/database.ts
|
|
2
|
+
var INDEXED_DB_NAME = "vezham";
|
|
3
|
+
var RECORDS = "records";
|
|
4
|
+
var DELETED_PREFIXES = "\0deleted-prefixes";
|
|
5
|
+
var openDatabase = (factory) => new Promise((resolve, reject) => {
|
|
6
|
+
if (!factory) {
|
|
7
|
+
reject(/* @__PURE__ */ new Error("IndexedDB is unavailable in this browser."));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const request = factory.open(INDEXED_DB_NAME, 1);
|
|
11
|
+
let blocked = false;
|
|
12
|
+
request.onupgradeneeded = () => {
|
|
13
|
+
if (!request.result.objectStoreNames.contains(RECORDS)) request.result.createObjectStore(RECORDS);
|
|
14
|
+
};
|
|
15
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("Unable to open IndexedDB."));
|
|
16
|
+
request.onblocked = () => {
|
|
17
|
+
blocked = true;
|
|
18
|
+
reject(/* @__PURE__ */ new Error("IndexedDB is blocked by another open page."));
|
|
19
|
+
};
|
|
20
|
+
request.onsuccess = () => {
|
|
21
|
+
if (blocked) {
|
|
22
|
+
request.result.close();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
request.result.onversionchange = () => request.result.close();
|
|
26
|
+
resolve(request.result);
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
var updateIndexedDBValue = async (factory, key, initialValue, parse, update, options = {}) => {
|
|
30
|
+
const database = await openDatabase(factory);
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
let value;
|
|
33
|
+
let failure;
|
|
34
|
+
const transaction = database.transaction(RECORDS, "readwrite");
|
|
35
|
+
const records = transaction.objectStore(RECORDS);
|
|
36
|
+
const deletedRequest = records.get(DELETED_PREFIXES);
|
|
37
|
+
deletedRequest.onsuccess = () => {
|
|
38
|
+
const deleted = deletedRequest.result ?? [];
|
|
39
|
+
if (deleted.some((prefix) => key.startsWith(prefix))) {
|
|
40
|
+
failure = /* @__PURE__ */ new Error("This data scope has been deleted.");
|
|
41
|
+
transaction.abort();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const request = records.get(key);
|
|
45
|
+
request.onsuccess = () => {
|
|
46
|
+
try {
|
|
47
|
+
const stored = request.result;
|
|
48
|
+
const current = stored === void 0 ? initialValue : parse ? parse(stored) : stored;
|
|
49
|
+
value = update ? update(current) : current;
|
|
50
|
+
if (update || stored === void 0) records.put(value, key);
|
|
51
|
+
const prefixes = options.deletePrefixes ?? [];
|
|
52
|
+
if (prefixes.length) {
|
|
53
|
+
if (!update || prefixes.some((prefix) => !prefix || key.startsWith(prefix))) throw new Error("Invalid IndexedDB deletion scope.");
|
|
54
|
+
records.put([.../* @__PURE__ */ new Set([...deleted, ...prefixes])], DELETED_PREFIXES);
|
|
55
|
+
const cursor = records.openCursor();
|
|
56
|
+
cursor.onsuccess = () => {
|
|
57
|
+
const entry = cursor.result;
|
|
58
|
+
if (!entry) return;
|
|
59
|
+
if (typeof entry.key === "string" && prefixes.some((prefix) => entry.key.toString().startsWith(prefix))) entry.delete();
|
|
60
|
+
entry.continue();
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
} catch (cause) {
|
|
64
|
+
failure = cause;
|
|
65
|
+
transaction.abort();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
transaction.oncomplete = () => {
|
|
70
|
+
database.close();
|
|
71
|
+
resolve(value);
|
|
72
|
+
};
|
|
73
|
+
transaction.onabort = () => {
|
|
74
|
+
database.close();
|
|
75
|
+
reject(failure ?? transaction.error ?? /* @__PURE__ */ new Error("IndexedDB transaction was aborted."));
|
|
76
|
+
};
|
|
77
|
+
transaction.onerror = () => {
|
|
78
|
+
failure ??= transaction.error;
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
//#endregion
|
|
83
|
+
export { updateIndexedDBValue };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IndexedDBStoreOptions } from './indexed-db-store';
|
|
2
|
+
export declare const useIndexedDBStore: <T>(key: string, initialValue: T, options?: IndexedDBStoreOptions<T>) => {
|
|
3
|
+
setValue: (update: T | ((current: T) => T), options?: import("./database").IndexedDBMutationOptions) => Promise<boolean>;
|
|
4
|
+
flush: () => Promise<undefined>;
|
|
5
|
+
refresh: () => Promise<undefined>;
|
|
6
|
+
value: T;
|
|
7
|
+
isReady: boolean;
|
|
8
|
+
isSaved: boolean;
|
|
9
|
+
error: unknown;
|
|
10
|
+
};
|
|
11
|
+
export type { IndexedDBStoreOptions } from './indexed-db-store';
|
|
12
|
+
export type { IndexedDBMutationOptions } from './database';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/use-indexed-db-store/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAI/D,eAAO,MAAM,iBAAiB,GAAI,CAAC,EACjC,KAAK,MAAM,EACX,cAAc,CAAC,EACf,UAAS,qBAAqB,CAAC,CAAC,CAAM;;;;;;;;CAkBvC,CAAA;AAED,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAE/D,YAAY,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getIndexedDBStore } from "./indexed-db-store.js";
|
|
2
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
3
|
+
//#region src/lib/use-indexed-db-store/index.ts
|
|
4
|
+
var disabledSubscribe = () => () => void 0;
|
|
5
|
+
var useIndexedDBStore = (key, initialValue, options = {}) => {
|
|
6
|
+
const { parse, enabled = true } = options;
|
|
7
|
+
const store = useMemo(() => getIndexedDBStore(key, initialValue, parse), [
|
|
8
|
+
key,
|
|
9
|
+
initialValue,
|
|
10
|
+
parse
|
|
11
|
+
]);
|
|
12
|
+
return {
|
|
13
|
+
...useSyncExternalStore(enabled ? store.subscribe : disabledSubscribe, enabled ? store.getSnapshot : store.getInitialSnapshot, store.getInitialSnapshot),
|
|
14
|
+
setValue: store.setValue,
|
|
15
|
+
flush: store.flush,
|
|
16
|
+
refresh: store.refresh
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { useIndexedDBStore };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type IndexedDBMutationOptions } from './database';
|
|
2
|
+
export interface IndexedDBStoreOptions<T> {
|
|
3
|
+
parse?: (value: unknown) => T;
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
}
|
|
6
|
+
interface Snapshot<T> {
|
|
7
|
+
value: T;
|
|
8
|
+
isReady: boolean;
|
|
9
|
+
isSaved: boolean;
|
|
10
|
+
error: unknown;
|
|
11
|
+
}
|
|
12
|
+
declare const createIndexedDBStore: <T>(key: string, initialValue: T, parse?: (value: unknown) => T) => {
|
|
13
|
+
getSnapshot: () => Snapshot<T>;
|
|
14
|
+
getInitialSnapshot: () => Snapshot<T>;
|
|
15
|
+
setValue: (update: T | ((current: T) => T), options?: IndexedDBMutationOptions) => Promise<boolean>;
|
|
16
|
+
refresh: () => Promise<undefined>;
|
|
17
|
+
flush: () => Promise<undefined>;
|
|
18
|
+
subscribe: (listener: () => void) => () => void;
|
|
19
|
+
};
|
|
20
|
+
type IndexedDBStore<T> = ReturnType<typeof createIndexedDBStore<T>>;
|
|
21
|
+
export declare const getIndexedDBStore: <T>(key: string, initialValue: T, parse?: (value: unknown) => T) => IndexedDBStore<T>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=indexed-db-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexed-db-store.d.ts","sourceRoot":"","sources":["../../../src/lib/use-indexed-db-store/indexed-db-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,wBAAwB,EAAwB,MAAM,YAAY,CAAA;AAEhF,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,CAAA;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,UAAU,QAAQ,CAAC,CAAC;IAClB,KAAK,EAAE,CAAC,CAAA;IACR,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf;AAED,QAAA,MAAM,oBAAoB,GAAI,CAAC,EAC7B,KAAK,MAAM,EACX,cAAc,CAAC,EACf,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;;;uBA4CnB,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,YACrB,wBAAwB,KACjC,OAAO,CAAC,OAAO,CAAC;;;0BA2DK,MAAM,IAAI;CA6BnC,CAAA;AAED,KAAK,cAAc,CAAC,CAAC,IAAI,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAA;AAGnE,eAAO,MAAM,iBAAiB,GAAI,CAAC,EACjC,KAAK,MAAM,EACX,cAAc,CAAC,EACf,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,KAC5B,cAAc,CAAC,CAAC,CAE6B,CAAA"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { updateIndexedDBValue } from "./database.js";
|
|
2
|
+
//#region src/lib/use-indexed-db-store/indexed-db-store.ts
|
|
3
|
+
var createIndexedDBStore = (key, initialValue, parse) => {
|
|
4
|
+
const initial = {
|
|
5
|
+
value: initialValue,
|
|
6
|
+
isReady: false,
|
|
7
|
+
isSaved: true,
|
|
8
|
+
error: null
|
|
9
|
+
};
|
|
10
|
+
let snapshot = initial;
|
|
11
|
+
let factory;
|
|
12
|
+
let channel;
|
|
13
|
+
let queue = Promise.resolve();
|
|
14
|
+
let revision = 0;
|
|
15
|
+
let pending = 0;
|
|
16
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
17
|
+
const emit = () => listeners.forEach((listener) => listener());
|
|
18
|
+
const fail = (error) => {
|
|
19
|
+
snapshot = {
|
|
20
|
+
...snapshot,
|
|
21
|
+
isReady: true,
|
|
22
|
+
isSaved: false,
|
|
23
|
+
error
|
|
24
|
+
};
|
|
25
|
+
emit();
|
|
26
|
+
};
|
|
27
|
+
const acceptRead = (value, version) => {
|
|
28
|
+
if (revision !== version || pending) return;
|
|
29
|
+
snapshot = {
|
|
30
|
+
value,
|
|
31
|
+
isReady: true,
|
|
32
|
+
isSaved: true,
|
|
33
|
+
error: null
|
|
34
|
+
};
|
|
35
|
+
emit();
|
|
36
|
+
};
|
|
37
|
+
const read = async () => {
|
|
38
|
+
const version = revision;
|
|
39
|
+
try {
|
|
40
|
+
const value = await updateIndexedDBValue(factory, key, initialValue, parse);
|
|
41
|
+
acceptRead(value, version);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (revision === version) fail(error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const refresh = () => {
|
|
47
|
+
queue = queue.then(read);
|
|
48
|
+
return queue.then(() => void 0);
|
|
49
|
+
};
|
|
50
|
+
const setValue = (update, options) => {
|
|
51
|
+
if (!snapshot.isReady || snapshot.error) return Promise.resolve(false);
|
|
52
|
+
const apply = (current) => typeof update === "function" ? update(current) : update;
|
|
53
|
+
let optimistic;
|
|
54
|
+
try {
|
|
55
|
+
optimistic = apply(snapshot.value);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
fail(error);
|
|
58
|
+
return Promise.resolve(false);
|
|
59
|
+
}
|
|
60
|
+
const version = ++revision;
|
|
61
|
+
pending++;
|
|
62
|
+
snapshot = {
|
|
63
|
+
value: options?.optimistic === false ? snapshot.value : optimistic,
|
|
64
|
+
isReady: true,
|
|
65
|
+
isSaved: false,
|
|
66
|
+
error: null
|
|
67
|
+
};
|
|
68
|
+
emit();
|
|
69
|
+
const operation = queue.then(async () => {
|
|
70
|
+
try {
|
|
71
|
+
const value = await updateIndexedDBValue(factory, key, initialValue, parse, apply, options);
|
|
72
|
+
pending--;
|
|
73
|
+
if (version === revision) {
|
|
74
|
+
snapshot = {
|
|
75
|
+
value,
|
|
76
|
+
isReady: true,
|
|
77
|
+
isSaved: pending === 0,
|
|
78
|
+
error: null
|
|
79
|
+
};
|
|
80
|
+
emit();
|
|
81
|
+
}
|
|
82
|
+
channel?.postMessage(key);
|
|
83
|
+
return true;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
pending--;
|
|
86
|
+
if (version === revision) fail(error);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
queue = operation.then(() => void 0);
|
|
91
|
+
return operation;
|
|
92
|
+
};
|
|
93
|
+
const store = {
|
|
94
|
+
getSnapshot: () => snapshot,
|
|
95
|
+
getInitialSnapshot: () => initial,
|
|
96
|
+
setValue,
|
|
97
|
+
refresh,
|
|
98
|
+
flush: () => queue.then(() => void 0),
|
|
99
|
+
subscribe: (listener) => {
|
|
100
|
+
const first = listeners.size === 0;
|
|
101
|
+
listeners.add(listener);
|
|
102
|
+
if (first) {
|
|
103
|
+
stores.set(key, store);
|
|
104
|
+
factory ??= globalThis.indexedDB;
|
|
105
|
+
if (!channel && typeof BroadcastChannel !== "undefined") {
|
|
106
|
+
channel = new BroadcastChannel("vezham:indexeddb");
|
|
107
|
+
channel.onmessage = (event) => {
|
|
108
|
+
if (event.data === key && listeners.size) refresh();
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
refresh();
|
|
112
|
+
}
|
|
113
|
+
return () => {
|
|
114
|
+
listeners.delete(listener);
|
|
115
|
+
if (listeners.size) return;
|
|
116
|
+
if (stores.get(key) === store) stores.delete(key);
|
|
117
|
+
queue.then(() => {
|
|
118
|
+
if (!listeners.size) {
|
|
119
|
+
channel?.close();
|
|
120
|
+
channel = void 0;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
return store;
|
|
127
|
+
};
|
|
128
|
+
var stores = /* @__PURE__ */ new Map();
|
|
129
|
+
var getIndexedDBStore = (key, initialValue, parse) => stores.get(key) ?? createIndexedDBStore(key, initialValue, parse);
|
|
130
|
+
//#endregion
|
|
131
|
+
export { getIndexedDBStore };
|