Update getter to dynamically switch between modes
This commit is contained in:
@@ -1,35 +1,32 @@
|
|||||||
import {derived, readable} from "svelte/store"
|
import {derived, readable} from "svelte/store"
|
||||||
import {readProfile, displayProfile, displayPubkey, PROFILE} from "@welshman/util"
|
import {readProfile, displayProfile, displayPubkey, PROFILE} from "@welshman/util"
|
||||||
import {PublishedProfile} from "@welshman/util"
|
import {deriveItemsByKey, deriveItems, makeDeriveItem, getter} from "@welshman/store"
|
||||||
import {deriveEventsMapped, collection, withGetter} from "@welshman/store"
|
|
||||||
import {repository} from "./core.js"
|
import {repository} from "./core.js"
|
||||||
import {makeOutboxLoaderWithIndexers} from "./relaySelections.js"
|
import {makeOutboxLoaderWithIndexers} from "./relaySelections.js"
|
||||||
|
|
||||||
export const profiles = withGetter(
|
export const profilesByPubkey = deriveItemsByKey({
|
||||||
deriveEventsMapped<PublishedProfile>(repository, {
|
repository,
|
||||||
filters: [{kinds: [PROFILE]}],
|
eventToItem: readProfile,
|
||||||
eventToItem: readProfile,
|
filters: [{kinds: [PROFILE]}],
|
||||||
itemToEvent: item => item.event,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const {
|
|
||||||
indexStore: profilesByPubkey,
|
|
||||||
deriveItem: deriveProfile,
|
|
||||||
loadItem: loadProfile,
|
|
||||||
} = collection({
|
|
||||||
name: "profiles",
|
|
||||||
store: profiles,
|
|
||||||
getKey: profile => profile.event.pubkey,
|
getKey: profile => profile.event.pubkey,
|
||||||
load: makeOutboxLoaderWithIndexers(PROFILE),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const displayProfileByPubkey = (pubkey: string | undefined) =>
|
export const profiles = deriveItems(profilesByPubkey)
|
||||||
pubkey ? displayProfile(profilesByPubkey.get().get(pubkey), displayPubkey(pubkey)) : ""
|
|
||||||
|
|
||||||
export const deriveProfileDisplay = (pubkey: string | undefined, relays: string[] = []) =>
|
export const loadProfile = makeOutboxLoaderWithIndexers(PROFILE)
|
||||||
|
|
||||||
|
export const deriveProfile = makeDeriveItem(profilesByPubkey, loadProfile)
|
||||||
|
|
||||||
|
export const getProfilesByPubkey = getter(profilesByPubkey)
|
||||||
|
|
||||||
|
export const getProfile = (pubkey: string) => getProfilesByPubkey().get(pubkey)
|
||||||
|
|
||||||
|
export const displayProfileByPubkey = (pubkey: string | undefined) =>
|
||||||
|
pubkey ? displayProfile(getProfile(pubkey), displayPubkey(pubkey)) : ""
|
||||||
|
|
||||||
|
export const deriveProfileDisplay = (pubkey: string | undefined, ...args: any[]) =>
|
||||||
pubkey
|
pubkey
|
||||||
? derived(deriveProfile(pubkey, relays), $profile =>
|
? derived(deriveProfile(pubkey, ...args), $profile =>
|
||||||
displayProfile($profile, displayPubkey(pubkey)),
|
displayProfile($profile, displayPubkey(pubkey)),
|
||||||
)
|
)
|
||||||
: readable("")
|
: readable("")
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import {Readable, Writable} from "svelte/store"
|
|
||||||
|
|
||||||
export const getter = <T>(store: Readable<T>) => {
|
|
||||||
let value: T
|
|
||||||
|
|
||||||
store.subscribe((newValue: T) => {
|
|
||||||
value = newValue
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => value
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WritableWithGetter<T> = Writable<T> & {get: () => T}
|
|
||||||
export type ReadableWithGetter<T> = Readable<T> & {get: () => T}
|
|
||||||
|
|
||||||
export function withGetter<T>(store: Writable<T>): WritableWithGetter<T>
|
|
||||||
export function withGetter<T>(store: Readable<T>): ReadableWithGetter<T>
|
|
||||||
export function withGetter<T>(store: Readable<T> | Writable<T>) {
|
|
||||||
return {...store, get: getter<T>(store)}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
export * from "./synced.js"
|
export * from "./synced.js"
|
||||||
export * from "./getter.js"
|
export * from "./misc.js"
|
||||||
export * from "./loader.js"
|
export * from "./loader.js"
|
||||||
export * from "./throttle.js"
|
|
||||||
export * from "./memoize.js"
|
|
||||||
export * from "./repository.js"
|
export * from "./repository.js"
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import {readable, derived, writable, Readable, Subscriber} from "svelte/store"
|
import {Maybe, now} from "@welshman/lib"
|
||||||
import {Maybe, batch, indexBy, remove, assoc, now} from "@welshman/lib"
|
|
||||||
import {withGetter, ReadableWithGetter} from "./getter.js"
|
|
||||||
import {memoized} from "./memoize.js"
|
|
||||||
|
|
||||||
export type LoaderOptions<T> = {
|
export type LoaderOptions<T> = {
|
||||||
getItem: (key: string) => T
|
getItem: (key: string) => T
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import {derived, Readable, Subscriber, Stores, StoresValues} from "svelte/store"
|
|
||||||
import {memoize} from "@welshman/lib"
|
|
||||||
|
|
||||||
export const memoized = <T>(store: Readable<T>) => {
|
|
||||||
const {subscribe} = store
|
|
||||||
|
|
||||||
return {...store, subscribe: (f: Subscriber<T>) => subscribe(memoize(f))}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deriveDeduplicated = <S extends Stores, T>(
|
|
||||||
stores: S,
|
|
||||||
get: (storeValues: StoresValues<S>) => T,
|
|
||||||
): Readable<T> => {
|
|
||||||
let prev: T
|
|
||||||
|
|
||||||
return derived(stores, (storeValues, set) => {
|
|
||||||
const result = get(storeValues)
|
|
||||||
|
|
||||||
if (prev !== result) {
|
|
||||||
prev = result
|
|
||||||
set(result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import {
|
||||||
|
get,
|
||||||
|
derived,
|
||||||
|
Readable,
|
||||||
|
Unsubscriber,
|
||||||
|
Writable,
|
||||||
|
Subscriber,
|
||||||
|
Stores,
|
||||||
|
StoresValues,
|
||||||
|
} from "svelte/store"
|
||||||
|
import {memoize, throttle} from "@welshman/lib"
|
||||||
|
|
||||||
|
// Smart getter that adjusts between svelte's get and aggressive subscription depending on how hot
|
||||||
|
// the path is
|
||||||
|
|
||||||
|
export const getter = <T>(store: Readable<T>, {threshold = 10}: {threshold?: number} = {}) => {
|
||||||
|
const calls: number[] = []
|
||||||
|
let unsubscribe: Unsubscriber | undefined
|
||||||
|
let offset = 0
|
||||||
|
let value: T
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const now = Date.now()
|
||||||
|
const cutoff = now - 1000
|
||||||
|
|
||||||
|
// Find the first timestamp within the window (avoid expensive shift)
|
||||||
|
while (offset < calls.length && calls[offset] < cutoff) {
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periodically clean up old timestamps to prevent unbounded growth
|
||||||
|
if (offset > 100) {
|
||||||
|
calls.splice(0, offset)
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add current timestamp
|
||||||
|
calls.push(now)
|
||||||
|
|
||||||
|
// Check if call rate exceeds threshold and switch to more aggressive mode
|
||||||
|
if (calls.length - offset > threshold) {
|
||||||
|
if (!unsubscribe) {
|
||||||
|
unsubscribe = store.subscribe((newValue: T) => {
|
||||||
|
value = newValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
if (unsubscribe) {
|
||||||
|
unsubscribe()
|
||||||
|
unsubscribe = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return get(store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WritableWithGetter<T> = Writable<T> & {get: () => T}
|
||||||
|
export type ReadableWithGetter<T> = Readable<T> & {get: () => T}
|
||||||
|
|
||||||
|
export function withGetter<T>(store: Writable<T>): WritableWithGetter<T>
|
||||||
|
export function withGetter<T>(store: Readable<T>): ReadableWithGetter<T>
|
||||||
|
export function withGetter<T>(store: Readable<T> | Writable<T>) {
|
||||||
|
return {...store, get: getter<T>(store)}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const memoized = <T>(store: Readable<T>) => {
|
||||||
|
const {subscribe} = store
|
||||||
|
|
||||||
|
return {...store, subscribe: (f: Subscriber<T>) => subscribe(memoize(f))}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deriveDeduplicated = <S extends Stores, T>(
|
||||||
|
stores: S,
|
||||||
|
get: (storeValues: StoresValues<S>) => T,
|
||||||
|
): Readable<T> => {
|
||||||
|
let prev: T
|
||||||
|
|
||||||
|
return derived(stores, (storeValues, set) => {
|
||||||
|
const result = get(storeValues)
|
||||||
|
|
||||||
|
if (prev !== result) {
|
||||||
|
prev = result
|
||||||
|
set(result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,21 +1,8 @@
|
|||||||
import {derived, readable, Readable} from "svelte/store"
|
import {derived, readable, Readable} from "svelte/store"
|
||||||
import {
|
import {on, indexBy, mapPop, Maybe, call, sortBy, first} from "@welshman/lib"
|
||||||
on,
|
import {matchFilters, getIdFilters, Filter, TrustedEvent} from "@welshman/util"
|
||||||
indexBy,
|
|
||||||
mapPop,
|
|
||||||
Maybe,
|
|
||||||
call,
|
|
||||||
sortBy,
|
|
||||||
identity,
|
|
||||||
ensurePlural,
|
|
||||||
removeUndefined,
|
|
||||||
batch,
|
|
||||||
partition,
|
|
||||||
first,
|
|
||||||
} from "@welshman/lib"
|
|
||||||
import {matchFilters, getIdAndAddress, getIdFilters, Filter, TrustedEvent} from "@welshman/util"
|
|
||||||
import {Repository, RepositoryUpdate, Tracker} from "@welshman/net"
|
import {Repository, RepositoryUpdate, Tracker} from "@welshman/net"
|
||||||
import {deriveDeduplicated} from './memoize.js'
|
import {deriveDeduplicated} from "./misc.js"
|
||||||
|
|
||||||
// Events by id
|
// Events by id
|
||||||
|
|
||||||
@@ -179,9 +166,10 @@ export const deriveEventsByIdByUrl = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveEventsByIdForUrl = (url: string, eventsByIdByUrlStore: Readable<EventsByIdByUrl>) =>
|
export const deriveEventsByIdForUrl = (
|
||||||
deriveDeduplicated(eventsByIdByUrlStore, eventsByIdByUrl => eventsByIdByUrl.get(url))
|
url: string,
|
||||||
|
eventsByIdByUrlStore: Readable<EventsByIdByUrl>,
|
||||||
|
) => deriveDeduplicated(eventsByIdByUrlStore, eventsByIdByUrl => eventsByIdByUrl.get(url))
|
||||||
|
|
||||||
// Items by key
|
// Items by key
|
||||||
|
|
||||||
@@ -274,6 +262,17 @@ export const deriveItems = <T>(itemsByKeyStore: Readable<ItemsByKey<T>>) =>
|
|||||||
export const deriveItemsSorted = <T>(sortFn: (item: T) => number, itemsStore: Readable<T[]>) =>
|
export const deriveItemsSorted = <T>(sortFn: (item: T) => number, itemsStore: Readable<T[]>) =>
|
||||||
deriveDeduplicated(itemsStore, items => sortBy(sortFn, items))
|
deriveDeduplicated(itemsStore, items => sortBy(sortFn, items))
|
||||||
|
|
||||||
|
export const makeDeriveItem = <T>(
|
||||||
|
itemsByKeyStore: Readable<ItemsByKey<T>>,
|
||||||
|
onDerive?: (key: string, ...args: any[]) => void,
|
||||||
|
) => {
|
||||||
|
return (key: string, ...args: any[]) => {
|
||||||
|
onDerive?.(key, ...args)
|
||||||
|
|
||||||
|
return deriveDeduplicated(itemsByKeyStore, itemsByKey => itemsByKey.get(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Miscellaneous other stuff
|
// Miscellaneous other stuff
|
||||||
|
|
||||||
export const deriveEvent = (repository: Repository, idOrAddress: string) =>
|
export const deriveEvent = (repository: Repository, idOrAddress: string) =>
|
||||||
@@ -288,7 +287,7 @@ export const deriveEvent = (repository: Repository, idOrAddress: string) =>
|
|||||||
|
|
||||||
export const deriveIsDeleted = (repository: Repository, event: TrustedEvent) =>
|
export const deriveIsDeleted = (repository: Repository, event: TrustedEvent) =>
|
||||||
readable(repository.isDeleted(event), set => {
|
readable(repository.isDeleted(event), set => {
|
||||||
const unsubscribe = on(repository, 'update', ({removed}: RepositoryUpdate) => {
|
const unsubscribe = on(repository, "update", ({removed}: RepositoryUpdate) => {
|
||||||
if (removed.has(event.id)) {
|
if (removed.has(event.id)) {
|
||||||
set(true)
|
set(true)
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
|
|||||||
Reference in New Issue
Block a user