Skip to content

Commit

Permalink
refactor(store) simplify key value store, add SWR through local stora…
Browse files Browse the repository at this point in the history
…ge behavior
  • Loading branch information
bjoerge committed Sep 27, 2024
1 parent 7b9b556 commit baad873
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 204 deletions.
52 changes: 0 additions & 52 deletions packages/sanity/src/core/store/key-value/KeyValueStore.ts

This file was deleted.

53 changes: 0 additions & 53 deletions packages/sanity/src/core/store/key-value/backends/localStorage.ts

This file was deleted.

26 changes: 0 additions & 26 deletions packages/sanity/src/core/store/key-value/backends/memory.ts

This file was deleted.

15 changes: 0 additions & 15 deletions packages/sanity/src/core/store/key-value/backends/types.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/sanity/src/core/store/key-value/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './KeyValueStore'
export * from './keyValueStore'
export * from './types'
9 changes: 9 additions & 0 deletions packages/sanity/src/core/store/key-value/keyValueStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {type SanityClient} from '@sanity/client'

import {withLocalStorageSWR} from './localStorageSWR'
import {createServerKeyValueStore} from './serverKeyValueStore'

/** @internal */
export function createKeyValueStore(options: {client: SanityClient}) {
return withLocalStorageSWR(createServerKeyValueStore(options))
}
39 changes: 39 additions & 0 deletions packages/sanity/src/core/store/key-value/localStorageSWR.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {isEqual} from 'lodash'
import {fromEvent, merge, NEVER} from 'rxjs'
import {distinctUntilChanged, filter, map, tap} from 'rxjs/operators'

import {localStoreStorage} from './storage/localStoreStorage'
import {type KeyValueStore, type KeyValueStoreValue} from './types'

// Whether or not to enable instant user sync between tabs
// if set to true, the setting will update instantly across all tabs
const ENABLE_CROSS_TAB_SYNC = false

/**
* Wraps a KeyValueStore and adds Stale-While-Revalidate (SWR) behavior to it
*/
export function withLocalStorageSWR(wrappedStore: KeyValueStore): KeyValueStore {
const storageEvent = ENABLE_CROSS_TAB_SYNC ? fromEvent<StorageEvent>(window, 'storage') : NEVER

function getKey(key: string) {
const lsUpdates = storageEvent.pipe(
filter((event) => event.key === key),
map(() => localStoreStorage.getKey(key)),
)

return merge(lsUpdates, wrappedStore.getKey(key)).pipe(
distinctUntilChanged(isEqual),
tap((value) => {
localStoreStorage.setKey(key, value)
}),
)
}
function setKey(key: string, nextValue: KeyValueStoreValue) {
localStoreStorage.setKey(key, nextValue)
return wrappedStore.setKey(key, nextValue)
}
return {
getKey,
setKey,
}
}
49 changes: 49 additions & 0 deletions packages/sanity/src/core/store/key-value/serverKeyValueStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {type SanityClient} from '@sanity/client'
import {isEqual} from 'lodash'
import {concat, type Observable, Subject} from 'rxjs'
import {distinctUntilChanged, filter, map} from 'rxjs/operators'

import {createServerStorage} from './storage/serverStorage'
import {type KeyValueStore, type KeyValueStoreValue} from './types'

export function createServerKeyValueStore({client}: {client: SanityClient}): KeyValueStore {
const serverStorage = createServerStorage({client})

const events$ = new Subject<{
type: 'optimistic' | 'commit'
key: string
value: KeyValueStoreValue
}>()

function getKey(key: string) {
return serverStorage.getKey(key)
}

function setKey(key: string, value: KeyValueStoreValue) {
events$.next({type: 'optimistic', key, value})

/*
* The backend returns the result of the set operation, so we can just pass that along.
* Most utils do not use it (they will take advantage of local state first) but it reflects the
* backend function and could be useful for debugging.
*/
return serverStorage.setKey(key, value).then((storedValue) => {
events$.next({type: 'commit', key, value: storedValue})
return storedValue
})
}

return {
getKey(key: string): Observable<KeyValueStoreValue | null> {
return concat(
getKey(key),
events$.pipe(
filter((event) => event.key === key),
map((event) => event.value),
distinctUntilChanged(isEqual),
),
)
},
setKey,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {supportsLocalStorage} from '../../../util/supportsLocalStorage'
import {type KeyValueStoreValue} from '../types'
import {createMemoryStorage} from './memoryStorage'

function tryParse(val: string) {
try {
return JSON.parse(val)
} catch (err) {
// eslint-disable-next-line no-console
console.warn(`Failed to parse settings: ${err.message}`)
return null
}
}

function createLocalStoreStorage() {
if (!supportsLocalStorage) {
return createMemoryStorage()
}

function getKey(key: string): KeyValueStoreValue | null {
const val = localStorage.getItem(key)

return val === null ? null : tryParse(val)
}

const setKey = function (key: string, nextValue: KeyValueStoreValue) {
// Can't stringify undefined, and nulls are what
// `getItem` returns when key does not exist
if (typeof nextValue === 'undefined' || nextValue === null) {
localStorage.removeItem(key)
} else {
localStorage.setItem(key, JSON.stringify(nextValue))
}
}
return {getKey, setKey}
}

export const localStoreStorage = createLocalStoreStorage()
13 changes: 13 additions & 0 deletions packages/sanity/src/core/store/key-value/storage/memoryStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {type KeyValueStoreValue} from '../types'

export function createMemoryStorage() {
const DB = Object.create(null)
return {
getKey(key: string): KeyValueStoreValue | null {
return DB[key] || null
},
setKey(key: string, value: KeyValueStoreValue) {
DB[key] = value
},
}
}
Loading

0 comments on commit baad873

Please sign in to comment.