From f7c1927644a1fae060547da0e64b2f949f02ff5a Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Fri, 26 Apr 2024 16:36:25 +0000 Subject: [PATCH 01/12] Make persisted ignore events when value is null Previously it would set the store value to null, which would break with the type safety --- index.ts | 4 ++-- test/localStorageStore.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/index.ts b/index.ts index 3e11ca2..7e4f17d 100644 --- a/index.ts +++ b/index.ts @@ -77,10 +77,10 @@ export function persisted(key: string, initialValue: T, options?: Options) const store = internal(initial, (set) => { if (browser && storageType == 'local' && syncTabs) { const handleStorage = (event: StorageEvent) => { - if (event.key === key) { + if (event.key === key && event.newValue) { let newVal: any try { - newVal = event.newValue ? serializer.parse(event.newValue) : null + newVal = serializer.parse(event.newValue) } catch (e) { onParseError(event.newValue, e) return diff --git a/test/localStorageStore.test.ts b/test/localStorageStore.test.ts index 557e7f3..68b3ce0 100644 --- a/test/localStorageStore.test.ts +++ b/test/localStorageStore.test.ts @@ -151,7 +151,7 @@ describe('persisted()', () => { unsub() }) - it('sets store to null when value is null', () => { + it('ignores storages events when value is null', () => { const store = persisted('myKey9', {a: 1}) const values: NumberDict[] = [] @@ -162,7 +162,7 @@ describe('persisted()', () => { const event = new StorageEvent('storage', {key: 'myKey9', newValue: null}) window.dispatchEvent(event) - expect(values).toEqual([{a: 1}, null]) + expect(values).toEqual([{a: 1}]) unsub() }) From 32553f3ec8de194720f4a704dd47a5e10da3e6e3 Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Fri, 26 Apr 2024 16:44:09 +0000 Subject: [PATCH 02/12] Add beforeWrite and beforeRead methods --- index.ts | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/index.ts b/index.ts index 7e4f17d..091542a 100644 --- a/index.ts +++ b/index.ts @@ -14,20 +14,22 @@ const stores: Stores = { session: {} } -export interface Serializer { - parse(text: string): T - stringify(object: T): string +export interface Serializer { + parse(text: string): any + stringify(object: any): string } export type StorageType = 'local' | 'session' export interface Options { - serializer?: Serializer + serializer?: Serializer storage?: StorageType, syncTabs?: boolean, onError?: (e: unknown) => void onWriteError?: (e: unknown) => void onParseError?: (newValue: string | null, e: unknown) => void + beforeRead?: (val: any) => T + beforeWrite?: (val: T) => any } function getStorage(type: StorageType) { @@ -47,29 +49,33 @@ export function persisted(key: string, initialValue: T, options?: Options) const syncTabs = options?.syncTabs ?? true const onWriteError = options?.onWriteError ?? options?.onError ?? ((e) => console.error(`Error when writing value from persisted store "${key}" to ${storageType}`, e)) const onParseError = options?.onParseError ?? ((newVal, e) => console.error(`Error when parsing ${newVal ? '"' + newVal + '"' : "value"} from persisted store "${key}"`, e)) + + const beforeRead = options?.beforeRead ?? ((val) => val as T) + const beforeWrite = options?.beforeWrite ?? ((val) => val as any) + const browser = typeof (window) !== 'undefined' && typeof (document) !== 'undefined' const storage = browser ? getStorage(storageType) : null function updateStorage(key: string, value: T) { + const newVal = beforeWrite(value) try { - storage?.setItem(key, serializer.stringify(value)) + storage?.setItem(key, serializer.stringify(newVal)) } catch (e) { onWriteError(e) } } function maybeLoadInitial(): T { - const json = storage?.getItem(key) - - if (json) { + function serialize(json: any) { try { return serializer.parse(json) } catch (e) { onParseError(json, e) } } + const json = storage?.getItem(key) - return initialValue + return json ? beforeRead(serialize(json)) : initialValue } if (!stores[storageType][key]) { @@ -85,7 +91,8 @@ export function persisted(key: string, initialValue: T, options?: Options) onParseError(event.newValue, e) return } - set(newVal) + const processedVal = beforeRead(newVal) + set(processedVal) } } From 77ac10df6f2d773a1fb694453ba5ee084d729735 Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Fri, 26 Apr 2024 17:47:24 +0000 Subject: [PATCH 03/12] Implement tests for beforeRead and beforeWrite --- test/localStorageStore.test.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/localStorageStore.test.ts b/test/localStorageStore.test.ts index 68b3ce0..b4b27a6 100644 --- a/test/localStorageStore.test.ts +++ b/test/localStorageStore.test.ts @@ -132,6 +132,36 @@ describe('persisted()', () => { unsub2() }) + describe("beforeRead and beforeWrite", () => { + it("allows modifying initial value before reading", () => { + localStorage.setItem("beforeRead-init-test", JSON.stringify(2)) + const store = persisted("beforeRead-init-test", 0, { beforeRead: (v) => v * 2 }) + expect(get(store)).toEqual(4) + }) + it("allows modfiying value before reading upon event", () => { + const store = persisted("beforeRead-test", 0, { beforeRead: (v) => v * 2 }) + const values: number[] = [] + + const unsub = store.subscribe((val: number) => { + values.push(val) + }) + + const event = new StorageEvent('storage', {key: 'beforeRead-test', newValue: "2"}) + window.dispatchEvent(event) + + expect(values).toEqual([0, 4]) + + unsub() + }) + + it("allows modfiying value before writing", () => { + const store = persisted("beforeWrite-test", 0, { beforeWrite: (v) => v * 2 }) + store.set(2) + + expect(JSON.parse(localStorage.getItem("beforeWrite-test") as string)).toEqual(4) + }) + }) + describe('handles window.storage event', () => { type NumberDict = { [key: string] : number } From 15a0a883b951749be69c398dd841ede46676e3c7 Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Sat, 27 Apr 2024 08:42:57 +0000 Subject: [PATCH 04/12] Fix spelling mistakes --- test/localStorageStore.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/localStorageStore.test.ts b/test/localStorageStore.test.ts index b4b27a6..877dd51 100644 --- a/test/localStorageStore.test.ts +++ b/test/localStorageStore.test.ts @@ -138,7 +138,7 @@ describe('persisted()', () => { const store = persisted("beforeRead-init-test", 0, { beforeRead: (v) => v * 2 }) expect(get(store)).toEqual(4) }) - it("allows modfiying value before reading upon event", () => { + it("allows modifying value before reading upon event", () => { const store = persisted("beforeRead-test", 0, { beforeRead: (v) => v * 2 }) const values: number[] = [] @@ -154,7 +154,7 @@ describe('persisted()', () => { unsub() }) - it("allows modfiying value before writing", () => { + it("allows modifying value before writing", () => { const store = persisted("beforeWrite-test", 0, { beforeWrite: (v) => v * 2 }) store.set(2) From 49ab7f5922f158844bf84c3f7912d5acf2c049ba Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Sat, 27 Apr 2024 08:46:02 +0000 Subject: [PATCH 05/12] Add syntax example for beforeRead and beforeWrite to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 87b424d..2d55e7e 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ export const preferences = persisted('local-storage-key', 'default-value', { syncTabs: true, // choose whether to sync localStorage across tabs, default is true onWriteError: (error) => {/* handle or rethrow */}, // Defaults to console.error with the error object onParseError: (raw, error) => {/* handle or rethrow */}, // Defaults to console.error with the error object + beforeRead: (value) => {/* change value after serialization but before setting store to return value*/}, + beforeWrite: (value) => {/* change value after writing to store, but before writing return value to local storage*/}, }) ``` From ff269f00e7bba5cac42b90ac9672751a2174bd46 Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Thu, 16 May 2024 12:14:00 +0000 Subject: [PATCH 06/12] Add cancel operation to beforeRead and beforeWrite Uses a symbol which can be returned by either function to cancel the operation --- index.ts | 23 +++++++++--- test/localStorageStore.test.ts | 68 +++++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/index.ts b/index.ts index 091542a..de3ada4 100644 --- a/index.ts +++ b/index.ts @@ -28,8 +28,8 @@ export interface Options { onError?: (e: unknown) => void onWriteError?: (e: unknown) => void onParseError?: (newValue: string | null, e: unknown) => void - beforeRead?: (val: any) => T - beforeWrite?: (val: T) => any + beforeRead?: (val: any, cancel: S) => T | S + beforeWrite?: (val: T, cancel: S) => any | S } function getStorage(type: StorageType) { @@ -57,7 +57,10 @@ export function persisted(key: string, initialValue: T, options?: Options) const storage = browser ? getStorage(storageType) : null function updateStorage(key: string, value: T) { - const newVal = beforeWrite(value) + const cancel = Symbol("cancel") + const newVal = beforeWrite(value, cancel) + if (newVal === cancel) return + try { storage?.setItem(key, serializer.stringify(newVal)) } catch (e) { @@ -74,8 +77,15 @@ export function persisted(key: string, initialValue: T, options?: Options) } } const json = storage?.getItem(key) + if (json == null) return initialValue + + const serialized = serialize(json) + if (serialized == null) return initialValue - return json ? beforeRead(serialize(json)) : initialValue + const cancel = Symbol("cancel") + const newVal = beforeRead(serialized, cancel) + if (newVal === cancel) return initialValue + return newVal } if (!stores[storageType][key]) { @@ -91,7 +101,10 @@ export function persisted(key: string, initialValue: T, options?: Options) onParseError(event.newValue, e) return } - const processedVal = beforeRead(newVal) + const cancel = Symbol("cancel") + const processedVal = beforeRead(newVal, cancel) + if (processedVal === cancel) return + set(processedVal) } } diff --git a/test/localStorageStore.test.ts b/test/localStorageStore.test.ts index 877dd51..f17d56a 100644 --- a/test/localStorageStore.test.ts +++ b/test/localStorageStore.test.ts @@ -90,7 +90,7 @@ describe('persisted()', () => { it('publishes updates', () => { const store = persisted('myKey7', 123) const values: number[] = [] - const unsub = store.subscribe((value : number) => { + const unsub = store.subscribe((value: number) => { if (value !== undefined) values.push(value) }) store.set(456) @@ -146,7 +146,7 @@ describe('persisted()', () => { values.push(val) }) - const event = new StorageEvent('storage', {key: 'beforeRead-test', newValue: "2"}) + const event = new StorageEvent('storage', { key: 'beforeRead-test', newValue: "2" }) window.dispatchEvent(event) expect(values).toEqual([0, 4]) @@ -160,39 +160,79 @@ describe('persisted()', () => { expect(JSON.parse(localStorage.getItem("beforeWrite-test") as string)).toEqual(4) }) + + it("allows to cancel read operation during initialization", () => { + localStorage.setItem("beforeRead-init-cancel", JSON.stringify(2)) + const beforeRead = vi.fn((_: any, cancel: S) => cancel) + const store = persisted("beforeRead-init-cancel", 0, { beforeRead }) + expect(beforeRead).toHaveBeenCalledOnce() + expect(get(store)).toEqual(0) + }) + + it("allows to cancel read operation during event handling", () => { + // Will only call beforeRead on init if key exists, so creates key + localStorage.setItem("beforeRead-cancel", JSON.stringify(2)) + + const beforeRead = vi.fn((_: any, cancel: S) => cancel) + const store = persisted("beforeRead-cancel", 0, { beforeRead }) + + const values: number[] = [] + + const unsub = store.subscribe((val: number) => { + values.push(val) + }) + + const event = new StorageEvent('storage', { key: 'beforeRead-cancel', newValue: "2" }) + window.dispatchEvent(event) + + expect(beforeRead).toHaveBeenCalledTimes(2) + expect(values).toEqual([0]) + + unsub() + }) + + it("allows to cancel write operation", () => { + const beforeWrite = vi.fn((_: number, cancel: S) => cancel) + const store = persisted("beforeWrite-cancel", 0, { beforeWrite }) + store.set(2) + + expect(JSON.parse(localStorage.getItem("beforeWrite-cancel") as string)).toEqual(null) + expect(get(store)).toEqual(2) + expect(beforeWrite).toHaveBeenCalledOnce() + }) }) describe('handles window.storage event', () => { - type NumberDict = { [key: string] : number } + type NumberDict = { [key: string]: number } it('sets storage when key matches', () => { - const store = persisted('myKey8', {a: 1}) + const store = persisted('myKey8', { a: 1 }) const values: NumberDict[] = [] const unsub = store.subscribe((value: NumberDict) => { values.push(value) }) - const event = new StorageEvent('storage', {key: 'myKey8', newValue: '{"a": 1, "b": 2}'}) + const event = new StorageEvent('storage', { key: 'myKey8', newValue: '{"a": 1, "b": 2}' }) window.dispatchEvent(event) - expect(values).toEqual([{a: 1}, {a: 1, b: 2}]) + expect(values).toEqual([{ a: 1 }, { a: 1, b: 2 }]) unsub() }) it('ignores storages events when value is null', () => { - const store = persisted('myKey9', {a: 1}) + const store = persisted('myKey9', { a: 1 }) const values: NumberDict[] = [] const unsub = store.subscribe((value: NumberDict) => { values.push(value) }) - const event = new StorageEvent('storage', {key: 'myKey9', newValue: null}) + const event = new StorageEvent('storage', { key: 'myKey9', newValue: null }) window.dispatchEvent(event) - expect(values).toEqual([{a: 1}]) + expect(values).toEqual([{ a: 1 }]) unsub() }) @@ -205,7 +245,7 @@ describe('persisted()', () => { values.push(value) }) - const event = new StorageEvent('storage', {key: 'unknownKey', newValue: '2'}) + const event = new StorageEvent('storage', { key: 'unknownKey', newValue: '2' }) window.dispatchEvent(event) expect(values).toEqual([1]) @@ -219,7 +259,7 @@ describe('persisted()', () => { const store = persisted('myKeyb', 1) const values: number[] = [] - const event = new StorageEvent('storage', {key: 'myKeyb', newValue: '2'}) + const event = new StorageEvent('storage', { key: 'myKeyb', newValue: '2' }) window.dispatchEvent(event) const unsub = store.subscribe((value: number) => { @@ -239,7 +279,7 @@ describe('persisted()', () => { values.push(value) }) - const event = new StorageEvent('storage', {key: 'myKey10', newValue: '2'}) + const event = new StorageEvent('storage', { key: 'myKey10', newValue: '2' }) window.dispatchEvent(event) expect(values).toEqual([1]) @@ -255,7 +295,7 @@ describe('persisted()', () => { values.push(value) }) - const event = new StorageEvent('storage', {key: 'myKey13', newValue: '2'}) + const event = new StorageEvent('storage', { key: 'myKey13', newValue: '2' }) window.dispatchEvent(event) expect(values).toEqual([1]) @@ -278,7 +318,7 @@ describe('persisted()', () => { store.update(d => d.add(4)) expect(value).toEqual(testSet) - expect(localStorage.myKey11).toEqual(serializer.stringify(new Set([1,2,3,4]))) + expect(localStorage.myKey11).toEqual(serializer.stringify(new Set([1, 2, 3, 4]))) }) it('lets you switch storage type', () => { From 9e0c092bf8279b5ff9d16a3b7997dbd2ca6390b6 Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Thu, 16 May 2024 12:21:10 +0000 Subject: [PATCH 07/12] Add seperate store and serializer types --- index.ts | 34 +++++++++++++++++----------------- test/localStorageStore.test.ts | 6 +++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/index.ts b/index.ts index de3ada4..0f089db 100644 --- a/index.ts +++ b/index.ts @@ -14,22 +14,22 @@ const stores: Stores = { session: {} } -export interface Serializer { - parse(text: string): any - stringify(object: any): string +export interface Serializer { + parse(text: string): T + stringify(object: T): string } export type StorageType = 'local' | 'session' -export interface Options { - serializer?: Serializer +export interface Options { + serializer?: Serializer storage?: StorageType, syncTabs?: boolean, onError?: (e: unknown) => void onWriteError?: (e: unknown) => void onParseError?: (newValue: string | null, e: unknown) => void - beforeRead?: (val: any, cancel: S) => T | S - beforeWrite?: (val: T, cancel: S) => any | S + beforeRead?: (val: SerializerType, cancel: S) => StoreType | S + beforeWrite?: (val: StoreType, cancel: S) => SerializerType | S } function getStorage(type: StorageType) { @@ -37,11 +37,11 @@ function getStorage(type: StorageType) { } /** @deprecated `writable()` has been renamed to `persisted()` */ -export function writable(key: string, initialValue: T, options?: Options): Writable { +export function writable(key: string, initialValue: StoreType, options?: Options): Writable { 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'") - return persisted(key, initialValue, options) + return persisted(key, initialValue, options) } -export function persisted(key: string, initialValue: T, options?: Options): Writable { +export function persisted(key: string, initialValue: StoreType, options?: Options): Writable { if (options?.onError) console.warn("onError has been deprecated. Please use onWriteError instead") const serializer = options?.serializer ?? JSON @@ -50,13 +50,13 @@ export function persisted(key: string, initialValue: T, options?: Options) const onWriteError = options?.onWriteError ?? options?.onError ?? ((e) => console.error(`Error when writing value from persisted store "${key}" to ${storageType}`, e)) const onParseError = options?.onParseError ?? ((newVal, e) => console.error(`Error when parsing ${newVal ? '"' + newVal + '"' : "value"} from persisted store "${key}"`, e)) - const beforeRead = options?.beforeRead ?? ((val) => val as T) - const beforeWrite = options?.beforeWrite ?? ((val) => val as any) + const beforeRead = options?.beforeRead ?? ((val) => val as unknown as StoreType) + const beforeWrite = options?.beforeWrite ?? ((val) => val as unknown as SerializerType) const browser = typeof (window) !== 'undefined' && typeof (document) !== 'undefined' const storage = browser ? getStorage(storageType) : null - function updateStorage(key: string, value: T) { + function updateStorage(key: string, value: StoreType) { const cancel = Symbol("cancel") const newVal = beforeWrite(value, cancel) if (newVal === cancel) return @@ -68,10 +68,10 @@ export function persisted(key: string, initialValue: T, options?: Options) } } - function maybeLoadInitial(): T { + function maybeLoadInitial(): StoreType { function serialize(json: any) { try { - return serializer.parse(json) + return serializer.parse(json) } catch (e) { onParseError(json, e) } @@ -118,11 +118,11 @@ export function persisted(key: string, initialValue: T, options?: Options) const { subscribe, set } = store stores[storageType][key] = { - set(value: T) { + set(value: StoreType) { set(value) updateStorage(key, value) }, - update(callback: Updater) { + update(callback: Updater) { return store.update((last) => { const value = callback(last) diff --git a/test/localStorageStore.test.ts b/test/localStorageStore.test.ts index f17d56a..255da6c 100644 --- a/test/localStorageStore.test.ts +++ b/test/localStorageStore.test.ts @@ -135,11 +135,11 @@ describe('persisted()', () => { describe("beforeRead and beforeWrite", () => { it("allows modifying initial value before reading", () => { localStorage.setItem("beforeRead-init-test", JSON.stringify(2)) - const store = persisted("beforeRead-init-test", 0, { beforeRead: (v) => v * 2 }) + const store = persisted("beforeRead-init-test", 0, { beforeRead: (v: number) => v * 2 }) expect(get(store)).toEqual(4) }) it("allows modifying value before reading upon event", () => { - const store = persisted("beforeRead-test", 0, { beforeRead: (v) => v * 2 }) + const store = persisted("beforeRead-test", 0, { beforeRead: (v: number) => v * 2 }) const values: number[] = [] const unsub = store.subscribe((val: number) => { @@ -193,7 +193,7 @@ describe('persisted()', () => { it("allows to cancel write operation", () => { const beforeWrite = vi.fn((_: number, cancel: S) => cancel) - const store = persisted("beforeWrite-cancel", 0, { beforeWrite }) + const store = persisted("beforeWrite-cancel", 0, { beforeWrite }) store.set(2) expect(JSON.parse(localStorage.getItem("beforeWrite-cancel") as string)).toEqual(null) From 81a918c19f1c6819231ffa1885cca0bacb23484c Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Thu, 16 May 2024 12:21:33 +0000 Subject: [PATCH 08/12] Fix single type-error --- test/localStorageStore.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/localStorageStore.test.ts b/test/localStorageStore.test.ts index 255da6c..a822ab7 100644 --- a/test/localStorageStore.test.ts +++ b/test/localStorageStore.test.ts @@ -289,7 +289,7 @@ describe('persisted()', () => { it("doesn't update, when syncTabs option is disabled", () => { const store = persisted('myKey13', 1, { syncTabs: false }) - const values = [] + const values: number[] = [] const unsub = store.subscribe((value) => { values.push(value) From b3b985f7ba42bdc4098cecfde790712509591124 Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Thu, 16 May 2024 12:27:16 +0000 Subject: [PATCH 09/12] Update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2d55e7e..0129da1 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ export const preferences = persisted('local-storage-key', 'default-value', { syncTabs: true, // choose whether to sync localStorage across tabs, default is true onWriteError: (error) => {/* handle or rethrow */}, // Defaults to console.error with the error object onParseError: (raw, error) => {/* handle or rethrow */}, // Defaults to console.error with the error object - beforeRead: (value) => {/* change value after serialization but before setting store to return value*/}, - beforeWrite: (value) => {/* change value after writing to store, but before writing return value to local storage*/}, + beforeRead: (value, cancel) => {/* change value after serialization but before setting store to return value. Return cancel to cancel the operation*/}, + beforeWrite: (value, cancel) => {/* change value after writing to store, but before writing return value to local storage. Return cancel to cancel the operation*/}, }) ``` From f89f201f04e1bc94033c35483079d35f5134c552 Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Thu, 23 May 2024 11:45:25 +0000 Subject: [PATCH 10/12] Revert implementation, documentation and testing for cancelling before-Write and -Read --- README.md | 4 ++-- index.ts | 16 +++++--------- test/localStorageStore.test.ts | 40 ---------------------------------- 3 files changed, 7 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 0129da1..2e6b31f 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ export const preferences = persisted('local-storage-key', 'default-value', { syncTabs: true, // choose whether to sync localStorage across tabs, default is true onWriteError: (error) => {/* handle or rethrow */}, // Defaults to console.error with the error object onParseError: (raw, error) => {/* handle or rethrow */}, // Defaults to console.error with the error object - beforeRead: (value, cancel) => {/* change value after serialization but before setting store to return value. Return cancel to cancel the operation*/}, - beforeWrite: (value, cancel) => {/* change value after writing to store, but before writing return value to local storage. Return cancel to cancel the operation*/}, + beforeRead: (value) => {/* change value after serialization but before setting store to return value. Return cancel to cancel the operation*/}, + beforeWrite: (value) => {/* change value after writing to store, but before writing return value to local storage. Return cancel to cancel the operation*/}, }) ``` diff --git a/index.ts b/index.ts index 0f089db..f5aaffd 100644 --- a/index.ts +++ b/index.ts @@ -28,8 +28,8 @@ export interface Options { onError?: (e: unknown) => void onWriteError?: (e: unknown) => void onParseError?: (newValue: string | null, e: unknown) => void - beforeRead?: (val: SerializerType, cancel: S) => StoreType | S - beforeWrite?: (val: StoreType, cancel: S) => SerializerType | S + beforeRead?: (val: SerializerType) => StoreType + beforeWrite?: (val: StoreType) => SerializerType } function getStorage(type: StorageType) { @@ -57,9 +57,7 @@ export function persisted(key: string, initialValue: const storage = browser ? getStorage(storageType) : null function updateStorage(key: string, value: StoreType) { - const cancel = Symbol("cancel") - const newVal = beforeWrite(value, cancel) - if (newVal === cancel) return + const newVal = beforeWrite(value) try { storage?.setItem(key, serializer.stringify(newVal)) @@ -82,9 +80,7 @@ export function persisted(key: string, initialValue: const serialized = serialize(json) if (serialized == null) return initialValue - const cancel = Symbol("cancel") - const newVal = beforeRead(serialized, cancel) - if (newVal === cancel) return initialValue + const newVal = beforeRead(serialized) return newVal } @@ -101,9 +97,7 @@ export function persisted(key: string, initialValue: onParseError(event.newValue, e) return } - const cancel = Symbol("cancel") - const processedVal = beforeRead(newVal, cancel) - if (processedVal === cancel) return + const processedVal = beforeRead(newVal) set(processedVal) } diff --git a/test/localStorageStore.test.ts b/test/localStorageStore.test.ts index a822ab7..ec00a38 100644 --- a/test/localStorageStore.test.ts +++ b/test/localStorageStore.test.ts @@ -160,46 +160,6 @@ describe('persisted()', () => { expect(JSON.parse(localStorage.getItem("beforeWrite-test") as string)).toEqual(4) }) - - it("allows to cancel read operation during initialization", () => { - localStorage.setItem("beforeRead-init-cancel", JSON.stringify(2)) - const beforeRead = vi.fn((_: any, cancel: S) => cancel) - const store = persisted("beforeRead-init-cancel", 0, { beforeRead }) - expect(beforeRead).toHaveBeenCalledOnce() - expect(get(store)).toEqual(0) - }) - - it("allows to cancel read operation during event handling", () => { - // Will only call beforeRead on init if key exists, so creates key - localStorage.setItem("beforeRead-cancel", JSON.stringify(2)) - - const beforeRead = vi.fn((_: any, cancel: S) => cancel) - const store = persisted("beforeRead-cancel", 0, { beforeRead }) - - const values: number[] = [] - - const unsub = store.subscribe((val: number) => { - values.push(val) - }) - - const event = new StorageEvent('storage', { key: 'beforeRead-cancel', newValue: "2" }) - window.dispatchEvent(event) - - expect(beforeRead).toHaveBeenCalledTimes(2) - expect(values).toEqual([0]) - - unsub() - }) - - it("allows to cancel write operation", () => { - const beforeWrite = vi.fn((_: number, cancel: S) => cancel) - const store = persisted("beforeWrite-cancel", 0, { beforeWrite }) - store.set(2) - - expect(JSON.parse(localStorage.getItem("beforeWrite-cancel") as string)).toEqual(null) - expect(get(store)).toEqual(2) - expect(beforeWrite).toHaveBeenCalledOnce() - }) }) describe('handles window.storage event', () => { From 007544cb712b4b94b8eb5d052fd6781db2bbc421 Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Thu, 23 May 2024 14:37:52 +0000 Subject: [PATCH 11/12] Remove mention of cancel param from readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2e6b31f..2d55e7e 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ export const preferences = persisted('local-storage-key', 'default-value', { syncTabs: true, // choose whether to sync localStorage across tabs, default is true onWriteError: (error) => {/* handle or rethrow */}, // Defaults to console.error with the error object onParseError: (raw, error) => {/* handle or rethrow */}, // Defaults to console.error with the error object - beforeRead: (value) => {/* change value after serialization but before setting store to return value. Return cancel to cancel the operation*/}, - beforeWrite: (value) => {/* change value after writing to store, but before writing return value to local storage. Return cancel to cancel the operation*/}, + beforeRead: (value) => {/* change value after serialization but before setting store to return value*/}, + beforeWrite: (value) => {/* change value after writing to store, but before writing return value to local storage*/}, }) ``` From cb0a31737125c5ac1f643e8d76686287df843943 Mon Sep 17 00:00:00 2001 From: bertmad3400 Date: Sun, 26 May 2024 10:15:19 +0000 Subject: [PATCH 12/12] Set StoreType as the default shape for SerializerType --- index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.ts b/index.ts index f5aaffd..426993c 100644 --- a/index.ts +++ b/index.ts @@ -37,11 +37,11 @@ function getStorage(type: StorageType) { } /** @deprecated `writable()` has been renamed to `persisted()` */ -export function writable(key: string, initialValue: StoreType, options?: Options): Writable { +export function writable(key: string, initialValue: StoreType, options?: Options): Writable { 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'") return persisted(key, initialValue, options) } -export function persisted(key: string, initialValue: StoreType, options?: Options): Writable { +export function persisted(key: string, initialValue: StoreType, options?: Options): Writable { if (options?.onError) console.warn("onError has been deprecated. Please use onWriteError instead") const serializer = options?.serializer ?? JSON