import type { JetstreamEvent } from '@atcute/jetstream'; import type { RecordValue, UrlEntry } from './types'; import { sanitizeLastmod } from './xml'; // deletes carry no record and only the at-uri finds the url export type ExtractedAction = | { type: 'delete'; uri: string } | { type: 'upsert'; uri: string; did: string; url: string; entry: UrlEntry; claim?: string }; interface Located { owner: string; path: string; // the url only shows once a declaration at this uri points at the record's did claim?: string; } interface CollectionSpec { locate: (did: string, rkey: string, record: RecordValue) => Located | null; changefreq: UrlEntry['changefreq']; priority: number; } export const DECLARATION = 'org.tangled.repo.declaration'; export const COLLECTION_SPECS: Record = { // owner comes from the record, not the did the record lives in 'org.tangled.repo.manifest': { locate: (_did, _rkey, record) => { const { owner, name } = (record.declaration ?? {}) as { owner?: unknown; name?: unknown }; return typeof owner === 'string' && typeof name === 'string' && owner.startsWith('did:') && name ? { owner, path: `${owner}/${name}`, claim: `at://${owner}/${DECLARATION}/${name.toLowerCase()}` } : null; }, changefreq: 'daily', priority: 0.8, }, 'org.tangled.actor.profile': { locate: (did) => ({ owner: did, path: did }), changefreq: 'daily', priority: 0.8, }, 'org.tangled.snippet.string': { locate: (did, rkey) => ({ owner: did, path: `strings/${did}/${rkey}` }), changefreq: 'weekly', priority: 0.6, }, }; // owners are dids here so a url never moves when someone changes handle, and // rendering swaps the handle in export function extractUrlAction( event: JetstreamEvent, baseUrl: string, now = new Date(), ): ExtractedAction | null { if (event.kind !== 'commit') { return null; } const { collection, rkey } = event.commit; const spec = COLLECTION_SPECS[collection]; if (!spec) { return null; } const uri = `at://${event.did}/${collection}/${rkey}`; if (event.commit.operation === 'delete') { return { type: 'delete', uri }; } const record = event.commit.record as RecordValue; const located = spec.locate(event.did, rkey, record); if (!located) { return { type: 'delete', uri }; } const url = `${baseUrl.replace(/\/+$/, '')}/${located.path}`; const lastmod = sanitizeLastmod(record.createdAt as string | undefined, now); return { type: 'upsert', uri, did: located.owner, url, ...(located.claim && { claim: located.claim }), entry: { loc: url, lastmod, changefreq: spec.changefreq, priority: spec.priority, }, }; } export function extractDeclaration(event: JetstreamEvent): { uri: string; repo: string | null } | null { if (event.kind !== 'commit' || event.commit.collection !== DECLARATION) { return null; } const uri = `at://${event.did}/${DECLARATION}/${event.commit.rkey}`; if (event.commit.operation === 'delete') { return { uri, repo: null }; } const { repo } = event.commit.record as RecordValue; return { uri, repo: typeof repo === 'string' ? repo : null }; }