From 824ba552de32727a60653399a30bdec65616e21a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 13:32:45 +0000 Subject: [PATCH] fix: add applesauce caching to extractMetadataFromEvent Use getOrComputeCachedValue to cache extracted group metadata on the event object. This prevents recomputing tag lookups on every access. Also updated resolveGroupMetadata to use the cached extraction. Regarding eventStore.replaceable() vs timeline(): - replaceable(kind, pubkey, identifier) requires knowing the author - For kind 39000, the relay signs the event and we don't know its pubkey upfront, so timeline query with #d filter is correct https://claude.ai/code/session_01CCxAcUsRBkWSL6as1wtFoA --- src/lib/chat/group-metadata-helpers.ts | 45 +++++++++++++------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/lib/chat/group-metadata-helpers.ts b/src/lib/chat/group-metadata-helpers.ts index b13b5ec..c5fe8b2 100644 --- a/src/lib/chat/group-metadata-helpers.ts +++ b/src/lib/chat/group-metadata-helpers.ts @@ -1,9 +1,15 @@ import { firstValueFrom } from "rxjs"; import { kinds } from "nostr-tools"; import { profileLoader } from "@/services/loaders"; -import { getProfileContent } from "applesauce-core/helpers"; +import { + getProfileContent, + getOrComputeCachedValue, +} from "applesauce-core/helpers"; import type { NostrEvent } from "@/types/nostr"; +// Symbol for caching extracted group metadata on event objects +const GroupMetadataSymbol = Symbol("groupMetadata"); + /** * Check if a string is a valid nostr pubkey (64 character hex string) */ @@ -30,22 +36,25 @@ export function getGroupCacheKey(relayUrl: string, groupId: string): string { /** * Extract metadata synchronously from a kind 39000 event - * This is the fast path - no async needed when we have the metadata event + * Uses applesauce caching to avoid recomputing on every access */ export function extractMetadataFromEvent( groupId: string, metadataEvent: NostrEvent, ): ResolvedGroupMetadata { - const name = metadataEvent.tags.find((t) => t[0] === "name")?.[1] || groupId; - const description = metadataEvent.tags.find((t) => t[0] === "about")?.[1]; - const icon = metadataEvent.tags.find((t) => t[0] === "picture")?.[1]; + return getOrComputeCachedValue(metadataEvent, GroupMetadataSymbol, () => { + const name = + metadataEvent.tags.find((t) => t[0] === "name")?.[1] || groupId; + const description = metadataEvent.tags.find((t) => t[0] === "about")?.[1]; + const icon = metadataEvent.tags.find((t) => t[0] === "picture")?.[1]; - return { - name, - description, - icon, - source: "nip29", - }; + return { + name, + description, + icon, + source: "nip29" as const, + }; + }); } /** @@ -66,19 +75,9 @@ export async function resolveGroupMetadata( relayUrl: string, metadataEvent?: NostrEvent, ): Promise { - // If NIP-29 metadata exists, use it (priority 1) + // If NIP-29 metadata exists, use cached extraction (priority 1) if (metadataEvent && metadataEvent.kind === 39000) { - const name = - metadataEvent.tags.find((t) => t[0] === "name")?.[1] || groupId; - const description = metadataEvent.tags.find((t) => t[0] === "about")?.[1]; - const icon = metadataEvent.tags.find((t) => t[0] === "picture")?.[1]; - - return { - name, - description, - icon, - source: "nip29", - }; + return extractMetadataFromEvent(groupId, metadataEvent); } // If no NIP-29 metadata and groupId is a valid pubkey, try profile fallback (priority 2)