Add incremental Automerge field changes

This commit is contained in:
Timothy J. Aveni
2026-09-03 09:35:28 -07:00
parent fceb49bb2c
commit a5899e6b57
5 changed files with 251 additions and 35 deletions
+50 -1
View File
@@ -2,7 +2,10 @@ import assert from "node:assert/strict";
import test from "node:test";
import * as Automerge from "@automerge/automerge";
import {
AUTOMERGE_CHANGES_ENCODING,
AUTOMERGE_SNAPSHOT_ENCODING,
createAutomergeFieldValue,
createAutomergeFieldChanges,
encodeAutomergeFieldWrite,
isAutomergeFieldDoc,
loadAutomergeFieldDoc,
@@ -17,7 +20,7 @@ test("Automerge CRDT fields explicitly create stored envelopes", () => {
const stored = createAutomergeFieldValue({ title: "draft" }, type);
assert.equal(stored.$caminoCrdtType, type);
assert.equal(stored.$caminoCrdtEncoding, "base64");
assert.equal(stored.$caminoCrdtEncoding, AUTOMERGE_SNAPSHOT_ENCODING);
assert.deepEqual(materializeAutomergeFieldValue(stored, type), {
title: "draft",
});
@@ -72,3 +75,49 @@ test("Automerge CRDT fields merge divergent saved documents", () => {
right: true,
});
});
test("Automerge CRDT fields encode and apply incremental changes", () => {
const stored = createAutomergeFieldValue({ title: "draft" }, type);
const base = loadAutomergeFieldDoc(stored, type);
const changed = Automerge.change(base, (doc) => {
(doc.value as { title: string }).title = "edited";
});
const update = createAutomergeFieldChanges(base, changed, type);
assert.equal(update.$caminoCrdtEncoding, AUTOMERGE_CHANGES_ENCODING);
assert.ok(update.$caminoCrdtPayload.length < saveAutomergeFieldDoc(changed, type).$caminoCrdtPayload.length);
const result = mergeAutomergeFieldValues({ existing: stored, incoming: update, type });
assert.deepEqual(result.materialized, { title: "edited" });
assert.equal(result.stored.$caminoCrdtEncoding, AUTOMERGE_SNAPSHOT_ENCODING);
});
test("Automerge change application is idempotent", () => {
const stored = createAutomergeFieldValue({ count: 0 }, type);
const base = loadAutomergeFieldDoc(stored, type);
const changed = Automerge.change(base, (doc) => {
(doc.value as { count: number }).count = 1;
});
const update = createAutomergeFieldChanges(base, changed, type);
const once = mergeAutomergeFieldValues({ existing: stored, incoming: update, type });
const twice = mergeAutomergeFieldValues({ existing: once.stored, incoming: update, type });
assert.deepEqual(twice.materialized, { count: 1 });
});
test("Automerge change batches reject malformed framing", () => {
const stored = createAutomergeFieldValue({ count: 0 }, type);
assert.throws(
() =>
mergeAutomergeFieldValues({
existing: stored,
incoming: {
$caminoCrdtType: type,
$caminoCrdtEncoding: AUTOMERGE_CHANGES_ENCODING,
$caminoCrdtPayload: Buffer.from([0, 0, 0, 1]).toString("base64"),
},
type,
}),
/missing change length/,
);
});