Skip to content

Commit

Permalink
Merge pull request #216 from bertmad3400/catchError
Browse files Browse the repository at this point in the history
Catch error when writting to local storage
  • Loading branch information
joshnuss authored Dec 22, 2023
2 parents ac50568 + 68e1aba commit 3d3319d
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 9 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
}
})
```

Expand Down
20 changes: 13 additions & 7 deletions index.ts
Original file line number Diff line number Diff line change
@@ -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<T> = (value: T) => T;
declare type StoreDict<T> = { [key: string]: Writable<T> }
Expand All @@ -9,7 +9,7 @@ interface Stores {
session: StoreDict<any>,
}

const stores : Stores = {
const stores: Stores = {
local: {},
session: {}
}
Expand All @@ -24,7 +24,8 @@ export type StorageType = 'local' | 'session'
export interface Options<T> {
serializer?: Serializer<T>
storage?: StorageType,
syncTabs: boolean
syncTabs: boolean,
onError?: (e: unknown) => void
}

function getStorage(type: StorageType) {
Expand All @@ -40,11 +41,16 @@ export function persisted<T>(key: string, initialValue: T, options?: Options<T>)
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]) {
Expand All @@ -67,12 +73,12 @@ export function persisted<T>(key: string, initialValue: T, options?: Options<T>)
}
})

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<T>) {
return store.update((last) => {
Expand Down
31 changes: 31 additions & 0 deletions test/domExceptions.test.ts
Original file line number Diff line number Diff line change
@@ -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()
})
})
3 changes: 2 additions & 1 deletion test/localStorageStore.test.ts
Original file line number Diff line number Diff line change
@@ -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())

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 3d3319d

Please sign in to comment.