-
Notifications
You must be signed in to change notification settings - Fork 428
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(store) simplify key value store, add SWR through local stora…
…ge behavior
- Loading branch information
Showing
14 changed files
with
186 additions
and
204 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
53 changes: 0 additions & 53 deletions
53
packages/sanity/src/core/store/key-value/backends/localStorage.ts
This file was deleted.
Oops, something went wrong.
26 changes: 0 additions & 26 deletions
26
packages/sanity/src/core/store/key-value/backends/memory.ts
This file was deleted.
Oops, something went wrong.
15 changes: 0 additions & 15 deletions
15
packages/sanity/src/core/store/key-value/backends/types.ts
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
export * from './KeyValueStore' | ||
export * from './keyValueStore' | ||
export * from './types' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
39
packages/sanity/src/core/store/key-value/localStorageSWR.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
49
packages/sanity/src/core/store/key-value/serverKeyValueStore.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
packages/sanity/src/core/store/key-value/storage/localStoreStorage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
13
packages/sanity/src/core/store/key-value/storage/memoryStorage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}, | ||
} | ||
} |
Oops, something went wrong.