From 1ec48ad1a95c42ab77302dc11a1b32a438b92359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lohrer?= Date: Sat, 18 Apr 2026 05:27:47 +0200 Subject: [PATCH] publish(task 8): outbox-relay-loader (kind:10002 parser + fetcher) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- publish/src/core/outbox.ts | 37 +++++++++++++++++++++++++++++++ publish/tests/outbox_test.ts | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 publish/src/core/outbox.ts create mode 100644 publish/tests/outbox_test.ts diff --git a/publish/src/core/outbox.ts b/publish/src/core/outbox.ts new file mode 100644 index 0000000..de05222 --- /dev/null +++ b/publish/src/core/outbox.ts @@ -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 { + 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) +} diff --git a/publish/tests/outbox_test.ts b/publish/tests/outbox_test.ts new file mode 100644 index 0000000..bc9ab9c --- /dev/null +++ b/publish/tests/outbox_test.ts @@ -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'] }) +})