user-index.actor.ts raw

   1  import { Func, Inbox, Lifecycle, spawn, loop, STOP } from 'anneal'
   2  import { Event as NEvent, nip19 } from 'nostr-tools'
   3  import FlexSearch from 'flexsearch'
   4  
   5  export type UserIndexActor = {
   6    addProfile: Inbox<NEvent>
   7    search: Func<{ query: string; limit: number }, string[]>
   8    lc: Lifecycle
   9  }
  10  
  11  export function createUserIndexActor(): UserIndexActor {
  12    const index = new FlexSearch.Index({ tokenize: 'forward' })
  13  
  14    const addProfile = new Inbox<NEvent>(0)
  15    const search = new Func<{ query: string; limit: number }, string[]>()
  16    const lc = new Lifecycle()
  17  
  18    spawn(lc, async () => {
  19      await loop(
  20        lc.on(() => STOP),
  21        addProfile.on((event) => {
  22          try {
  23            const obj = JSON.parse(event.content)
  24            const text = [
  25              obj.display_name?.trim() ?? '',
  26              obj.name?.trim() ?? '',
  27              obj.nip05
  28                ?.split('@')
  29                .map((s: string) => s.trim())
  30                .join(' ') ?? ''
  31            ].join(' ')
  32            if (text) {
  33              index.add(event.pubkey, text)
  34            }
  35          } catch {
  36            // skip invalid profiles
  37          }
  38        }),
  39        search.on(({ req: { query, limit }, reply }) => {
  40          const result = index.search(query, { limit })
  41          reply(
  42            result.map((pubkey) => nip19.npubEncode(pubkey as string)).filter(Boolean) as string[]
  43          )
  44        })
  45      )
  46    })
  47  
  48    return { addProfile, search, lc }
  49  }
  50