import type { Document, Publication } from "./types.ts"; const DOCUMENT_COLLECTION = "site.standard.document"; const PUBLICATION_COLLECTION = "site.standard.publication"; interface AtUri { did: string; collection: string; rkey: string; } /** * Parse an AT URI into its components * Format: at://did:plc:xyz/collection/rkey */ export function parseAtUri(uri: string): AtUri { const match = uri.match(/^at:\/\/(did:[^/]+)\/([^/]+)\/([^/]+)$/); if (!match) { throw new Error(`Invalid AT URI: ${uri}`); } return { did: match[1] as string, collection: match[2] as string, rkey: match[3] as string, }; } /** * Get the PDS endpoint for a DID by resolving through plc.directory */ export async function getPdsEndpoint(did: string): Promise { const response = await fetch(`https://plc.directory/${did}`); if (!response.ok) { throw new Error(`Failed to resolve DID ${did}: ${response.status}`); } const doc = (await response.json()) as { service?: Array<{ id: string; serviceEndpoint: string }>; }; const pdsService = doc.service?.find((s) => s.id === "#atproto_pds"); if (!pdsService) { throw new Error(`No PDS service found for DID ${did}`); } return pdsService.serviceEndpoint; } /** * Fetch a publication record from a PDS */ export async function fetchPublication( pdsEndpoint: string, did: string, rkey: string, ): Promise { const url = `${pdsEndpoint}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=${PUBLICATION_COLLECTION}&rkey=${encodeURIComponent(rkey)}`; const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch publication: ${response.status}`); } const data = (await response.json()) as { value: Publication }; return data.value; } /** * Fetch all documents for a publication from a PDS */ export async function fetchDocuments( pdsEndpoint: string, did: string, publicationUri: string, ): Promise { const documents: Document[] = []; let cursor: string | undefined; do { const params = new URLSearchParams({ repo: did, collection: DOCUMENT_COLLECTION, limit: "100", }); if (cursor) { params.set("cursor", cursor); } const url = `${pdsEndpoint}/xrpc/com.atproto.repo.listRecords?${params}`; const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch documents: ${response.status}`); } const data = (await response.json()) as { records: Array<{ value: Document }>; cursor?: string; }; // Filter to only documents belonging to this publication for (const record of data.records) { if (record.value.site === publicationUri) { documents.push(record.value); } } cursor = data.cursor; } while (cursor); return documents; } /** * Fetch a publication and its documents given a publication AT URI */ export async function fetchPublicationWithDocuments( publicationUri: string, ): Promise<{ publication: Publication; documents: Document[]; }> { const { did, collection, rkey } = parseAtUri(publicationUri); if (collection !== PUBLICATION_COLLECTION) { throw new Error( `Expected ${PUBLICATION_COLLECTION} collection, got ${collection}`, ); } const pdsEndpoint = await getPdsEndpoint(did); const publication = await fetchPublication(pdsEndpoint, did, rkey); const documents = await fetchDocuments(pdsEndpoint, did, publicationUri); return { publication, documents }; }