Get rid of createEventStore, throttle storage adapters

This commit is contained in:
Jon Staab
2024-09-13 14:29:22 -07:00
parent 97b0d78b9d
commit 73949b9627
3 changed files with 90 additions and 77 deletions
+1 -3
View File
@@ -3,12 +3,10 @@ import {Repository, Relay, LOCAL_RELAY_URL, getFilterResultCardinality} from "@w
import type {TrustedEvent, Filter} from "@welshman/util" import type {TrustedEvent, Filter} from "@welshman/util"
import {Tracker, subscribe as baseSubscribe} from "@welshman/net" import {Tracker, subscribe as baseSubscribe} from "@welshman/net"
import type {SubscribeRequestWithHandlers} from "@welshman/net" import type {SubscribeRequestWithHandlers} from "@welshman/net"
import {createEventStore, custom} from "@welshman/store" import {custom} from "@welshman/store"
export const repository = new Repository<TrustedEvent>() export const repository = new Repository<TrustedEvent>()
export const events = createEventStore(repository)
export const relay = new Relay(repository) export const relay = new Relay(repository)
export const tracker = new Tracker() export const tracker = new Tracker()
+59 -18
View File
@@ -3,15 +3,19 @@ import type {IDBPDatabase} from "idb"
import {throttle} from "throttle-debounce" import {throttle} from "throttle-debounce"
import {writable} from "svelte/store" import {writable} from "svelte/store"
import type {Unsubscriber, Writable} from "svelte/store" import type {Unsubscriber, Writable} from "svelte/store"
import {randomInt, fromPairs} from "@welshman/lib" import {indexBy, fromPairs} from "@welshman/lib"
import type {TrustedEvent, Repository} from "@welshman/util"
import type {Tracker} from "@welshman/net" import type {Tracker} from "@welshman/net"
import {withGetter, adapter, custom} from "@welshman/store" import {withGetter, adapter, throttled, custom} from "@welshman/store"
export type Item = Record<string, any> export type IndexedDbAdapterOptions = {
migrate?: (items: any[]) => any[]
}
export type IndexedDbAdapter = { export type IndexedDbAdapter = {
keyPath: string keyPath: string
store: Writable<Item[]> store: Writable<any[]>
options?: IndexedDbAdapterOptions
} }
export let db: IDBPDatabase export let db: IDBPDatabase
@@ -49,21 +53,29 @@ export const bulkDelete = async (name: string, ids: string[]) => {
export const initIndexedDbAdapter = async (name: string, adapter: IndexedDbAdapter) => { export const initIndexedDbAdapter = async (name: string, adapter: IndexedDbAdapter) => {
let prevRecords = await getAll(name) let prevRecords = await getAll(name)
if (adapter.options?.migrate) {
prevRecords = adapter.options.migrate(prevRecords)
}
adapter.store.set(prevRecords) adapter.store.set(prevRecords)
adapter.store.subscribe( adapter.store.subscribe(
throttle(randomInt(3000, 5000), async (newRecords: Item[]) => { async (currentRecords: any[]) => {
if (dead.get()) { if (dead.get()) {
return return
} }
const currentIds = new Set(newRecords.map(item => item[adapter.keyPath])) const currentIds = new Set(currentRecords.map(item => item[adapter.keyPath]))
const removedRecords = prevRecords.filter(r => !currentIds.has(r[adapter.keyPath])) const removedRecords = prevRecords.filter(r => !currentIds.has(r[adapter.keyPath]))
prevRecords = newRecords const prevRecordsById = indexBy(item => item[adapter.keyPath], prevRecords)
const updatedRecords = currentRecords.filter(r => r !== prevRecordsById.get(r[adapter.keyPath]))
if (newRecords.length > 0) { prevRecords = currentRecords
await bulkPut(name, newRecords)
if (updatedRecords.length > 0) {
await bulkPut(name, updatedRecords)
} }
if (removedRecords.length > 0) { if (removedRecords.length > 0) {
@@ -72,7 +84,7 @@ export const initIndexedDbAdapter = async (name: string, adapter: IndexedDbAdapt
removedRecords.map(item => item[adapter.keyPath]), removedRecords.map(item => item[adapter.keyPath]),
) )
} }
}), },
) )
} }
@@ -121,36 +133,47 @@ export const clearStorage = async () => {
await deleteDB(db.name) await deleteDB(db.name)
} }
export type StorageAdapterOptions = IndexedDbAdapterOptions & {
throttle?: number
}
export const storageAdapters = { export const storageAdapters = {
fromObjectStore: <T>(store: Writable<Record<string, T>>) => ({ fromObjectStore: <T>(store: Writable<Record<string, T>>, options: StorageAdapterOptions = {}) => ({
options,
keyPath: "key", keyPath: "key",
store: adapter({ store: throttled(options.throttle || 0, adapter({
store: store, store: store,
forward: ($data: Record<string, T>) => forward: ($data: Record<string, T>) =>
Object.entries($data).map(([key, value]) => ({key, value})), Object.entries($data).map(([key, value]) => ({key, value})),
backward: (data: {key: string, value: T}[]) => backward: (data: {key: string, value: T}[]) =>
fromPairs(data.map(({key, value}) => [key, value])), fromPairs(data.map(({key, value}) => [key, value])),
}), })),
}), }),
fromMapStore: <T>(store: Writable<Map<string, T>>) => ({ fromMapStore: <T>(store: Writable<Map<string, T>>, options: StorageAdapterOptions = {}) => ({
options,
keyPath: "key", keyPath: "key",
store: adapter({ store: throttled(options.throttle || 0, adapter({
store: store, store: store,
forward: ($data: Map<string, T>) => forward: ($data: Map<string, T>) =>
Array.from($data.entries()).map(([key, value]) => ({key, value})), Array.from($data.entries()).map(([key, value]) => ({key, value})),
backward: (data: {key: string, value: T}[]) => backward: (data: {key: string, value: T}[]) =>
new Map(data.map(({key, value}) => [key, value])), new Map(data.map(({key, value}) => [key, value])),
}), })),
}), }),
fromTracker: (tracker: Tracker) => ({ fromTracker: (tracker: Tracker, options: StorageAdapterOptions = {}) => ({
options,
keyPath: 'key', keyPath: 'key',
store: custom(setter => { store: custom(setter => {
const onUpdate = () => let onUpdate = () =>
setter( setter(
Array.from(tracker.data.entries()) Array.from(tracker.data.entries())
.map(([key, urls]) => ({key, value: Array.from(urls)})) .map(([key, urls]) => ({key, value: Array.from(urls)}))
) )
if (options.throttle) {
onUpdate = throttle(options.throttle, onUpdate)
}
onUpdate() onUpdate()
tracker.on('update', onUpdate) tracker.on('update', onUpdate)
@@ -160,4 +183,22 @@ export const storageAdapters = {
tracker.load(new Map(data.map(({key, value}) => [key, new Set(value)]))), tracker.load(new Map(data.map(({key, value}) => [key, new Set(value)]))),
}), }),
}), }),
fromRepository: (repository: Repository, options: StorageAdapterOptions = {}) => ({
options,
keyPath: 'id',
store: custom(setter => {
let onUpdate = () => setter(repository.dump())
if (options.throttle) {
onUpdate = throttle(options.throttle, onUpdate)
}
onUpdate()
repository.on('update', onUpdate)
return () => repository.off('update', onUpdate)
}, {
set: (events: TrustedEvent[]) => repository.load(events),
}),
}),
} }
+30 -56
View File
@@ -1,13 +1,13 @@
import {throttle} from "throttle-debounce" import {throttle} from "throttle-debounce"
import {derived, writable} from "svelte/store" import {derived, writable} from "svelte/store"
import type {Readable, Updater, Writable, Subscriber, Unsubscriber} from "svelte/store" import type {Readable, Writable, Subscriber, Unsubscriber} from "svelte/store"
import {identity, ensurePlural, getJson, setJson, batch, partition, first} from "@welshman/lib" import {identity, ensurePlural, getJson, setJson, batch, partition, first} from "@welshman/lib"
import type {Maybe} from "@welshman/lib" import type {Maybe} from "@welshman/lib"
import type {Repository} from "@welshman/util" import type {Repository} from "@welshman/util"
import {matchFilters, getIdAndAddress, getIdFilters} from "@welshman/util" import {matchFilters, getIdAndAddress, getIdFilters} from "@welshman/util"
import type {Filter, TrustedEvent} from "@welshman/util" import type {Filter, TrustedEvent} from "@welshman/util"
// Generic store utils // Sync with localstorage
export const synced = <T>(key: string, defaultValue: T) => { export const synced = <T>(key: string, defaultValue: T) => {
const init = getJson(key) const init = getJson(key)
@@ -18,6 +18,8 @@ export const synced = <T>(key: string, defaultValue: T) => {
return store return store
} }
// Getters
export const getter = <T>(store: Readable<T>) => { export const getter = <T>(store: Readable<T>) => {
let value: T let value: T
@@ -37,6 +39,20 @@ export function withGetter<T>(store: Readable<T> | Writable<T>) {
return {...store, get: getter<T>(store)} return {...store, get: getter<T>(store)}
} }
// Throttle
export const throttled = <T, S extends Readable<T>>(delay: number, store: S) => {
if (delay) {
const {subscribe} = store
store = {...store, subscribe: (f: Subscriber<T>) => subscribe(throttle(delay, f))}
}
return store
}
// Custom store
type Start<T> = (set: Subscriber<T>) => Unsubscriber type Start<T> = (set: Subscriber<T>) => Unsubscriber
export type CustomStoreOpts<T> = { export type CustomStoreOpts<T> = {
@@ -96,6 +112,8 @@ export const custom = <T>(start: Start<T>, opts: CustomStoreOpts<T> = {}): Writa
} }
} }
// Simple adapter
export const adapter = <Source, Target>({ export const adapter = <Source, Target>({
store, store,
forward, forward,
@@ -110,54 +128,14 @@ export const adapter = <Source, Target>({
update: (f: (x: Target) => Target) => store.update((x: Source) => backward(f(forward(x)))), update: (f: (x: Target) => Target) => store.update((x: Source) => backward(f(forward(x)))),
}) })
export const throttled = <T>(delay: number, store: Readable<T>) =>
custom<T>(set => store.subscribe(throttle(delay, set)))
// Event related stores // Event related stores
export const createEventStore = ( export type DeriveEventsMappedOptions<T> = {
repository: Repository, filters: Filter[]
migrate?: (events: TrustedEvent[]) => TrustedEvent[], eventToItem: (event: TrustedEvent) => Maybe<T | T[] | Promise<T | T[]>>
): Writable<TrustedEvent[]> => { itemToEvent: (item: T) => TrustedEvent
let subs: Subscriber<TrustedEvent[]>[] = [] throttle?: number
includeDeleted?: boolean
const onUpdate = () => {
const $events = repository.dump()
for (const sub of subs) {
sub($events)
}
}
const setEvents = (events: TrustedEvent[]) => {
if (migrate) {
events = migrate(events)
}
repository.load(events)
}
return {
set: (events: TrustedEvent[]) => setEvents(events),
update: (f: Updater<TrustedEvent[]>) => setEvents(f(repository.dump())),
subscribe: (f: Subscriber<TrustedEvent[]>) => {
f(repository.dump())
subs.push(f)
if (subs.length === 1) {
repository.on("update", onUpdate)
}
return () => {
subs = subs.filter(x => x !== f)
if (subs.length === 0) {
repository.off("update", onUpdate)
}
}
},
}
} }
export const deriveEventsMapped = <T>(repository: Repository, { export const deriveEventsMapped = <T>(repository: Repository, {
@@ -166,13 +144,7 @@ export const deriveEventsMapped = <T>(repository: Repository, {
itemToEvent, itemToEvent,
throttle = 0, throttle = 0,
includeDeleted = false, includeDeleted = false,
}: { }: DeriveEventsMappedOptions<T>) =>
filters: Filter[]
eventToItem: (event: TrustedEvent) => Maybe<T | T[] | Promise<T | T[]>>
itemToEvent: (item: T) => TrustedEvent
throttle?: number
includeDeleted?: boolean
}) =>
custom<T[]>(setter => { custom<T[]>(setter => {
let data: T[] = [] let data: T[] = []
const deferred = new Set() const deferred = new Set()
@@ -267,7 +239,9 @@ export const deriveEventsMapped = <T>(repository: Repository, {
return () => repository.off("update", onUpdate) return () => repository.off("update", onUpdate)
}, {throttle}) }, {throttle})
export const deriveEvents = (repository: Repository, opts: {filters: Filter[], includeDeleted?: boolean}) => export type DeriveEventsOptions<T> = Omit<DeriveEventsMappedOptions<T>, "itemToEvent" | "eventToItem">
export const deriveEvents = <T>(repository: Repository, opts: DeriveEventsOptions<T>) =>
deriveEventsMapped<TrustedEvent>(repository, { deriveEventsMapped<TrustedEvent>(repository, {
...opts, ...opts,
eventToItem: identity, eventToItem: identity,