import type {SembleCard, SembleCollection, SembleCollectionLink} from "../semble/types"; import type {SembleUrlMetadata} from "../metadata/citoid"; import { CARD_COLLECTION, COLLECTION_COLLECTION, COLLECTION_LINK_COLLECTION } from "./collections"; const DEFAULT_DID = "did:example:semble"; export interface MockStrongRef { uri: string; cid: string; } export interface AtprotoRecordEnvelope { collection: string; rkey: string; record: Record; strongRef: MockStrongRef; recordType: "card" | "collection" | "collectionLink" | "note"; recordId: string; cardId?: string; collectionName?: string; } export interface AtprotoDraftRecords { cards: AtprotoRecordEnvelope[]; collections: AtprotoRecordEnvelope[]; collectionLinks: AtprotoRecordEnvelope[]; notes: AtprotoRecordEnvelope[]; } export function buildMockAtprotoRecords(input: { cards: SembleCard[]; collections: SembleCollection[]; collectionLinks: SembleCollectionLink[]; now?: Date; did?: string; }): AtprotoDraftRecords { const now = input.now ?? new Date(); const did = input.did ?? DEFAULT_DID; const cardRecords = input.cards.map(card => buildCardRecord(card, now, did)); const collectionRecords = input.collections.map(collection => buildCollectionRecord(collection, now, did) ); const cardRefMap = new Map( cardRecords.map(record => [record.recordId, record.strongRef]) ); const collectionRefMap = new Map( collectionRecords.map(record => [record.recordId, record.strongRef]) ); const collectionLinkRecords = input.collectionLinks.map(link => { const collectionRef = collectionRefMap.get(link.collectionName); const cardRef = cardRefMap.get(link.cardId); if (!collectionRef || !cardRef) { return buildCollectionLinkRecord(link, now, did, undefined, undefined); } return buildCollectionLinkRecord(link, now, did, collectionRef, cardRef); }); const noteRecords = input.cards .filter(card => Boolean(card.note)) .map(card => { const parentRef = cardRefMap.get(card.id); return buildNoteRecord(card, now, did, parentRef); }); return { cards: cardRecords.map(record => record.envelope), collections: collectionRecords.map(record => record.envelope), collectionLinks: collectionLinkRecords.map(record => record.envelope), notes: noteRecords.map(record => record.envelope) }; } function buildCardRecord(card: SembleCard, now: Date, did: string): { recordId: string; envelope: AtprotoRecordEnvelope; strongRef: MockStrongRef; } { const rkey = stableRkey(`card:${card.id}`); const uri = `at://${did}/${CARD_COLLECTION}/${rkey}`; const strongRef = {uri, cid: `mock:${stableHash(uri)}`}; const record: Record = { $type: CARD_COLLECTION, type: "URL", content: buildUrlContent(card), createdAt: now.toISOString() }; const envelope: AtprotoRecordEnvelope = { collection: CARD_COLLECTION, rkey, record, strongRef, recordType: "card", recordId: card.id }; return {recordId: card.id, envelope, strongRef}; } function buildUrlContent(card: SembleCard): Record { const content: Record = { $type: `${CARD_COLLECTION}#urlContent`, url: card.url }; const metadata = buildUrlMetadata(card.metadata); if (metadata) { content.metadata = metadata; } return content; } function buildUrlMetadata(metadata?: SembleUrlMetadata): Record | undefined { if (!metadata) return undefined; const record: Record = { $type: `${CARD_COLLECTION}#urlMetadata` }; if (metadata.title) record.title = metadata.title; if (metadata.description) record.description = metadata.description; if (metadata.author) record.author = metadata.author; if (metadata.publishedDate) { record.publishedDate = metadata.publishedDate.toISOString(); } if (metadata.siteName) record.siteName = metadata.siteName; if (metadata.type) record.type = metadata.type; if (metadata.doi) record.doi = metadata.doi; if (metadata.isbn) record.isbn = metadata.isbn; return record; } function buildCollectionRecord(collection: SembleCollection, now: Date, did: string): { recordId: string; envelope: AtprotoRecordEnvelope; strongRef: MockStrongRef; } { const rkey = stableRkey(`collection:${collection.name}`); const uri = `at://${did}/${COLLECTION_COLLECTION}/${rkey}`; const strongRef = {uri, cid: `mock:${stableHash(uri)}`}; const record: Record = { $type: COLLECTION_COLLECTION, name: collection.name, accessType: "OPEN", createdAt: now.toISOString() }; if (collection.description) { record.description = collection.description; } const envelope: AtprotoRecordEnvelope = { collection: COLLECTION_COLLECTION, rkey, record, strongRef, recordType: "collection", recordId: collection.name }; return {recordId: collection.name, envelope, strongRef}; } function buildNoteRecord( card: SembleCard, now: Date, did: string, parentRef?: MockStrongRef ): { recordId: string; envelope: AtprotoRecordEnvelope; } { const rkey = stableRkey(`note:${card.id}`); const uri = `at://${did}/${CARD_COLLECTION}/${rkey}`; const strongRef = {uri, cid: `mock:${stableHash(uri)}`}; const record: Record = { $type: CARD_COLLECTION, type: "NOTE", content: { $type: `${CARD_COLLECTION}#noteContent`, text: card.note }, createdAt: now.toISOString() }; if (card.url) { record.url = card.url; } if (parentRef) { record.parentCard = parentRef; } const envelope: AtprotoRecordEnvelope = { collection: CARD_COLLECTION, rkey, record, strongRef, recordType: "note", recordId: `${card.id}:note`, cardId: card.id }; return {recordId: `${card.id}:note`, envelope}; } function buildCollectionLinkRecord( link: SembleCollectionLink, now: Date, did: string, collectionRef?: MockStrongRef, cardRef?: MockStrongRef ): { recordId: string; envelope: AtprotoRecordEnvelope; } { const rkey = stableRkey(`link:${link.collectionName}:${link.cardId}`); const uri = `at://${did}/${COLLECTION_LINK_COLLECTION}/${rkey}`; const strongRef = {uri, cid: `mock:${stableHash(uri)}`}; const record: Record = { $type: COLLECTION_LINK_COLLECTION, collection: collectionRef ?? {uri: "at://unknown/collection", cid: "mock:unknown"}, card: cardRef ?? {uri: "at://unknown/card", cid: "mock:unknown"}, addedBy: did, addedAt: now.toISOString(), createdAt: now.toISOString() }; const envelope: AtprotoRecordEnvelope = { collection: COLLECTION_LINK_COLLECTION, rkey, record, strongRef, recordType: "collectionLink", recordId: `${link.collectionName}:${link.cardId}`, cardId: link.cardId, collectionName: link.collectionName }; return {recordId: `${link.collectionName}:${link.cardId}`, envelope}; } export function stableRkey(seed: string): string { return `semble-${stableHash(seed)}`; } function stableHash(input: string): string { let hash = 0; for (let i = 0; i < input.length; i += 1) { hash = (hash * 31 + input.charCodeAt(i)) | 0; } return Math.abs(hash).toString(36); }