Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120import * as A from '@automerge/automerge'import bs58check from 'bs58check'import type { AutomergeUrl, DocHandle, DocumentId, Repository } from './document-runtime-contract'import type { DirectoryEntry, PushworkDirectoryDocument } from '../types/pushwork'
/** Versioned tuples, not concatenated names or mutable account handles. */export async function reservedDigest(...parts: string[]): Promise<Uint8Array> { return new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(parts))))}
function hex(bytes: Uint8Array): string { return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')}
const openings = new WeakMap<Repository, Map<AutomergeUrl, Promise<DocHandle<unknown>>>>()
function canonicalInitial<T>(initial: T): T { return JSON.parse(JSON.stringify(initial, (_key, value: unknown) => { if (value && typeof value === 'object' && !Array.isArray(value)) { return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) } return value })) as T}
function assertSharedGenesis(document: A.Doc<unknown>, genesis: A.Doc<unknown>): void { const hash = A.getHeads(genesis)[0]! const roots = A.getAllChanges(document).map(bytes => A.decodeChange(bytes)).filter(change => change.deps.length === 0) if (roots.length !== 1 || roots[0]!.hash !== hash) { throw new Error('Reserved document genesis mismatch') }}
/** The library import primitive loads with a fresh actor and merges shared * history. Genesis is independent of callers' defaults, clocks and devices. */export async function reservedDocumentGenesis<T>(scopeId: string, kind: string, initial: T): Promise<{ digest: Uint8Array; document: A.Doc<T> }> { const digest = await reservedDigest('reams/internal/v1', scopeId, kind) const document = A.change(A.init<T>({ actor: hex(digest) }), { time: 0 }, draft => Object.assign(draft as object, canonicalInitial(initial))) return { digest, document }}
export async function openReservedDocument<T>(repo: Repository, scopeId: string, kind: string, initial: T): Promise<DocHandle<T>> { const { digest, document: genesis } = await reservedDocumentGenesis(scopeId, kind, initial) const url = repo.reservedDocumentUrl?.(digest.slice(0, 16)) ?? `automerge:${bs58check.encode(digest.slice(0, 16))}` as AutomergeUrl const id = url.slice('automerge:'.length) as DocumentId let pending = openings.get(repo) if (!pending) { pending = new Map(); openings.set(repo, pending) } const previous = pending.get(url) if (previous) { const opened = await previous assertSharedGenesis(opened.doc(), genesis) return opened as DocHandle<T> } const opening = (async () => { if (repo.openReserved) return await repo.openReserved<T>(url, A.save(genesis)) if (!repo.import) throw new Error('Repository does not support reserved document import') const cached = repo.handles?.[id] if (cached?.isDeleted()) throw new Error('Reserved document is deleted') if (cached?.isReady()) { assertSharedGenesis(cached.doc(), genesis) return cached as DocHandle<T> } // Classic import treats a new handle as fresh and writes it immediately; // recover persisted detached history before that write can replace it. const stored = await repo.storageSubsystem?.loadDoc(id) if (stored) assertSharedGenesis(stored, genesis) const document = stored ? A.merge(A.load<T>(A.save(stored)), genesis) : genesis repo.import<T>(A.save(document), { docId: id }) return await repo.find<T>(url) })() pending.set(url, opening) try { return await opening } finally { if (pending.get(url) === opening) pending.delete(url) }}
/** Append exactly the same Automerge operation on independently initialized * clients. Both use the founding list's history and a per-link reserved actor; * ordinary device edits continue using the handle's independent actor. * This is an idempotent insertion, not projection-time deduplication. */export async function linkReservedDocument(parent: DocHandle<PushworkDirectoryDocument>, entry: DirectoryEntry, validateChild: () => void = () => {}): Promise<void> { entry = canonicalInitial(entry) const doc = parent.doc() assertFolderListUncontested(doc) const matches = doc.docs.filter(item => item.name === entry.name) if (matches.length) { if (matches.length === 1 && matches[0]!.url === entry.url && matches[0]!.type === entry.type) { validateChild(); return } throw new Error(`Ambiguous reserved link ${entry.name}`) } const listId = A.getObjectId(doc.docs) const founding = A.getAllChanges(doc).map(bytes => A.decodeChange(bytes)).find(change => change.ops.some((op, index) => op.action === 'makeList' && `${change.startOp + index}@${change.actor}` === listId), ) if (!founding) throw new Error('Reserved link requires a founding folder list') const actor = hex(await reservedDigest('reams/internal-link/v1', parent.url, founding.hash, JSON.stringify(entry))) const base = A.clone(A.view(doc, [founding.hash]), { actor }) const linked = A.change(base, { time: 0 }, draft => { draft.docs.push(entry) }) const change = A.getLastLocalChange(linked)! parent.update(current => { validateChild() assertFolderListUncontested(current) const occupied = current.docs.filter(item => item.name === entry.name) if (occupied.length) { if (occupied.length === 1 && occupied[0]!.url === entry.url && occupied[0]!.type === entry.type) return current throw new Error(`Ambiguous reserved link ${entry.name}`) } if (A.getObjectId(current.docs) !== listId) throw new Error('Folder list changed during reserved linking') const linked = A.applyChanges(A.clone(current, { actor: A.getActorId(current) }), [change])[0] const published = linked.docs.filter(item => item.name === entry.name) if (published.length !== 1 || published[0]!.url !== entry.url) { throw new Error('Reserved link was previously removed; explicit recovery is required') } return linked })}
export function assertFolderListUncontested(doc: A.Doc<PushworkDirectoryDocument>): void { if (!Array.isArray(doc.docs) || Object.keys(A.getConflicts(doc, 'docs') ?? {}).length > 1) { throw new Error('Conflicting or malformed folder list roots') }}