Created
July 13, 2023 17:50
-
-
Save stephen/2892b843927a17b9dc78a3751e8dc603 to your computer and use it in GitHub Desktop.
repair a damaged automerge doc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable no-loop-func */ | |
const Automerge = require("@automerge/automerge"); | |
const assert = require("node:assert"); | |
const fs = require("fs"); | |
function verify(document, unchecked = false) { | |
return Automerge.load(Automerge.save(document), { unchecked }); | |
} | |
const brokenDoc = process.argv[2]; | |
const original = Automerge.load(fs.readFileSync(brokenDoc)); | |
const changes = Automerge.getAllChanges(original); | |
let dummy = Automerge.init(); | |
let repaired = Automerge.init(); | |
for (const [i, change] of changes.entries()) { | |
console.time(`applied ${i}`); | |
const decoded = Automerge.decodeChange(change); | |
[dummy] = Automerge.applyChanges(dummy, [change], { | |
patchCallback: (patches, { after }) => { | |
repaired = Automerge.change( | |
repaired, | |
{ message: decoded.message, time: decoded.time }, | |
(doc) => { | |
for (const patch of patches) { | |
let target = doc; | |
for (const part of patch.path.slice(0, -1)) { | |
target = target[part]; | |
} | |
switch (patch.action) { | |
case "put": | |
let dummyValue = after; | |
for (const part of patch.path) { | |
dummyValue = dummyValue[part]; | |
} | |
target[patch.path[patch.path.length - 1]] = | |
dummyValue instanceof Automerge.Text | |
? new Automerge.Text(patch.value) | |
: patch.value; | |
break; | |
case "splice": | |
if (patch.marks) { | |
throw new Error("marks not implemented"); | |
} | |
if (target instanceof Automerge.Text) { | |
target.insertAt( | |
patch.path[patch.path.length - 1], | |
patch.value | |
); | |
} else if (Array.isArray(target)) { | |
target.splice( | |
patch.path[patch.path.length - 1], | |
0, | |
patch.value | |
); | |
} else { | |
throw new Error("unknown type to splice"); | |
} | |
break; | |
case "insert": | |
if (patch.conflicts || patch.marks) { | |
console.error(patch); | |
throw new Error("not supported"); | |
} | |
if (target instanceof Automerge.Text) { | |
target.insertAt( | |
patch.path[patch.path.length - 1], | |
...patch.values | |
); | |
} else if (Array.isArray(target)) { | |
target.splice( | |
patch.path[patch.path.length - 1], | |
0, | |
...patch.values | |
); | |
} else { | |
throw new Error("unknown type to splice"); | |
} | |
break; | |
case "del": | |
if (patch.length) { | |
if (target instanceof Automerge.Text) { | |
target.deleteAt( | |
patch.path[patch.path.length - 1], | |
patch.length | |
); | |
} else if (Array.isArray(target)) { | |
target.splice( | |
patch.path[patch.path.length - 1], | |
patch.length | |
); | |
} else { | |
throw new Error("unknown type to splice"); | |
} | |
} else { | |
delete target[patch.path[patch.path.length - 1]]; | |
} | |
break; | |
case "conflict": | |
break; | |
default: | |
throw new Error("not implemented"); | |
} | |
} | |
} | |
); | |
}, | |
}); | |
console.timeEnd(`applied ${i}`); | |
} | |
verify(repaired); | |
assert.deepStrictEqual(repaired, original); | |
fs.writeFileSync("fixed.bin", Automerge.save(repaired)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment