diff --git a/README.md b/README.md index 76585da..ab9225d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ get(preferences) // read value $preferences // read value with automatic subscription ``` -You can also optionally set the `serializer` or `storage` type: +You can also optionally set the `serializer`, `storage` and `onError` type: ```javascript import * as devalue from 'devalue' @@ -49,6 +49,17 @@ export const preferences = persisted('local-storage-key', 'default-value', { serializer: devalue, // defaults to `JSON` storage: 'session', // 'session' for sessionStorage, defaults to 'local' syncTabs: true // choose wether to sync localStorage across tabs, default is true + onError: (e) => {/* Do something */} // Defaults to console.error with the error object +}) +``` + +As the library will swallow errors encountered when reading from browser storage it is possible to specify a custom function to handle the error. Should the swallowing not be desirable, it is possible to re-throw the error like the following example (not recommended): + +```javascript +export const preferences = persisted('local-storage-key', 'default-value', { + onError: (e) => { + throw e + } }) ``` diff --git a/index.ts b/index.ts index 6023e01..81a0239 100644 --- a/index.ts +++ b/index.ts @@ -1,4 +1,4 @@ -import {writable as internal, type Writable} from 'svelte/store' +import { writable as internal, type Writable } from 'svelte/store' declare type Updater = (value: T) => T; declare type StoreDict = { [key: string]: Writable } @@ -9,7 +9,7 @@ interface Stores { session: StoreDict, } -const stores : Stores = { +const stores: Stores = { local: {}, session: {} } @@ -24,7 +24,8 @@ export type StorageType = 'local' | 'session' export interface Options { serializer?: Serializer storage?: StorageType, - syncTabs: boolean + syncTabs: boolean, + onError?: (e: unknown) => void } function getStorage(type: StorageType) { @@ -40,11 +41,16 @@ export function persisted(key: string, initialValue: T, options?: Options) const serializer = options?.serializer ?? JSON const storageType = options?.storage ?? 'local' const syncTabs = options?.syncTabs ?? true - const browser = typeof(window) !== 'undefined' && typeof(document) !== 'undefined' + const onError = options?.onError ?? ((e) => console.error(`Error when writing value from persisted store "${key}" to ${storageType}`, e)) + const browser = typeof (window) !== 'undefined' && typeof (document) !== 'undefined' const storage = browser ? getStorage(storageType) : null function updateStorage(key: string, value: T) { - storage?.setItem(key, serializer.stringify(value)) + try { + storage?.setItem(key, serializer.stringify(value)) + } catch (e) { + onError(e) + } } if (!stores[storageType][key]) { @@ -67,12 +73,12 @@ export function persisted(key: string, initialValue: T, options?: Options) } }) - const {subscribe, set} = store + const { subscribe, set } = store stores[storageType][key] = { set(value: T) { - updateStorage(key, value) set(value) + updateStorage(key, value) }, update(callback: Updater) { return store.update((last) => { diff --git a/test/domExceptions.test.ts b/test/domExceptions.test.ts new file mode 100644 index 0000000..27a1403 --- /dev/null +++ b/test/domExceptions.test.ts @@ -0,0 +1,31 @@ +/** + * @vitest-environment jsdom + * @vitest-environment-options { "storageQuota": "0" } + */ + +import { persisted } from '../index' +import { expect, vi, beforeEach, describe, it } from 'vitest' + +beforeEach(() => localStorage.clear()) + +describe('persisted()', () => { + + it('logs error encountered when saving to local storage', () => { + const consoleMock = vi.spyOn(console, 'error').mockImplementation(() => undefined); + const store = persisted('myKey', 'myVal') + + store.set("myNewVal") + + expect(consoleMock).toHaveBeenCalledWith("Error when writing value from persisted store \"myKey\" to local", new DOMException) + consoleMock.mockReset(); + }) + + it('calls custom error function', () => { + const mockFunc = vi.fn() + + const store = persisted('myKey2', 'myVal', { onError: mockFunc }) + store.set("myNewVal") + + expect(mockFunc).toHaveBeenCalledOnce() + }) +}) diff --git a/test/localStorageStore.test.ts b/test/localStorageStore.test.ts index 9916b49..986a2b9 100644 --- a/test/localStorageStore.test.ts +++ b/test/localStorageStore.test.ts @@ -1,5 +1,6 @@ import { persisted, writable } from '../index' import { get } from 'svelte/store' +import { expect, vi, beforeEach, describe, test, it } from 'vitest' beforeEach(() => localStorage.clear()) @@ -193,7 +194,7 @@ describe('persisted()', () => { it('ignores session-backed stores', () => { const store = persisted('myKey10', 1, { storage: 'session' }) - const values = [] + const values: number[] = [] const unsub = store.subscribe((value) => { values.push(value)