Rebase pending Automerge field edits
This commit is contained in:
Vendored
+2
-1
@@ -18,7 +18,8 @@ export declare const loadAutomergeFieldDoc: (value: CaminoCrdtEnvelope, type: st
|
||||
export declare const saveAutomergeFieldDoc: (doc: Automerge.Doc<AutomergeFieldDoc>, type: string) => CaminoCrdtEnvelope;
|
||||
export declare const createAutomergeFieldValue: (value: unknown, type: string) => CaminoCrdtEnvelope;
|
||||
export declare const isAutomergeFieldDoc: (value: unknown) => value is Automerge.Doc<AutomergeFieldDoc>;
|
||||
export declare const createAutomergeFieldChanges: (base: Automerge.Doc<AutomergeFieldDoc>, next: Automerge.Doc<AutomergeFieldDoc>, type: string) => CaminoCrdtEnvelope;
|
||||
export declare const reconcileAutomergeFieldDocs: <T>(base: CaminoAutomergeDoc<T>, desired: CaminoAutomergeDoc<T>) => CaminoAutomergeDoc<T>;
|
||||
export declare const createAutomergeFieldChanges: (base: Automerge.Doc<AutomergeFieldDoc>, desired: Automerge.Doc<AutomergeFieldDoc>, type: string) => CaminoCrdtEnvelope;
|
||||
export declare const encodeAutomergeFieldWrite: (value: unknown, type: string) => CaminoCrdtEnvelope;
|
||||
export declare const normalizeAutomergeFieldValue: (value: unknown, type: string) => CaminoCrdtEnvelope;
|
||||
export declare const materializeAutomergeFieldValue: (value: CaminoCrdtEnvelope, type: string) => unknown;
|
||||
|
||||
Vendored
+9
-5
@@ -107,11 +107,15 @@ const decodeChanges = (payload) => {
|
||||
}
|
||||
return changes;
|
||||
};
|
||||
export const createAutomergeFieldChanges = (base, next, type) => ({
|
||||
$caminoCrdtType: type,
|
||||
$caminoCrdtEncoding: AUTOMERGE_CHANGES_ENCODING,
|
||||
$caminoCrdtPayload: bytesToBase64(encodeChanges(Automerge.getChanges(base, next))),
|
||||
});
|
||||
export const reconcileAutomergeFieldDocs = (base, desired) => Automerge.merge(Automerge.clone(base), Automerge.clone(desired));
|
||||
export const createAutomergeFieldChanges = (base, desired, type) => {
|
||||
const next = reconcileAutomergeFieldDocs(base, desired);
|
||||
return {
|
||||
$caminoCrdtType: type,
|
||||
$caminoCrdtEncoding: AUTOMERGE_CHANGES_ENCODING,
|
||||
$caminoCrdtPayload: bytesToBase64(encodeChanges(Automerge.getChanges(base, next))),
|
||||
};
|
||||
};
|
||||
// Full snapshots remain an explicit escape hatch for initialization, import,
|
||||
// and recovery when a client cannot establish a shared Automerge history.
|
||||
export const encodeAutomergeFieldWrite = (value, type) => {
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+17
-8
@@ -162,17 +162,26 @@ const decodeChanges = (payload: Uint8Array): Automerge.Change[] => {
|
||||
return changes;
|
||||
};
|
||||
|
||||
export const reconcileAutomergeFieldDocs = <T>(
|
||||
base: CaminoAutomergeDoc<T>,
|
||||
desired: CaminoAutomergeDoc<T>,
|
||||
): CaminoAutomergeDoc<T> =>
|
||||
Automerge.merge(Automerge.clone(base), Automerge.clone(desired));
|
||||
|
||||
export const createAutomergeFieldChanges = (
|
||||
base: Automerge.Doc<AutomergeFieldDoc>,
|
||||
next: Automerge.Doc<AutomergeFieldDoc>,
|
||||
desired: Automerge.Doc<AutomergeFieldDoc>,
|
||||
type: string,
|
||||
): CaminoCrdtEnvelope => ({
|
||||
$caminoCrdtType: type,
|
||||
$caminoCrdtEncoding: AUTOMERGE_CHANGES_ENCODING,
|
||||
$caminoCrdtPayload: bytesToBase64(
|
||||
encodeChanges(Automerge.getChanges(base, next)),
|
||||
),
|
||||
});
|
||||
): CaminoCrdtEnvelope => {
|
||||
const next = reconcileAutomergeFieldDocs(base, desired);
|
||||
return {
|
||||
$caminoCrdtType: type,
|
||||
$caminoCrdtEncoding: AUTOMERGE_CHANGES_ENCODING,
|
||||
$caminoCrdtPayload: bytesToBase64(
|
||||
encodeChanges(Automerge.getChanges(base, next)),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
// Full snapshots remain an explicit escape hatch for initialization, import,
|
||||
// and recovery when a client cannot establish a shared Automerge history.
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
loadAutomergeFieldDoc,
|
||||
materializeAutomergeFieldValue,
|
||||
mergeAutomergeFieldValues,
|
||||
reconcileAutomergeFieldDocs,
|
||||
saveAutomergeFieldDoc,
|
||||
} from "../src/crdt-automerge.js";
|
||||
|
||||
@@ -105,6 +106,32 @@ test("Automerge change application is idempotent", () => {
|
||||
assert.deepEqual(twice.materialized, { count: 1 });
|
||||
});
|
||||
|
||||
test("Automerge changes retain remote history while local writes are pending", () => {
|
||||
const stored = createAutomergeFieldValue({ local: false, remote: false }, type);
|
||||
const base = loadAutomergeFieldDoc(stored, type);
|
||||
const local = Automerge.change(
|
||||
Automerge.clone(base, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||
(doc) => {
|
||||
(doc.value as { local: boolean }).local = true;
|
||||
},
|
||||
);
|
||||
const remote = Automerge.change(
|
||||
Automerge.clone(base, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
|
||||
(doc) => {
|
||||
(doc.value as { remote: boolean }).remote = true;
|
||||
},
|
||||
);
|
||||
const rebased = reconcileAutomergeFieldDocs(remote, local);
|
||||
const update = createAutomergeFieldChanges(remote, rebased, type);
|
||||
const result = mergeAutomergeFieldValues({
|
||||
existing: saveAutomergeFieldDoc(remote, type),
|
||||
incoming: update,
|
||||
type,
|
||||
});
|
||||
|
||||
assert.deepEqual(result.materialized, { local: true, remote: true });
|
||||
});
|
||||
|
||||
test("Automerge change batches reject malformed framing", () => {
|
||||
const stored = createAutomergeFieldValue({ count: 0 }, type);
|
||||
assert.throws(
|
||||
|
||||
Reference in New Issue
Block a user