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<string, CollectionSpec> = {
// 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 };
}