Clean up reconcile wrapper

This commit is contained in:
Andras Schmelczer 2026-09-27 21:52:25 +01:00
commit 58ab2e004e

View file

@ -1,60 +1,46 @@
import { base64ToBytes, bytesToBase64 } from "byte-base64"; import type { RelativePath } from "../persistence/database";
import { reconcile } from "reconcile-text"; import { reconcile } from "reconcile-text";
import type { FileSnapshot } from "../file-operations/filesystem-operations"; import { toHashedSnapshot, type HashedSnapshot } from "../snapshot";
import type { StoredSnapshot } from "../persistence/database";
import { hash } from "../utils/hash";
import { isBinary } from "../utils/is-binary"; import { isBinary } from "../utils/is-binary";
import { isFileTypeMergable } from "../utils/is-file-type-mergable"; import { isFileTypeMergeable } from "../utils/is-file-type-mergeable";
import { decodeText } from "../utils/decode-text";
export async function toStoredSnapshot(
snapshot: FileSnapshot
): Promise<StoredSnapshot> {
return {
contentBase64: bytesToBase64(snapshot.content),
cursors: snapshot.cursors,
hash: await hash(snapshot.content)
};
}
export function fromStoredSnapshot(snapshot: StoredSnapshot): FileSnapshot {
return {
content: base64ToBytes(snapshot.contentBase64),
cursors: snapshot.cursors
};
}
export async function mergeContent( export async function mergeContent(
path: string, path: RelativePath,
base: StoredSnapshot | undefined, base: HashedSnapshot | undefined,
local: StoredSnapshot, local: HashedSnapshot,
remote: StoredSnapshot, remote: HashedSnapshot,
extensions: string[] extensions: string[]
): Promise<StoredSnapshot> { ): Promise<HashedSnapshot> {
if (local.contentBase64 === remote.contentBase64) if (local.hash === remote.hash) {
return { return {
...remote, ...remote,
cursors: local.cursors cursors: local.cursors
}; };
}
if (!local.cursors && local.contentBase64 === base?.contentBase64) if (!local.cursors && local.hash === base?.hash) {
return remote; return remote;
}
if (remote.contentBase64 === base?.contentBase64) return local; if (remote.hash === base?.hash) {
return local;
}
const inputs = [ const inputs = [
base?.contentBase64 ?? "", base?.content ?? new Uint8Array(),
local.contentBase64, local.content,
remote.contentBase64 remote.content
].map(base64ToBytes); ];
// Concurrent unmergeable edits keep the server's content. The filesystem // Concurrent unmergeable edits keep the server's content.
// journal retains displaced local bytes for recovery. if (!isFileTypeMergeable(path, extensions) || inputs.some(isBinary)) {
if (!isFileTypeMergable(path, extensions) || inputs.some(isBinary))
return remote; return remote;
}
const [parentText, localText, remoteText] = inputs.map((bytes) => const parentText = decodeText(base?.content ?? new Uint8Array());
new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes) const localText = decodeText(local.content);
); const remoteText = decodeText(remote.content);
const merged = reconcile( const merged = reconcile(
parentText, parentText,
@ -62,7 +48,7 @@ export async function mergeContent(
remoteText remoteText
); );
return toStoredSnapshot({ return toHashedSnapshot({
content: new TextEncoder().encode(merged.text), content: new TextEncoder().encode(merged.text),
cursors: local.cursors ? merged.cursors : undefined cursors: local.cursors ? merged.cursors : undefined
}); });