Get rid of createEventStore, throttle storage adapters
This commit is contained in:
@@ -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
@@ -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
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user