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