Optimize date formatting and repository/tracker stores

This commit is contained in:
Jon Staab
2024-12-09 13:37:07 -08:00
parent 7982bebb35
commit fba0482ec3
10 changed files with 115 additions and 99 deletions
+27 -67
View File
@@ -1,85 +1,45 @@
import {ctx, isNil} from "@welshman/lib" import {throttle} from 'throttle-debounce'
import {Repository, Relay, LOCAL_RELAY_URL, getFilterResultCardinality} from "@welshman/util" import {Repository, Relay} from "@welshman/util"
import type {TrustedEvent, Filter} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {Tracker, subscribe as baseSubscribe, SubscriptionEvent} from "@welshman/net" import {Tracker} from "@welshman/net"
import type {SubscribeRequestWithHandlers} from "@welshman/net"
import {custom} from "@welshman/store" import {custom} from "@welshman/store"
export const repository = new Repository<TrustedEvent>() export const repository = new Repository<TrustedEvent>()
export const repositoryStore = custom(setter => {
const onUpdate = () => setter(repository)
onUpdate()
repository.on('update', onUpdate)
return () => repository.off('update', onUpdate)
}, {
set: (other: Repository) => repository.load(other.dump()),
})
export const relay = new Relay(repository) export const relay = new Relay(repository)
export const tracker = new Tracker() export const tracker = new Tracker()
export const trackerStore = custom(setter => { // Adapt above objects to stores
const onUpdate = () => setter(tracker)
onUpdate() export const makeRepositoryStore = ({throttle: t = 300}: {throttle?: number} = {}) =>
tracker.on('update', onUpdate) custom(setter => {
let onUpdate = () => setter(repository)
return () => tracker.off('update', onUpdate) if (t) {
}, { onUpdate = throttle(t, onUpdate)
set: (other: Tracker) => tracker.load(other.relaysById),
})
export type PartialSubscribeRequest = Partial<SubscribeRequestWithHandlers> & {filters: Filter[]}
export const subscribe = (request: PartialSubscribeRequest) => {
const events: TrustedEvent[] = []
// If we already have all results for any filter, don't send the filter to the network
if (request.closeOnEose) {
for (const filter of request.filters.splice(0)) {
const cardinality = getFilterResultCardinality(filter)
if (!isNil(cardinality)) {
const results = repository.query([filter])
if (results.length === cardinality) {
for (const event of results) {
events.push(event)
}
break
}
}
request.filters.push(filter)
} }
}
// Make sure to query our local relay too onUpdate()
const delay = ctx.app.requestDelay repository.on('update', onUpdate)
const authTimeout = ctx.app.authTimeout
const timeout = request.closeOnEose ? ctx.app.requestTimeout : 0
const sub = baseSubscribe({delay, timeout, authTimeout, relays: [], ...request})
// Keep cached results async so the caller can set up handlers return () => repository.off('update', onUpdate)
setTimeout(() => { }, {
for (const event of events) { set: (other: Repository) => repository.load(other.dump()),
sub.emitter.emit(SubscriptionEvent.Event, LOCAL_RELAY_URL, event)
}
}) })
return sub export const makeTrackerStore = ({throttle: t = 300}: {throttle?: number} = {}) =>
} custom(setter => {
let onUpdate = () => setter(tracker)
export const load = (request: PartialSubscribeRequest) => if (t) {
new Promise<TrustedEvent[]>(resolve => { onUpdate = throttle(t, onUpdate)
const sub = subscribe({closeOnEose: true, timeout: ctx.app.requestTimeout, ...request}) }
const events: TrustedEvent[] = []
sub.emitter.on(SubscriptionEvent.Event, (url: string, e: TrustedEvent) => events.push(e)) onUpdate()
sub.emitter.on(SubscriptionEvent.Complete, () => resolve(events)) tracker.on('update', onUpdate)
return () => tracker.off('update', onUpdate)
}, {
set: (other: Tracker) => tracker.load(other.relaysById),
}) })
+1 -1
View File
@@ -8,7 +8,7 @@ import {pubkey, signer} from './session'
import {getFilterSelections} from './router' import {getFilterSelections} from './router'
import {loadRelaySelections} from './relaySelections' import {loadRelaySelections} from './relaySelections'
import {wotGraph, maxWot, getFollows, getNetwork, getFollowers} from './wot' import {wotGraph, maxWot, getFollows, getNetwork, getFollowers} from './wot'
import {load} from './core' import {load} from './subscribe'
export const request = async ({filters = [{}], relays = [], onEvent}: RequestOpts) => { export const request = async ({filters = [{}], relays = [], onEvent}: RequestOpts) => {
if (relays.length > 0) { if (relays.length > 0) {
+2 -1
View File
@@ -2,7 +2,8 @@ import {FOLLOWS, asDecryptedEvent, readList} from '@welshman/util'
import {type TrustedEvent, type PublishedList} from '@welshman/util' import {type TrustedEvent, type PublishedList} from '@welshman/util'
import {type SubscribeRequestWithHandlers} from "@welshman/net" import {type SubscribeRequestWithHandlers} from "@welshman/net"
import {deriveEventsMapped} from '@welshman/store' import {deriveEventsMapped} from '@welshman/store'
import {repository, load} from './core' import {repository} from './core'
import {load} from './subscribe'
import {collection} from './collection' import {collection} from './collection'
import {loadRelaySelections} from './relaySelections' import {loadRelaySelections} from './relaySelections'
+1
View File
@@ -15,6 +15,7 @@ export * from './router'
export * from './search' export * from './search'
export * from './session' export * from './session'
export * from './storage' export * from './storage'
export * from './subscribe'
export * from './sync' export * from './sync'
export * from './tags' export * from './tags'
export * from './thunk' export * from './thunk'
+2 -1
View File
@@ -2,7 +2,8 @@ import {MUTES, asDecryptedEvent, readList} from '@welshman/util'
import {type TrustedEvent, type PublishedList} from '@welshman/util' import {type TrustedEvent, type PublishedList} from '@welshman/util'
import {type SubscribeRequestWithHandlers} from "@welshman/net" import {type SubscribeRequestWithHandlers} from "@welshman/net"
import {deriveEventsMapped} from '@welshman/store' import {deriveEventsMapped} from '@welshman/store'
import {repository, load} from './core' import {repository} from './core'
import {load} from './subscribe'
import {collection} from './collection' import {collection} from './collection'
import {ensurePlaintext} from './plaintext' import {ensurePlaintext} from './plaintext'
import {loadRelaySelections} from './relaySelections' import {loadRelaySelections} from './relaySelections'
+2 -1
View File
@@ -3,7 +3,8 @@ import {readProfile, displayProfile, displayPubkey, PROFILE} from '@welshman/uti
import type {SubscribeRequestWithHandlers} from "@welshman/net" import type {SubscribeRequestWithHandlers} from "@welshman/net"
import type {PublishedProfile} from "@welshman/util" import type {PublishedProfile} from "@welshman/util"
import {deriveEventsMapped, withGetter} from '@welshman/store' import {deriveEventsMapped, withGetter} from '@welshman/store'
import {repository, load} from './core' import {repository} from './core'
import {load} from './subscribe'
import {collection} from './collection' import {collection} from './collection'
import {loadRelaySelections} from './relaySelections' import {loadRelaySelections} from './relaySelections'
+2 -1
View File
@@ -3,7 +3,8 @@ import {INBOX_RELAYS, RELAYS, normalizeRelayUrl, asDecryptedEvent, readList, get
import type {TrustedEvent, PublishedList, List} from '@welshman/util' import type {TrustedEvent, PublishedList, List} from '@welshman/util'
import type {SubscribeRequestWithHandlers} from "@welshman/net" import type {SubscribeRequestWithHandlers} from "@welshman/net"
import {deriveEventsMapped} from '@welshman/store' import {deriveEventsMapped} from '@welshman/store'
import {load, repository} from './core' import {repository} from './core'
import {load} from './subscribe'
import {collection} from './collection' import {collection} from './collection'
export const getRelayUrls = (list?: List): string[] => export const getRelayUrls = (list?: List): string[] =>
+1 -1
View File
@@ -6,7 +6,7 @@ import {dec, sortBy} from '@welshman/lib'
import {PROFILE} from '@welshman/util' import {PROFILE} from '@welshman/util'
import {throttled} from '@welshman/store' import {throttled} from '@welshman/store'
import type {PublishedProfile} from "@welshman/util" import type {PublishedProfile} from "@welshman/util"
import {load} from './core' import {load} from './subscribe'
import {wotGraph} from './wot' import {wotGraph} from './wot'
import {profiles} from './profiles' import {profiles} from './profiles'
import {topics} from './topics' import {topics} from './topics'
+57
View File
@@ -0,0 +1,57 @@
import {ctx, isNil} from "@welshman/lib"
import {LOCAL_RELAY_URL, getFilterResultCardinality} from "@welshman/util"
import type {TrustedEvent, Filter} from "@welshman/util"
import {subscribe as baseSubscribe, SubscriptionEvent} from "@welshman/net"
import type {SubscribeRequestWithHandlers} from "@welshman/net"
import {repository} from './core'
export type PartialSubscribeRequest = Partial<SubscribeRequestWithHandlers> & {filters: Filter[]}
export const subscribe = (request: PartialSubscribeRequest) => {
const events: TrustedEvent[] = []
// If we already have all results for any filter, don't send the filter to the network
if (request.closeOnEose) {
for (const filter of request.filters.splice(0)) {
const cardinality = getFilterResultCardinality(filter)
if (!isNil(cardinality)) {
const results = repository.query([filter])
if (results.length === cardinality) {
for (const event of results) {
events.push(event)
}
break
}
}
request.filters.push(filter)
}
}
// Make sure to query our local relay too
const delay = ctx.app.requestDelay
const authTimeout = ctx.app.authTimeout
const timeout = request.closeOnEose ? ctx.app.requestTimeout : 0
const sub = baseSubscribe({delay, timeout, authTimeout, relays: [], ...request})
// Keep cached results async so the caller can set up handlers
setTimeout(() => {
for (const event of events) {
sub.emitter.emit(SubscriptionEvent.Event, LOCAL_RELAY_URL, event)
}
})
return sub
}
export const load = (request: PartialSubscribeRequest) =>
new Promise<TrustedEvent[]>(resolve => {
const sub = subscribe({closeOnEose: true, timeout: ctx.app.requestTimeout, ...request})
const events: TrustedEvent[] = []
sub.emitter.on(SubscriptionEvent.Event, (url: string, e: TrustedEvent) => events.push(e))
sub.emitter.on(SubscriptionEvent.Complete, () => resolve(events))
})
+20 -26
View File
@@ -1,41 +1,35 @@
import {now, int, DAY, HOUR, MINUTE} from "@welshman/lib" import {now, int, DAY, HOUR, MINUTE} from "@welshman/lib"
export const LOCALE = new Intl.DateTimeFormat().resolvedOptions().locale
export const TIMEZONE = new Date().toString().match(/GMT[^\s]+/)
export const secondsToDate = (ts: number) => new Date(ts * 1000) export const secondsToDate = (ts: number) => new Date(ts * 1000)
export const dateToSeconds = (date: Date) => Math.round(date.valueOf() / 1000) export const dateToSeconds = (date: Date) => Math.round(date.valueOf() / 1000)
export const getTimeZone = () => new Date().toString().match(/GMT[^\s]+/) export const createLocalDate = (dateString: any) => new Date(`${dateString} ${TIMEZONE}`)
export const createLocalDate = (dateString: any) => new Date(`${dateString} ${getTimeZone()}`) export const timestampFormatter = new Intl.DateTimeFormat(LOCALE, {
dateStyle: "short",
timeStyle: "short",
})
export const getLocale = () => new Intl.DateTimeFormat().resolvedOptions().locale export const formatTimestamp = (ts: number) => timestampFormatter.format(secondsToDate(ts))
export const formatTimestamp = (ts: number) => { export const dateFormatter = new Intl.DateTimeFormat(LOCALE, {
const formatter = new Intl.DateTimeFormat(getLocale(), { year: "numeric",
dateStyle: "short", month: "long",
timeStyle: "short", day: "numeric",
}) })
return formatter.format(secondsToDate(ts)) export const formatTimestampAsDate = (ts: number) => dateFormatter.format(secondsToDate(ts))
}
export const formatTimestampAsDate = (ts: number) => { export const timeFormatter = new Intl.DateTimeFormat(LOCALE, {
const formatter = new Intl.DateTimeFormat(getLocale(), { timeStyle: "short",
year: "numeric", })
month: "long",
day: "numeric",
})
return formatter.format(secondsToDate(ts)) export const formatTimestampAsTime = (ts: number) => timeFormatter.format(secondsToDate(ts))
}
export const formatTimestampAsTime = (ts: number) => {
const formatter = new Intl.DateTimeFormat(getLocale(), {
timeStyle: "short",
})
return formatter.format(secondsToDate(ts))
}
export const formatTimestampRelative = (ts: number) => { export const formatTimestampRelative = (ts: number) => {
let unit let unit