|
1 | | -import { get, writable as internal, type Writable } from 'svelte/store' |
| 1 | +import { get, writable as internal, type Writable } from "svelte/store"; |
2 | 2 | if (process.env.NODE_ENV === "test" || process.env.NODE_ENV === "development") { |
3 | | - require("fake-indexeddb/auto") |
| 3 | + require("fake-indexeddb/auto"); |
4 | 4 | } |
5 | | -import localforage from "localforage" |
6 | | -declare type StoreDict<T> = { [key: string]: Persisted<T> } |
7 | | -declare type Updater<T> = (value: T) => T |
| 5 | +import localforage from "localforage"; |
| 6 | +declare type StoreDict<T> = { [key: string]: Persisted<T> }; |
| 7 | +declare type Updater<T> = (value: T) => T; |
8 | 8 |
|
9 | 9 | interface Persisted<T> extends Writable<T> { |
10 | | - set: (this: void, value: T) => Promise<void> |
11 | | - reset: () => Promise<void> |
12 | | - update: (callback: Updater<T>) => Promise<void> |
| 10 | + set: (this: void, value: T) => Promise<void>; |
| 11 | + reset: () => Promise<void>; |
| 12 | + update: (callback: Updater<T>) => Promise<void>; |
13 | 13 | } |
14 | 14 |
|
15 | 15 | /* eslint-disable @typescript-eslint/no-explicit-any */ |
16 | 16 | interface Stores { |
17 | | - local: StoreDict<any>, |
18 | | - session: StoreDict<any>, |
19 | | - indexedDB: StoreDict<any> |
| 17 | + local: StoreDict<any>; |
| 18 | + session: StoreDict<any>; |
| 19 | + indexedDB: StoreDict<any>; |
20 | 20 | } |
21 | 21 |
|
22 | 22 | const stores: Stores = { |
23 | 23 | local: {}, |
24 | 24 | session: {}, |
25 | | - indexedDB: {} |
26 | | -} |
| 25 | + indexedDB: {}, |
| 26 | +}; |
27 | 27 |
|
28 | 28 | export interface Serializer<T> { |
29 | | - parse(text: string): T |
30 | | - stringify(object: T): string |
| 29 | + parse(text: string): T; |
| 30 | + stringify(object: T): string; |
31 | 31 | } |
32 | 32 |
|
33 | | -export type StorageType = 'local' | 'session' | 'indexedDB' |
| 33 | +export type StorageType = "local" | "session" | "indexedDB"; |
34 | 34 |
|
35 | 35 | export interface Options<StoreType, SerializerType> { |
36 | | - serializer?: Serializer<SerializerType> |
37 | | - storage?: StorageType, |
38 | | - syncTabs?: boolean, |
39 | | - onError?: (e: unknown) => void |
40 | | - onWriteError?: (e: unknown) => void |
41 | | - onParseError?: (newValue: string | null, e: unknown) => void |
42 | | - beforeRead?: (val: SerializerType) => StoreType |
43 | | - beforeWrite?: (val: StoreType) => SerializerType |
| 36 | + serializer?: Serializer<SerializerType>; |
| 37 | + storage?: StorageType; |
| 38 | + syncTabs?: boolean; |
| 39 | + onError?: (e: unknown) => void; |
| 40 | + onWriteError?: (e: unknown) => void; |
| 41 | + onParseError?: (newValue: string | null, e: unknown) => void; |
| 42 | + beforeRead?: (val: SerializerType) => StoreType; |
| 43 | + beforeWrite?: (val: StoreType) => SerializerType; |
44 | 44 | } |
45 | 45 |
|
46 | 46 | async function getStorage(type: StorageType) { |
47 | | - let storage: LocalForage | Storage | null |
| 47 | + let storage: LocalForage | Storage | null; |
48 | 48 | try { |
49 | | - storage = type === "session" ? window.sessionStorage : localforage |
50 | | - if (type === "local") await storage.setDriver(localforage.LOCALSTORAGE) |
51 | | - if (type === "indexedDB") await storage.setDriver(localforage.INDEXEDDB) |
| 49 | + storage = type === "session" ? window.sessionStorage : localforage; |
| 50 | + if (type === "local") await storage.setDriver(localforage.LOCALSTORAGE); |
| 51 | + if (type === "indexedDB") await storage.setDriver(localforage.INDEXEDDB); |
52 | 52 | } catch (error) { |
53 | | - |
54 | | - storage = null |
| 53 | + storage = null; |
55 | 54 | } |
56 | | - return storage |
| 55 | + return storage; |
57 | 56 | } |
58 | 57 |
|
59 | 58 | /** @deprecated `writable()` has been renamed to `persisted()` */ |
60 | | -export async function writable<StoreType, SerializerType = StoreType>(key: string, initialValue: StoreType, options?: Options<StoreType, SerializerType>): Promise<Persisted<StoreType>> { |
61 | | - console.warn("writable() has been deprecated. Please use persisted() instead.\n\nchange:\n\nimport { writable } from 'svelte-persisted-store'\n\nto:\n\nimport { persisted } from 'svelte-persisted-store'") |
62 | | - return await persisted<StoreType, SerializerType>(key, initialValue, options) |
| 59 | +export async function writable<StoreType, SerializerType = StoreType>( |
| 60 | + key: string, |
| 61 | + initialValue: StoreType, |
| 62 | + options?: Options<StoreType, SerializerType> |
| 63 | +): Promise<Persisted<StoreType>> { |
| 64 | + console.warn( |
| 65 | + "writable() has been deprecated. Please use persisted() instead.\n\nchange:\n\nimport { writable } from 'svelte-persisted-store'\n\nto:\n\nimport { persisted } from 'svelte-persisted-store'" |
| 66 | + ); |
| 67 | + return await persisted<StoreType, SerializerType>(key, initialValue, options); |
63 | 68 | } |
64 | | -export function persisted<StoreType, SerializerType = StoreType>(key: string, initialValue: StoreType, options?: Options<StoreType, SerializerType>): Persisted<StoreType> { |
65 | | - if (options?.onError) console.warn("onError has been deprecated. Please use onWriteError instead") |
66 | | - |
67 | | - const serializer = options?.serializer ?? JSON |
68 | | - const storageType = options?.storage ?? 'local' |
69 | | - const syncTabs = options?.syncTabs ?? true |
70 | | - const onWriteError = options?.onWriteError ?? options?.onError ?? ((e) => console.error(`Error when writing value from persisted store "${key}" to ${storageType}`, e)) |
71 | | - const onParseError = options?.onParseError ?? ((newVal, e) => console.error(`Error when parsing ${newVal ? '"' + newVal + '"' : "value"} from persisted store "${key}"`, e)) |
72 | | - |
73 | | - const beforeRead = options?.beforeRead ?? ((val) => val as unknown as StoreType) |
74 | | - const beforeWrite = options?.beforeWrite ?? ((val) => val as unknown as SerializerType) |
75 | | - |
| 69 | +export async function persisted<StoreType, SerializerType = StoreType>( |
| 70 | + key: string, |
| 71 | + initialValue: StoreType, |
| 72 | + options?: Options<StoreType, SerializerType> |
| 73 | +): Promise<Persisted<StoreType>> { |
| 74 | + if (options?.onError) |
| 75 | + console.warn( |
| 76 | + "onError has been deprecated. Please use onWriteError instead" |
| 77 | + ); |
| 78 | + |
| 79 | + const serializer = options?.serializer ?? JSON; |
| 80 | + const storageType = options?.storage ?? "local"; |
| 81 | + const syncTabs = options?.syncTabs ?? true; |
| 82 | + const onWriteError = |
| 83 | + options?.onWriteError ?? |
| 84 | + options?.onError ?? |
| 85 | + ((e) => |
| 86 | + console.error( |
| 87 | + `Error when writing value from persisted store "${key}" to ${storageType}`, |
| 88 | + e |
| 89 | + )); |
| 90 | + const onParseError = |
| 91 | + options?.onParseError ?? |
| 92 | + ((newVal, e) => |
| 93 | + console.error( |
| 94 | + `Error when parsing ${ |
| 95 | + newVal ? '"' + newVal + '"' : "value" |
| 96 | + } from persisted store "${key}"`, |
| 97 | + e |
| 98 | + )); |
| 99 | + |
| 100 | + const beforeRead = |
| 101 | + options?.beforeRead ?? ((val) => val as unknown as StoreType); |
| 102 | + const beforeWrite = |
| 103 | + options?.beforeWrite ?? ((val) => val as unknown as SerializerType); |
76 | 104 |
|
77 | 105 | const browser = |
78 | | - typeof window !== "undefined" && typeof document !== "undefined" |
| 106 | + typeof window !== "undefined" && typeof document !== "undefined"; |
79 | 107 | const storage: Storage | LocalForage | null = browser |
80 | 108 | ? await getStorage(storageType) |
81 | | - : null |
| 109 | + : null; |
82 | 110 | async function updateStorage(key: string, value: StoreType) { |
83 | | - const newVal = beforeWrite(value) |
| 111 | + const newVal = beforeWrite(value); |
84 | 112 | try { |
85 | | - await storage?.setItem(key, serializer.stringify(newVal)) |
86 | | - |
| 113 | + await storage?.setItem(key, serializer.stringify(newVal)); |
87 | 114 | } catch (e) { |
88 | | - onWriteError(e) |
| 115 | + onWriteError(e); |
89 | 116 | } |
90 | 117 | } |
91 | 118 |
|
92 | 119 | async function maybeLoadInitial(): Promise<StoreType> { |
93 | 120 | function serialize(json: any) { |
94 | 121 | try { |
95 | | - return <SerializerType>serializer.parse(json) |
| 122 | + return <SerializerType>serializer.parse(json); |
96 | 123 | } catch (e) { |
97 | | - onParseError(json, e) |
| 124 | + onParseError(json, e); |
98 | 125 | } |
99 | 126 | } |
100 | | - const json = await storage?.getItem(key) |
101 | | - if (json == null) return initialValue |
| 127 | + const json = await storage?.getItem(key); |
| 128 | + if (json == null) return initialValue; |
102 | 129 |
|
103 | | - const serialized = serialize(json) |
104 | | - if (serialized == null) return initialValue |
| 130 | + const serialized = serialize(json); |
| 131 | + if (serialized == null) return initialValue; |
105 | 132 |
|
106 | | - const newVal = beforeRead(serialized) |
107 | | - return newVal |
| 133 | + const newVal = beforeRead(serialized); |
| 134 | + return newVal; |
108 | 135 | } |
109 | 136 |
|
110 | 137 | if (!stores[storageType][key]) { |
111 | | - const initial: StoreType = await maybeLoadInitial() |
| 138 | + const initial: StoreType = await maybeLoadInitial(); |
112 | 139 | const store = internal(initial, (set) => { |
113 | | - if (browser && storageType == 'local' && syncTabs) { |
| 140 | + if (browser && storageType == "local" && syncTabs) { |
114 | 141 | const handleStorage = async (event: StorageEvent) => { |
115 | 142 | if (event.key === key && event.newValue) { |
116 | | - let newVal: any |
| 143 | + let newVal: any; |
117 | 144 | try { |
118 | | - newVal = serializer.parse(event.newValue) |
| 145 | + newVal = serializer.parse(event.newValue); |
119 | 146 | } catch (e) { |
120 | | - onParseError(event.newValue, e) |
121 | | - return |
| 147 | + onParseError(event.newValue, e); |
| 148 | + return; |
122 | 149 | } |
123 | | - const processedVal = beforeRead(newVal) |
| 150 | + const processedVal = beforeRead(newVal); |
124 | 151 |
|
125 | | - set(processedVal) |
| 152 | + set(processedVal); |
126 | 153 | } |
127 | | - } |
| 154 | + }; |
128 | 155 |
|
129 | | - window.addEventListener("storage", handleStorage) |
| 156 | + window.addEventListener("storage", handleStorage); |
130 | 157 |
|
131 | | - return () => window.removeEventListener("storage", handleStorage) |
| 158 | + return () => window.removeEventListener("storage", handleStorage); |
132 | 159 | } |
133 | | - }) |
| 160 | + }); |
134 | 161 |
|
135 | | - const { subscribe, set } = store |
| 162 | + const { subscribe, set } = store; |
136 | 163 |
|
137 | 164 | stores[storageType][key] = { |
138 | 165 | async set(value: StoreType) { |
139 | | - set(value) |
140 | | - await updateStorage(key, value) |
| 166 | + set(value); |
| 167 | + await updateStorage(key, value); |
141 | 168 | }, |
142 | 169 | async update(callback: Updater<StoreType>) { |
143 | 170 | // this is more concise than store.update |
144 | | - await this.set(callback(get(store))) |
| 171 | + await this.set(callback(get(store))); |
145 | 172 | }, |
146 | 173 | async reset() { |
147 | | - await this.set(initialValue) |
| 174 | + await this.set(initialValue); |
148 | 175 | }, |
149 | | - subscribe |
150 | | - } |
| 176 | + subscribe, |
| 177 | + }; |
151 | 178 | } |
152 | | - return stores[storageType][key] |
| 179 | + return stores[storageType][key]; |
153 | 180 | } |
0 commit comments