import * as CID from '@atcute/cid'; import type { Cid } from '@atcute/lexicons'; /** A project's files by path. The SDK speaks in these; the app adapts them to the kernel's filesystem. */ export type FileTree = Map; /** Changes on top of a base tree. A null value deletes the base's path. */ export type Overlay = Map; /** The raw-codec CID a PDS assigns to these bytes as a blob. */ export async function blobCid(bytes: Uint8Array): Promise { return CID.toString(await CID.create(CID.CODEC_RAW, bytes)); } export async function hashTree(tree: ReadonlyMap): Promise> { const entries = await Promise.all([...tree].map(async ([path, bytes]) => [path, await blobCid(bytes)] as const)); return new Map(entries); } /** * The overlay that turns `base` into `current`, sorted by path. * Without a base the overlay is the whole tree; with one it holds only changed and added paths, plus a null for each removed path. */ export function diffAgainstBase( base: ReadonlyMap | undefined, current: ReadonlyMap, ): Overlay { const overlay: Overlay = new Map(); for (const [path, cid] of current) { if (base?.get(path) !== cid) overlay.set(path, cid); } if (base) { for (const path of base.keys()) { if (!current.has(path)) overlay.set(path, null); } } return sortByPath(overlay); } export function applyOverlay(base: ReadonlyMap, overlay: ReadonlyMap): Map { const tree = new Map(base); for (const [path, value] of overlay) { if (value === null) tree.delete(path); else tree.set(path, value); } return sortByPath(tree); } function sortByPath(map: Map): Map { return new Map([...map].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))); }