Skip to content

Commit

Permalink
Merge pull request #307 from joshnuss/remove-cache-ssr
Browse files Browse the repository at this point in the history
Removes store caching during SSR
  • Loading branch information
joshnuss authored Dec 23, 2024
2 parents 5046b27 + 986d28f commit 4cafa41
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 46 deletions.
94 changes: 50 additions & 44 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,20 @@ export function writable<StoreType, SerializerType = StoreType>(key: string, ini
export function persisted<StoreType, SerializerType = StoreType>(key: string, initialValue: StoreType, options?: Options<StoreType, SerializerType>): Persisted<StoreType> {
if (options?.onError) console.warn("onError has been deprecated. Please use onWriteError instead")

const serializer = options?.serializer ?? JSON
const storageType = options?.storage ?? 'local'
const browser = typeof (window) !== 'undefined' && typeof (document) !== 'undefined'

if (browser && stores[storageType][key]) {
return stores[storageType][key]
}

const serializer = options?.serializer ?? JSON
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 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: StoreType) {
Expand Down Expand Up @@ -88,52 +92,54 @@ export function persisted<StoreType, SerializerType = StoreType>(key: string, in
return newVal
}

if (!stores[storageType][key]) {
const initial = maybeLoadInitial()
const store = internal(initial, (set) => {
if (browser && storageType == 'local' && syncTabs) {
const handleStorage = (event: StorageEvent) => {
if (event.key === key && event.newValue) {
let newVal: any
try {
newVal = serializer.parse(event.newValue)
} catch (e) {
onParseError(event.newValue, e)
return
}
const processedVal = beforeRead(newVal)

set(processedVal)
const initial = maybeLoadInitial()
const store = internal(initial, (set) => {
if (browser && storageType == 'local' && syncTabs) {
const handleStorage = (event: StorageEvent) => {
if (event.key === key && event.newValue) {
let newVal: any
try {
newVal = serializer.parse(event.newValue)
} catch (e) {
onParseError(event.newValue, e)
return
}
}
const processedVal = beforeRead(newVal)

window.addEventListener("storage", handleStorage)

return () => window.removeEventListener("storage", handleStorage)
set(processedVal)
}
}
})

const { subscribe, set } = store
window.addEventListener("storage", handleStorage)

stores[storageType][key] = {
set(value: StoreType) {
set(value)
updateStorage(key, value)
},
update(callback: Updater<StoreType>) {
return store.update((last) => {
const value = callback(last)

updateStorage(key, value)

return value
})
},
reset() {
this.set(initialValue)
},
subscribe
return () => window.removeEventListener("storage", handleStorage)
}
})

const { subscribe, set } = store
const persistedStore = {
set(value: StoreType) {
set(value)
updateStorage(key, value)
},
update(callback: Updater<StoreType>) {
return store.update((last) => {
const value = callback(last)

updateStorage(key, value)

return value
})
},
reset() {
this.set(initialValue)
},
subscribe
}

if (browser) {
stores[storageType][key] = persistedStore
}
return stores[storageType][key]

return persistedStore
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @vitest-environment jsdom
import { persisted, writable } from '../index'
import { get } from 'svelte/store'
import { expect, vi, beforeEach, describe, test, it } from 'vitest'
Expand Down
144 changes: 144 additions & 0 deletions test/localStorageStore.node.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// @vitest-environment node
import { persisted, writable } from '../index'
import { get } from 'svelte/store'
import { expect, vi, describe, test, it } from 'vitest'

describe('writable()', () => {
test('it works, but raises deprecation warning', () => {
console.warn = vi.fn()

const store = writable('myKey2', 'initial')
const value = get(store)

expect(value).toEqual('initial')
expect(console.warn).toHaveBeenCalledWith(expect.stringMatching(/deprecated/))
})
})

describe('persisted()', () => {
test('uses initial value if nothing in local storage', () => {
const store = persisted('myKey', 123)
const value = get(store)

expect(value).toEqual(123)
})

describe('set()', () => {
test('replaces old value', () => {
const store = persisted('myKey3', '')
store.set('new-value')
const value = get(store)

expect(value).toEqual('new-value')
})

test('adds new value', () => {
const store = persisted('myKey4', '')
store.set('new-value')
const value = get(store)

expect(value).toEqual('new-value')
})
})

describe('update()', () => {
test('replaces old value', () => {
const store = persisted('myKey5', 123)
store.update(n => n + 1)
const value = get(store)

expect(value).toEqual(124)
})

test('adds new value', () => {
const store = persisted('myKey6', 123)
store.update(n => n + 1)
const value = get(store)

expect(value).toEqual(124)
})
})

describe('reset', () => {
it('resets to initial value', () => {
const store = persisted('myKey14', 123);
store.set(456);
store.reset();
const value = get(store);

expect(value).toEqual(123);
});
});

describe('subscribe()', () => {
it('publishes updates', () => {
const store = persisted('myKey7', 123)
const values: number[] = []
const unsub = store.subscribe((value: number) => {
if (value !== undefined) values.push(value)
})
store.set(456)
store.set(999)

expect(values).toEqual([123, 456, 999])

unsub()
})
})

it("doesn't handle duplicate stores with the same key", () => {
const store1 = persisted('same-key', 1)
const values1: number[] = []

const unsub1 = store1.subscribe(value => {
values1.push(value)
})

store1.set(2)

const store2 = persisted('same-key', 99)
const values2: number[] = []

const unsub2 = store2.subscribe(value => {
values2.push(value)
})

store1.set(3)
store2.set(4)

expect(values1).toEqual([1, 2, 3])
expect(values2).toEqual([99, 4])
expect(get(store1)).not.toEqual(get(store2))

expect(store1).not.toEqual(store2)

unsub1()
unsub2()
})

it('allows custom serialize/deserialize functions', () => {
const serializer = {
stringify: (set: Set<number>) => JSON.stringify(Array.from(set)),
parse: (json: string) => new Set(JSON.parse(json)),
}

const testSet = new Set([1, 2, 3])

const store = persisted('myKey11', testSet, { serializer })
const value = get(store)

store.update(d => d.add(4))

expect(value).toEqual(testSet)
})

it('lets you switch storage type', () => {
const store = persisted('myKey12', 'foo', {
storage: 'session'
})

store.set('bar')

expect(get(store)).toEqual('bar')
})
})
1 change: 1 addition & 0 deletions test/readDomExceptions.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @vitest-environment jsdom
import { persisted } from '../index'
import { expect, vi, beforeEach, describe, it } from 'vitest'

Expand Down
3 changes: 1 addition & 2 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
globals: true,
environment: 'jsdom'
globals: true
},
})

0 comments on commit 4cafa41

Please sign in to comment.