story-research-zapwall/lib/seriesQueries.ts
2026-01-06 14:17:55 +01:00

147 lines
4.0 KiB
TypeScript

import type { Event } from 'nostr-tools'
import { nostrService } from './nostr'
import type { Series } from '@/types/nostr'
import { parseSeriesFromEvent } from './nostrEventParsing'
import { buildTagFilter, extractTagsFromEvent } from './nostrTagSystem'
import { getPrimaryRelaySync } from './config'
import { PLATFORM_SERVICE, MIN_EVENT_DATE } from './platformConfig'
import { objectCache } from './objectCache'
import { parseObjectId } from './urlGenerator'
function buildSeriesFilters(authorPubkey: string): Array<{
kinds: number[]
authors?: string[]
'#series'?: string[]
since: number
}> {
const tagFilter = buildTagFilter({
type: 'series',
authorPubkey,
service: PLATFORM_SERVICE,
})
return [
{
kinds: tagFilter.kinds as number[],
...(tagFilter.authors ? { authors: tagFilter.authors as string[] } : {}),
...(tagFilter['#series'] ? { '#series': tagFilter['#series'] as string[] } : {}),
since: MIN_EVENT_DATE,
},
]
}
export function getSeriesByAuthor(authorPubkey: string, timeoutMs: number = 5000): Promise<Series[]> {
const pool = nostrService.getPool()
if (!pool) {
throw new Error('Pool not initialized')
}
const filters = buildSeriesFilters(authorPubkey)
const { createSubscription } = require('@/types/nostr-tools-extended')
return new Promise<Series[]>((resolve) => {
const results: Series[] = []
const relayUrl = getPrimaryRelaySync()
const sub = createSubscription(pool, [relayUrl], filters)
let finished = false
const done = (): void => {
if (finished) {
return
}
finished = true
sub.unsub()
resolve(results)
}
sub.on('event', async (event: Event): Promise<void> => {
const parsed = await parseSeriesFromEvent(event)
if (parsed) {
results.push(parsed)
}
})
sub.on('eose', (): void => {
done()
})
setTimeout(() => done(), timeoutMs).unref?.()
})
}
function buildSeriesByIdFilters(seriesId: string): Array<{
kinds: number[]
ids: string[]
since: number
[key: string]: unknown
}> {
return [
{
kinds: [1],
ids: [seriesId],
...buildTagFilter({
type: 'series',
service: PLATFORM_SERVICE,
}),
since: MIN_EVENT_DATE,
},
]
}
export async function getSeriesById(seriesId: string, timeoutMs: number = 5000): Promise<Series | null> {
// Try to parse seriesId as id format (<hash>_<index>_<version>) or use it as hash
const parsed = parseObjectId(seriesId)
const hash = parsed.hash ?? seriesId
// Check cache first
const cached = await objectCache.get('series', hash)
if (cached) {
return cached as Series
}
const pool = nostrService.getPool()
if (!pool) {
throw new Error('Pool not initialized')
}
const filters = buildSeriesByIdFilters(seriesId)
const { createSubscription } = require('@/types/nostr-tools-extended')
return new Promise<Series | null>((resolve) => {
const relayUrl = getPrimaryRelaySync()
const sub = createSubscription(pool, [relayUrl], filters)
let finished = false
const done = async (value: Series | null): Promise<void> => {
if (finished) {
return
}
finished = true
sub.unsub()
// Cache the result if found
if (value && value.hash) {
// Find the event to cache it
// Note: We would need the event here, but for now we cache what we have
// The event will be cached when it's parsed from the subscription
}
resolve(value)
}
sub.on('event', async (event: Event): Promise<void> => {
const parsed = await parseSeriesFromEvent(event)
if (parsed) {
// Cache the parsed series
const tags = extractTagsFromEvent(event)
if (parsed.hash) {
await objectCache.set('series', parsed.hash, event, parsed, tags.version ?? 0, tags.hidden ?? false, parsed.index)
}
await done(parsed)
}
})
sub.on('eose', (): void => {
void done(null)
})
setTimeout(() => done(null), timeoutMs).unref?.()
})
}