/** * Identity-preserving merges. * * Rich text in a CMS is not just its words. A Portable Text block carries a * `_key` that annotations and real-time collaboration address it by; a * Contentful node carries a `data` object with entry links in it; every format * has fields no converter here understands. Re-serialising a document from * scratch throws all of that away, and the diff a content editor sees on the * next publish is "every block replaced" — which is both wrong and, on a * dataset of any size, unreviewable. * * So a write is always a merge against what was already stored: blocks whose * content did not change come back as the *original objects*, untouched, and * only genuinely edited blocks are rebuilt. */ import { type RichTextDoc } from "./doc.ts"; export declare function deepEqual(a: unknown, b: unknown): boolean; /** A shallow copy of `value` with the listed keys removed, recursively. */ export declare function omitDeep(value: T, keys: readonly string[]): T; /** * Pair each newly built item with the stored item it replaces, and keep the * stored one whenever the two say the same thing. * * Matching runs in two passes because a block that only *moved* must keep its * key: content-equality first (so a reordered document reuses every key), then * position for whatever is left over (so an edited block keeps the key of the * block that was in its slot rather than being issued a new one). * * `identityKeys` are the fields excluded from the content comparison — the * stored key itself, and anything else derived rather than authored. */ export declare function mergeByIdentity(next: T[], previous: T[], options: { /** * Reduce an item to just the part a human authored, so two items that say * the same thing compare equal however their storage keys differ. */ canonical: (item: T) => unknown; /** Copy the stored item's identity onto a rebuilt one. */ adopt: (rebuilt: T, stored: T) => T; /** Should this stored item be considered at all? */ matches?: (rebuilt: T, stored: T) => boolean; }): T[]; /** * Fold a freshly built document into the one that was stored. * * Two jobs, and the second is the one that stops data loss: * * 1. Carry stored `_key`s onto the rebuilt nodes, so a CMS write updates blocks * rather than replacing them. * 2. Put back anything the rebuilt document *could not have contained*. When a * plan rewrites a rich-text prop it sends a markdown string, and markdown has * no way to say "an embedded product card sat here" — so a naive coercion * deletes every such block on the first text edit. They are re-inserted at * the position they held before. That position is a guess once the * surrounding prose has changed length; keeping the block in roughly the * right place is the recoverable failure, deleting it is not. */ export declare function mergeRichTextDoc(next: RichTextDoc, previous: RichTextDoc, options?: { /** * The mark types the format `next` was built from can express. Marks * outside this set are treated as invisible: they take no part in deciding * whether a node changed, and they are carried over from the stored * document. Defaults to markdown's vocabulary, which is what every caller * of this function is coercing from. Pass every mark type in play to opt * out — appropriate only when `next` came from a lossless source. */ representableMarks?: readonly string[]; }): RichTextDoc;