perf(local-first): apply local-first then remote pattern across services (titles, bookmarks, highlights)

This commit is contained in:
Gigi
2025-10-12 22:42:24 +02:00
parent a1305fba81
commit e3debfa5df
3 changed files with 118 additions and 39 deletions

View File

@@ -1,9 +1,10 @@
import { RelayPool, completeOnEose } from 'applesauce-relay'
import { lastValueFrom, takeUntil, timer, toArray } from 'rxjs'
import { RelayPool, completeOnEose, onlyEvents } from 'applesauce-relay'
import { lastValueFrom, take, takeUntil, timer, toArray } from 'rxjs'
import { nip19 } from 'nostr-tools'
import { AddressPointer } from 'nostr-tools/nip19'
import { Helpers } from 'applesauce-core'
import { RELAYS } from '../config/relays'
import { prioritizeLocalRelays, partitionRelays } from '../utils/helpers'
const { getArticleTitle } = Helpers
@@ -25,9 +26,11 @@ export async function fetchArticleTitle(
const pointer = decoded.data as AddressPointer
// Define relays to query
const relays = pointer.relays && pointer.relays.length > 0
const baseRelays = pointer.relays && pointer.relays.length > 0
? pointer.relays
: RELAYS
const orderedRelays = prioritizeLocalRelays(baseRelays)
const { local: localRelays } = partitionRelays(orderedRelays)
// Fetch the article event
const filter = {
@@ -36,11 +39,27 @@ export async function fetchArticleTitle(
'#d': [pointer.identifier]
}
const events = await lastValueFrom(
relayPool
.req(relays, filter)
.pipe(completeOnEose(), takeUntil(timer(5000)), toArray())
)
// Try to get the first event quickly from local relays
let events = [] as any[]
if (localRelays.length > 0) {
try {
events = await lastValueFrom(
relayPool
.req(localRelays, filter)
.pipe(onlyEvents(), take(1), takeUntil(timer(1200)), toArray())
)
} catch {
events = []
}
}
// Fallback to all relays if nothing from local quickly
if (events.length === 0) {
events = await lastValueFrom(
relayPool
.req(orderedRelays, filter)
.pipe(onlyEvents(), take(1), takeUntil(timer(5000)), toArray())
)
}
if (events.length === 0) {
return null

View File

@@ -16,7 +16,7 @@ import { Bookmark } from '../types/bookmarks'
import { collectBookmarksFromEvents } from './bookmarkProcessing.ts'
import { UserSettings } from './settingsService'
import { rebroadcastEvents } from './rebroadcastService'
import { prioritizeLocalRelays } from '../utils/helpers'
import { prioritizeLocalRelays, partitionRelays } from '../utils/helpers'
@@ -33,11 +33,11 @@ export const fetchBookmarks = async (
}
// Get relay URLs from the pool
const relayUrls = prioritizeLocalRelays(Array.from(relayPool.relays.values()).map(relay => relay.url))
const { local: localRelays, remote: remoteRelays } = partitionRelays(relayUrls)
// Fetch bookmark events - NIP-51 standards, legacy formats, and web bookmarks (NIP-B0)
console.log('🔍 Fetching bookmark events from relays:', relayUrls)
// Try local-first quickly, then full set fallback
let rawEvents = [] as NostrEvent[]
const localRelays = relayUrls.filter(url => url.includes('localhost') || url.includes('127.0.0.1'))
if (localRelays.length > 0) {
try {
rawEvents = await lastValueFrom(
@@ -49,12 +49,17 @@ export const fetchBookmarks = async (
rawEvents = []
}
}
if (rawEvents.length === 0) {
rawEvents = await lastValueFrom(
relayPool
.req(relayUrls, { kinds: [10003, 30003, 30001, 39701], authors: [activeAccount.pubkey] })
.pipe(completeOnEose(), takeUntil(timer(6000)), toArray())
)
if (remoteRelays.length > 0) {
try {
const remoteEvents = await lastValueFrom(
relayPool
.req(remoteRelays, { kinds: [10003, 30003, 30001, 39701], authors: [activeAccount.pubkey] })
.pipe(completeOnEose(), takeUntil(timer(6000)), toArray())
)
rawEvents = rawEvents.concat(remoteEvents)
} catch {
// ignore
}
}
console.log('📊 Raw events fetched:', rawEvents.length, 'events')
@@ -119,11 +124,31 @@ export const fetchBookmarks = async (
let idToEvent: Map<string, NostrEvent> = new Map()
if (noteIds.length > 0) {
try {
const events = await lastValueFrom(
relayPool
.req(relayUrls, { ids: noteIds })
.pipe(completeOnEose(), takeUntil(timer(4000)), toArray())
)
const { local: localHydrate, remote: remoteHydrate } = partitionRelays(relayUrls)
let events: NostrEvent[] = []
if (localHydrate.length > 0) {
try {
events = await lastValueFrom(
relayPool
.req(localHydrate, { ids: noteIds })
.pipe(completeOnEose(), takeUntil(timer(800)), toArray())
)
} catch {
events = []
}
}
if (remoteHydrate.length > 0) {
try {
const remote = await lastValueFrom(
relayPool
.req(remoteHydrate, { ids: noteIds })
.pipe(completeOnEose(), takeUntil(timer(2500)), toArray())
)
events = events.concat(remote)
} catch {
// ignore
}
}
idToEvent = new Map(events.map((e: NostrEvent) => [e.id, e]))
} catch (error) {
console.warn('Failed to fetch events for hydration:', error)

View File

@@ -251,29 +251,64 @@ export const fetchHighlights = async (
): Promise<Highlight[]> => {
try {
const relayUrls = Array.from(relayPool.relays.values()).map(relay => relay.url)
const ordered = prioritizeLocalRelays(relayUrls)
const { local: localRelays, remote: remoteRelays } = partitionRelays(ordered)
console.log('🔍 Fetching highlights (kind 9802) by author:', pubkey)
const seenIds = new Set<string>()
const rawEvents = await lastValueFrom(
relayPool
.req(relayUrls, { kinds: [9802], authors: [pubkey] })
.pipe(
onlyEvents(),
tap((event: NostrEvent) => {
if (!seenIds.has(event.id)) {
seenIds.add(event.id)
const highlight = eventToHighlight(event)
if (onHighlight) {
onHighlight(highlight)
}
}
}),
completeOnEose(),
takeUntil(timer(10000)),
toArray()
let rawEvents: NostrEvent[] = []
if (localRelays.length > 0) {
try {
rawEvents = await lastValueFrom(
relayPool
.req(localRelays, { kinds: [9802], authors: [pubkey] })
.pipe(
onlyEvents(),
tap((event: NostrEvent) => {
if (!seenIds.has(event.id)) {
seenIds.add(event.id)
const highlight = eventToHighlight(event)
if (onHighlight) {
onHighlight(highlight)
}
}
}),
completeOnEose(),
takeUntil(timer(1200)),
toArray()
)
)
)
} catch {
rawEvents = []
}
}
if (remoteRelays.length > 0) {
try {
const remoteEvents = await lastValueFrom(
relayPool
.req(remoteRelays, { kinds: [9802], authors: [pubkey] })
.pipe(
onlyEvents(),
tap((event: NostrEvent) => {
if (!seenIds.has(event.id)) {
seenIds.add(event.id)
const highlight = eventToHighlight(event)
if (onHighlight) {
onHighlight(highlight)
}
}
}),
completeOnEose(),
takeUntil(timer(6000)),
toArray()
)
)
rawEvents = rawEvents.concat(remoteEvents)
} catch {
// ignore
}
}
console.log('📊 Raw highlight events fetched:', rawEvents.length)