publish(task 8): outbox-relay-loader (kind:10002 parser + fetcher)

parseOutbox(ev) interpretiert nip-65 r-tags: ohne marker → read+write,
"read"/"write"-marker → nur jeweiliges set. ignoriert fremde tag-namen.
loadOutbox(bootstrapRelay, pubkey) fragt kind:10002-event via
applesauce-relay 2.x und parst das ergebnis. 3 tests grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jörg Lohrer 2026-04-18 05:27:47 +02:00
parent ebe73cbf46
commit 1ec48ad1a9
2 changed files with 79 additions and 0 deletions

View File

@ -0,0 +1,37 @@
import { Relay } from 'applesauce-relay'
import { firstValueFrom, timeout } from 'rxjs'
import type { SignedEvent } from './relays.ts'
export interface Outbox {
read: string[]
write: string[]
}
export function parseOutbox(ev: { tags: string[][] }): Outbox {
const read: string[] = []
const write: string[] = []
for (const t of ev.tags) {
if (t[0] !== 'r' || !t[1]) continue
const marker = t[2]
if (marker === 'read') read.push(t[1])
else if (marker === 'write') write.push(t[1])
else {
read.push(t[1])
write.push(t[1])
}
}
return { read, write }
}
export async function loadOutbox(
bootstrapRelay: string,
authorPubkeyHex: string,
): Promise<Outbox> {
const relay = new Relay(bootstrapRelay)
const ev = await firstValueFrom(
relay
.request({ kinds: [10002], authors: [authorPubkeyHex], limit: 1 })
.pipe(timeout({ first: 10_000 })),
) as SignedEvent
return parseOutbox(ev)
}

View File

@ -0,0 +1,42 @@
import { assertEquals } from '@std/assert'
import { parseOutbox } from '../src/core/outbox.ts'
Deno.test('parseOutbox: r-tags ohne marker → beide', () => {
const ev = {
kind: 10002,
tags: [
['r', 'wss://damus'],
['r', 'wss://nos'],
],
}
assertEquals(parseOutbox(ev), {
read: ['wss://damus', 'wss://nos'],
write: ['wss://damus', 'wss://nos'],
})
})
Deno.test('parseOutbox: marker read ignoriert schreib-nutzung', () => {
const ev = {
kind: 10002,
tags: [
['r', 'wss://r-only', 'read'],
['r', 'wss://w-only', 'write'],
['r', 'wss://both'],
],
}
assertEquals(parseOutbox(ev), {
read: ['wss://r-only', 'wss://both'],
write: ['wss://w-only', 'wss://both'],
})
})
Deno.test('parseOutbox: ignoriert andere tag-namen', () => {
const ev = {
kind: 10002,
tags: [
['r', 'wss://x'],
['p', 'someone'],
],
}
assertEquals(parseOutbox(ev), { read: ['wss://x'], write: ['wss://x'] })
})