RelaySetRepositoryImpl.ts raw

   1  import { RelaySetRepository, RelaySet, Pubkey, tryToRelaySet } from '@/domain'
   2  import client from '@/services/client.service'
   3  import indexedDb from '@/services/indexed-db.service'
   4  import { kinds } from 'nostr-tools'
   5  import { RepositoryDependencies } from './types'
   6  
   7  /**
   8   * IndexedDB + Relay implementation of RelaySetRepository
   9   *
  10   * Uses IndexedDB for local caching and the client service for relay fetching.
  11   * Save operations publish to relays and update the local cache.
  12   */
  13  export class RelaySetRepositoryImpl implements RelaySetRepository {
  14    constructor(private readonly deps: RepositoryDependencies) {}
  15  
  16    async findById(pubkey: Pubkey, id: string): Promise<RelaySet | null> {
  17      // Try cache first
  18      const cachedEvent = await indexedDb.getReplaceableEvent(pubkey.hex, kinds.Relaysets, id)
  19      if (cachedEvent) {
  20        const relaySet = tryToRelaySet(cachedEvent)
  21        if (relaySet) return relaySet
  22      }
  23  
  24      // Fetch from relays
  25      const events = await client.fetchEvents([], {
  26        kinds: [kinds.Relaysets],
  27        authors: [pubkey.hex],
  28        '#d': [id]
  29      })
  30  
  31      if (events.length === 0) return null
  32  
  33      // Get the most recent event
  34      const event = events.sort((a, b) => b.created_at - a.created_at)[0]
  35  
  36      // Update cache
  37      await indexedDb.putReplaceableEvent(event)
  38  
  39      return tryToRelaySet(event)
  40    }
  41  
  42    async findByOwner(pubkey: Pubkey): Promise<RelaySet[]> {
  43      // Fetch all relay sets from relays
  44      const events = await client.fetchEvents([], {
  45        kinds: [kinds.Relaysets],
  46        authors: [pubkey.hex]
  47      })
  48  
  49      // Deduplicate by 'd' tag (keep latest)
  50      const eventMap = new Map<string, typeof events[0]>()
  51      for (const event of events) {
  52        const d = event.tags.find((t) => t[0] === 'd')?.[1] || ''
  53        const existing = eventMap.get(d)
  54        if (!existing || existing.created_at < event.created_at) {
  55          eventMap.set(d, event)
  56        }
  57      }
  58  
  59      // Update cache and convert to domain objects
  60      const relaySets: RelaySet[] = []
  61      for (const event of eventMap.values()) {
  62        await indexedDb.putReplaceableEvent(event)
  63        const relaySet = tryToRelaySet(event)
  64        if (relaySet) {
  65          relaySets.push(relaySet)
  66        }
  67      }
  68  
  69      return relaySets
  70    }
  71  
  72    async save(_pubkey: Pubkey, relaySet: RelaySet): Promise<void> {
  73      const draftEvent = relaySet.toDraftEvent()
  74      const publishedEvent = await this.deps.publish(draftEvent)
  75  
  76      // Update cache
  77      await indexedDb.putReplaceableEvent(publishedEvent)
  78    }
  79  
  80    async delete(_pubkey: Pubkey, _id: string): Promise<void> {
  81      // Note: In Nostr, "deleting" a replaceable event is done by publishing
  82      // an empty or tombstone version. For now, we just remove from local cache.
  83      // The actual deletion logic depends on the application's requirements.
  84      // Typically you'd publish a new version that doesn't include the relay set.
  85    }
  86  }
  87