Modify loader, apply changes to app stores

This commit is contained in:
Jon Staab
2025-11-19 15:41:56 -08:00
parent cd553d6f6a
commit d197acc41e
10 changed files with 177 additions and 160 deletions
+1 -2
View File
@@ -1,4 +1,3 @@
export * from "./synced.js"
export * from "./misc.js"
export * from "./loader.js"
export * from "./synced.js"
export * from "./repository.js"
-68
View File
@@ -1,68 +0,0 @@
import {Maybe, now} from "@welshman/lib"
export type LoaderOptions<T> = {
getItem: (key: string) => T
getLastFetched: (key: string) => number
setLastFetched: (key: string, ts: number) => void
load: (key: string, ...args: any[]) => Promise<unknown>
timeout?: number
}
export const makeLoader = <T>(options: LoaderOptions<T>) => {
const timeout = options.timeout || 3600
const pending = new Map<string, Promise<Maybe<T>>>()
const attempts = new Map<string, number>()
const baseLoad = async (key: string, force: boolean, ...args: any[]): Promise<Maybe<T>> => {
const stale = options.getItem(key)
const lastFetched = options.getLastFetched(key)
// If we have an item, reload if it's stale
if (stale && lastFetched > now() - timeout && !force) {
return stale
}
const pendingItem = pending.get(key)
// If we already are loading, await and return
if (pendingItem) {
return pendingItem
}
const attempt = attempts.get(key) || 0
// Use exponential backoff to throttle attempts
if (lastFetched > now() - Math.pow(2, attempt) && !force) {
return stale
}
attempts.set(key, attempt + 1)
options.setLastFetched(key, now())
const promise = options.load(key, ...args).then(() => options.getItem(key))
pending.set(key, promise)
let item
try {
item = await promise
} catch (e) {
console.warn(`Failed to load ${name} item ${key}`, e)
} finally {
pending.delete(key)
}
if (item) {
attempts.delete(key)
}
return item
}
const load = (key: string, ...args: any[]) => baseLoad(key, false, ...args)
const forceLoad = (key: string, ...args: any[]) => baseLoad(key, true, ...args)
return {load, forceLoad}
}
+73 -1
View File
@@ -1,5 +1,5 @@
import {derived, readable, Readable} from "svelte/store"
import {on, indexBy, mapPop, Maybe, call, sortBy, first} from "@welshman/lib"
import {on, now, indexBy, mapPop, Maybe, call, sortBy, first} from "@welshman/lib"
import {matchFilters, getIdFilters, Filter, TrustedEvent} from "@welshman/util"
import {Repository, RepositoryUpdate, Tracker} from "@welshman/net"
import {deriveDeduplicated} from "./misc.js"
@@ -177,6 +177,8 @@ export type ItemsByKey<T> = Map<string, T>
export type EventToItem<T> = (event: TrustedEvent) => T
export type GetItem<T> = (key: string, ...args: any[]) => Maybe<T>
export type DeriveItemsByKeyOptions<T> = {
getKey: (item: T) => string
filters: Filter[]
@@ -273,6 +275,76 @@ export const makeDeriveItem = <T>(
}
}
// Item loaders
export type LoadItem = (key: string, ...args: any[]) => Promise<unknown>
export const makeForceLoadItem = <T>(loadItem: LoadItem, getItem: GetItem<T>) => {
return (key: string, ...args: any[]) => loadItem(key, ...args).then(() => getItem(key))
}
export type MakeLoadItemOptions = {
getFetched?: (key: string) => number
setFetched?: (key: string, ts: number) => void
timeout?: number
}
export const makeLoadItem = <T>(loadItem: LoadItem, getItem: GetItem<T>, options: MakeLoadItemOptions = {}) => {
const timeout = options.timeout || 3600
const fetched = new Map<string, number>()
const getFetched = options.getFetched || ((key: string) => fetched.get(key) || 0)
const setFetched = options.setFetched || ((key: string, ts: number) => fetched.set(key, ts))
const pending = new Map<string, Promise<Maybe<T>>>()
const attempts = new Map<string, number>()
return async (key: string, ...args: any[]): Promise<Maybe<T>> => {
const stale = getItem(key)
const fetched = getFetched(key)
// If we have an item, reload if it's stale
if (stale && fetched > now() - timeout) {
return stale
}
const pendingItem = pending.get(key)
// If we already are loading, await and return
if (pendingItem) {
return pendingItem
}
const attempt = attempts.get(key) || 0
// Use exponential backoff to throttle attempts
if (fetched > now() - Math.pow(2, attempt)) {
return stale
}
attempts.set(key, attempt + 1)
setFetched(key, now())
const promise = loadItem(key, ...args).then(() => getItem(key))
pending.set(key, promise)
let item
try {
item = await promise
} catch (e) {
console.warn(`Failed to load ${name} item ${key}`, e)
} finally {
pending.delete(key)
}
if (item) {
attempts.delete(key)
}
return item
}
}
// Miscellaneous other stuff
export const deriveEvent = (repository: Repository, idOrAddress: string) =>