diff --git a/packages/app/src/Cache/ProfileWorkeCache.ts b/packages/app/src/Cache/ProfileWorkeCache.ts new file mode 100644 index 00000000..f3ae4ad8 --- /dev/null +++ b/packages/app/src/Cache/ProfileWorkeCache.ts @@ -0,0 +1,104 @@ +import { CachedTable, CacheEvents, removeUndefined } from "@snort/shared"; +import { CachedMetadata, mapEventToProfile, NostrEvent } from "@snort/system"; +import { WorkerRelayInterface } from "@snort/worker-relay"; +import EventEmitter from "eventemitter3"; + +export class ProfileCacheRelayWorker extends EventEmitter implements CachedTable { + #relay: WorkerRelayInterface; + #keys = new Set(); + #cache = new Map(); + + constructor(relay: WorkerRelayInterface) { + super(); + this.#relay = relay; + } + + async preload() { + const ids = await this.#relay.sql("select distinct(pubkey) from events where kind = ?", [0]); + this.#keys = new Set(ids.map(a => a[0] as string)); + } + + keysOnTable(): string[] { + return [...this.#keys]; + } + + getFromCache(key?: string | undefined) { + if (key) { + return this.#cache.get(key); + } + } + + discover(ev: NostrEvent) { + if (ev.kind === 0) { + this.#keys.add(ev.pubkey); + } + } + + async get(key?: string | undefined) { + if (key) { + const cached = this.getFromCache(key); + if (cached) { + return cached; + } + const res = await this.bulkGet([key]); + if (res.length > 0) { + return res[0]; + } + } + } + + async bulkGet(keys: string[]) { + if (keys.length === 0) return []; + + const results = await this.#relay.req({ + id: "ProfileCacheRelayWorker.bulkGet", + filters: [ + { + authors: keys, + kinds: [0], + }, + ], + }); + const mapped = removeUndefined(results.result.map(a => mapEventToProfile(a))); + for (const pf of mapped) { + this.#cache.set(this.key(pf), pf); + } + this.emit( + "change", + mapped.map(a => this.key(a)), + ); + console.debug("ProfileCacheRelayWorker", keys, results); + return mapped; + } + + async set(obj: CachedMetadata) { + this.#keys.add(this.key(obj)); + } + + async bulkSet(obj: CachedMetadata[] | readonly CachedMetadata[]) { + const mapped = obj.map(a => this.key(a)); + mapped.forEach(a => this.#keys.add(a)); + this.emit("change", mapped); + } + + async update( + m: TWithCreated, + ): Promise<"new" | "refresh" | "updated" | "no_change"> { + // do nothing + return "refresh"; + } + + async buffer(keys: string[]) { + const missing = keys.filter(a => !this.#cache.has(a)); + const res = await this.bulkGet(missing); + return missing.filter(a => !res.some(b => this.key(b) === a)); + } + + key(of: CachedMetadata) { + return of.pubkey; + } + + snapshot() { + return [...this.#cache.values()]; + } +} diff --git a/packages/app/src/Cache/index.ts b/packages/app/src/Cache/index.ts index faebdea5..be0f9888 100644 --- a/packages/app/src/Cache/index.ts +++ b/packages/app/src/Cache/index.ts @@ -1,4 +1,4 @@ -import { RelayMetricCache, UserProfileCache, UserRelaysCache } from "@snort/system"; +import { RelayMetricCache, UserRelaysCache } from "@snort/system"; import { SnortSystemDb } from "@snort/system-web"; import { WorkerRelayInterface } from "@snort/worker-relay"; import WorkerRelayPath from "@snort/worker-relay/dist/worker?worker&url"; @@ -6,6 +6,7 @@ import WorkerRelayPath from "@snort/worker-relay/dist/worker?worker&url"; import { ChatCache } from "./ChatCache"; import { EventCacheWorker } from "./EventCacheWorker"; import { GiftWrapCache } from "./GiftWrapCache"; +import { ProfileCacheRelayWorker } from "./ProfileWorkeCache"; export const Relay = new WorkerRelayInterface(WorkerRelayPath); export async function initRelayWorker() { @@ -21,9 +22,10 @@ export async function initRelayWorker() { } export const SystemDb = new SnortSystemDb(); -export const UserCache = new UserProfileCache(SystemDb.users); export const UserRelays = new UserRelaysCache(SystemDb.userRelays); export const RelayMetrics = new RelayMetricCache(SystemDb.relayMetrics); + +export const UserCache = new ProfileCacheRelayWorker(Relay); export const EventsCache = new EventCacheWorker(Relay); export const Chats = new ChatCache(); @@ -31,7 +33,7 @@ export const GiftsCache = new GiftWrapCache(); export async function preload(follows?: Array) { const preloads = [ - UserCache.preload(follows), + UserCache.preload(), Chats.preload(), RelayMetrics.preload(), GiftsCache.preload(), diff --git a/packages/app/src/Pages/settings/Cache.tsx b/packages/app/src/Pages/settings/Cache.tsx index 0e972a98..acaa83f3 100644 --- a/packages/app/src/Pages/settings/Cache.tsx +++ b/packages/app/src/Pages/settings/Cache.tsx @@ -2,7 +2,7 @@ import { FeedCache } from "@snort/shared"; import { ReactNode, useEffect, useState, useSyncExternalStore } from "react"; import { FormattedMessage, FormattedNumber } from "react-intl"; -import { Chats, GiftsCache, Relay, RelayMetrics, UserCache } from "@/Cache"; +import { Chats, GiftsCache, Relay, RelayMetrics } from "@/Cache"; import AsyncButton from "@/Components/Button/AsyncButton"; export function CacheSettings() { @@ -12,7 +12,6 @@ export function CacheSettings() { - } /> } /> } /> } /> diff --git a/packages/app/src/system.ts b/packages/app/src/system.ts index fdc98568..2c12f8fb 100644 --- a/packages/app/src/system.ts +++ b/packages/app/src/system.ts @@ -28,6 +28,7 @@ System.on("auth", async (c, r, cb) => { System.on("event", (_, ev) => { Relay.event(ev); EventsCache.discover(ev); + UserCache.discover(ev); }); /**