From e3debfa5df41cd404d481aba3c3c1009f38a7b71 Mon Sep 17 00:00:00 2001 From: Gigi Date: Sun, 12 Oct 2025 22:42:24 +0200 Subject: [PATCH] perf(local-first): apply local-first then remote pattern across services (titles, bookmarks, highlights) --- src/services/articleTitleResolver.ts | 35 ++++++++++---- src/services/bookmarkService.ts | 51 +++++++++++++++----- src/services/highlightService.ts | 71 +++++++++++++++++++++------- 3 files changed, 118 insertions(+), 39 deletions(-) diff --git a/src/services/articleTitleResolver.ts b/src/services/articleTitleResolver.ts index f990bb35..acc7ba76 100644 --- a/src/services/articleTitleResolver.ts +++ b/src/services/articleTitleResolver.ts @@ -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 diff --git a/src/services/bookmarkService.ts b/src/services/bookmarkService.ts index 84198162..dcd0e4f8 100644 --- a/src/services/bookmarkService.ts +++ b/src/services/bookmarkService.ts @@ -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 = 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) diff --git a/src/services/highlightService.ts b/src/services/highlightService.ts index dccb617b..cdf8216e 100644 --- a/src/services/highlightService.ts +++ b/src/services/highlightService.ts @@ -251,29 +251,64 @@ export const fetchHighlights = async ( ): Promise => { 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() - 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)