[Persisted] Make broadcast subscriptions granular by key (#4874)
* Add fast path for guaranteed noop updates * Change persisted.onUpdate() API to take a key * Implement granular broadcast listeners
This commit is contained in:
parent
966f6c511f
commit
686d5ebb53
17 changed files with 95 additions and 42 deletions
|
@ -41,7 +41,10 @@ export async function write<K extends keyof Schema>(
|
|||
}
|
||||
write satisfies PersistedApi['write']
|
||||
|
||||
export function onUpdate(_cb: () => void): () => void {
|
||||
export function onUpdate<K extends keyof Schema>(
|
||||
_key: K,
|
||||
_cb: (v: Schema[K]) => void,
|
||||
): () => void {
|
||||
return () => {}
|
||||
}
|
||||
onUpdate satisfies PersistedApi['onUpdate']
|
||||
|
|
|
@ -47,18 +47,36 @@ export async function write<K extends keyof Schema>(
|
|||
// Don't fire the update listeners yet to avoid a loop.
|
||||
// If there was a change, we'll receive the broadcast event soon enough which will do that.
|
||||
}
|
||||
try {
|
||||
if (JSON.stringify({v: _state[key]}) === JSON.stringify({v: value})) {
|
||||
// Fast path for updates that are guaranteed to be noops.
|
||||
// This is good mostly because it avoids useless broadcasts to other tabs.
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore and go through the normal path.
|
||||
}
|
||||
_state = {
|
||||
..._state,
|
||||
[key]: value,
|
||||
}
|
||||
writeToStorage(_state)
|
||||
broadcast.postMessage({event: UPDATE_EVENT})
|
||||
broadcast.postMessage({event: {type: UPDATE_EVENT, key}})
|
||||
broadcast.postMessage({event: UPDATE_EVENT}) // Backcompat while upgrading
|
||||
}
|
||||
write satisfies PersistedApi['write']
|
||||
|
||||
export function onUpdate(cb: () => void): () => void {
|
||||
_emitter.addListener('update', cb)
|
||||
return () => _emitter.removeListener('update', cb)
|
||||
export function onUpdate<K extends keyof Schema>(
|
||||
key: K,
|
||||
cb: (v: Schema[K]) => void,
|
||||
): () => void {
|
||||
const listener = () => cb(get(key))
|
||||
_emitter.addListener('update', listener) // Backcompat while upgrading
|
||||
_emitter.addListener('update:' + key, listener)
|
||||
return () => {
|
||||
_emitter.removeListener('update', listener) // Backcompat while upgrading
|
||||
_emitter.removeListener('update:' + key, listener)
|
||||
}
|
||||
}
|
||||
onUpdate satisfies PersistedApi['onUpdate']
|
||||
|
||||
|
@ -72,12 +90,23 @@ export async function clearStorage() {
|
|||
clearStorage satisfies PersistedApi['clearStorage']
|
||||
|
||||
async function onBroadcastMessage({data}: MessageEvent) {
|
||||
if (typeof data === 'object' && data.event === UPDATE_EVENT) {
|
||||
if (
|
||||
typeof data === 'object' &&
|
||||
(data.event === UPDATE_EVENT || // Backcompat while upgrading
|
||||
data.event?.type === UPDATE_EVENT)
|
||||
) {
|
||||
// read next state, possibly updated by another tab
|
||||
const next = readFromStorage()
|
||||
if (next === _state) {
|
||||
return
|
||||
}
|
||||
if (next) {
|
||||
_state = next
|
||||
_emitter.emit('update')
|
||||
if (typeof data.event.key === 'string') {
|
||||
_emitter.emit('update:' + data.event.key)
|
||||
} else {
|
||||
_emitter.emit('update') // Backcompat while upgrading
|
||||
}
|
||||
} else {
|
||||
logger.error(
|
||||
`persisted state: handled update update from broadcast channel, but found no data`,
|
||||
|
|
|
@ -4,6 +4,9 @@ export type PersistedApi = {
|
|||
init(): Promise<void>
|
||||
get<K extends keyof Schema>(key: K): Schema[K]
|
||||
write<K extends keyof Schema>(key: K, value: Schema[K]): Promise<void>
|
||||
onUpdate(_cb: () => void): () => void
|
||||
onUpdate<K extends keyof Schema>(
|
||||
key: K,
|
||||
cb: (v: Schema[K]) => void,
|
||||
): () => void
|
||||
clearStorage: () => Promise<void>
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue