-
Notifications
You must be signed in to change notification settings - Fork 3
/
persistentStore.ts
110 lines (97 loc) · 3.34 KB
/
persistentStore.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/** -------------------------- persistentStore.ts -------------------------- */
export type PersistentWritable<T> = {
subscribe: (subscription: (value: T) => void) => () => void;
set: (value: T) => void;
update: (update_func: (curr: T) => T) => void;
};
/** A generic persistent store according to the Svelte store contract
*
* @example
* import { persistentWritable } from "./persistentStore";
* export const store = persistentWritable<object>("storeKey", {});
*
* $store = { id: 1 };
* console.log($store.id);
*
* @template T - Should be a type JSON.stringify can process
* @param {string} storeKey - A unique key in localStorage for the store.
* This will also be the channel name in Broadcast API.
* @param {T} initialValue - Initial value of store
* @returns {PersistentWritable<T>} - A persistent writable store
*/
export const persistentWritable = <T>(storeKey: string, initialValue: T): PersistentWritable<T> => {
let subscriptions: ((value: T) => void)[] = [];
let storeValue: T;
const safeParse = (jsonString: string) => {
try {
return JSON.parse(jsonString);
} catch (error: unknown) {
if (error instanceof Error) {
console.log(error)
}
}
}
const safeSetItem = (key: string, value: T) => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
if (error instanceof Error) {
console.log(error);
}
}
}
const safeGetItem = (key: string) => {
try {
return localStorage.getItem(key);
} catch (error) {
if (error instanceof Error) {
console.log(error);
}
}
}
let currentStoreString = safeGetItem(storeKey);
if (currentStoreString === null || currentStoreString === undefined) {
storeValue = initialValue;
safeSetItem(storeKey, storeValue);
} else {
storeValue = safeParse(safeGetItem(storeKey));
}
let storeChannel = new BroadcastChannel(storeKey);
storeChannel.onmessage = event => {
storeValue = safeParse(safeGetItem(storeKey));
if (event.data === storeKey) {
subscriptions.forEach(subscriptions => subscriptions(storeValue));
}
}
// Subscribes function and returns an unsubscribe function
const subscribe = (subscription: (value: T) => void) => {
subscription(storeValue);
subscriptions = [...subscriptions, subscription];
// If subscribers go from 0 to 1 (after dropping to 0 before) recreate channel
if (subscription.length === 1 && storeChannel === null) {
storeChannel = new BroadcastChannel(storeKey);
}
const unsubscribe = () => {
subscriptions = subscriptions.filter(s => s != subscription);
// If subsccribers go from 1 to 0 close channel
if (storeChannel && subscription.length === 0) {
storeChannel.close();
storeChannel = null;
}
}
return unsubscribe;
}
// Sets stringified value in local storage and calls subscriptions
const set = (value: T) => {
storeValue = value;
safeSetItem(storeKey, value);
subscriptions.forEach(subscription => subscription(storeValue));
if (storeChannel) {
storeChannel.postMessage(storeKey);
}
}
// Updates store value according to input function
const update =
(update_func: (curr: T) => T) => set(update_func(storeValue));
return { subscribe, set, update };
}