Haha, due to popular demand (and personal emails asking me “how the heck do I do this?”) the code I used is:
revertChangesSinceSnapshot(snapshot: string) {
// this removes the leading `\x` from the snapshot string that was specific to our implementation
const buff = toUint8Array(snapshot.replace(/^\\x/, ''));
const snap = Y.decodeSnapshot(buff);
const tempdoc = Y.createDocFromSnapshot(this.yDoc, snap);
// We cannot simply replace `this.yDoc` because we have to sync with other clients.
// Replacing `this.yDoc` would be similar to doing `git reset --hard $SNAPSHOT && git push --force`.
// Instead, we compute an "anti-operation" of all the changes made since that snapshot.
// This ends up being similar to `git revert $SNAPSHOT..HEAD`.
const currentStateVector = Y.encodeStateVector(this.yDoc);
const snapshotStateVector = Y.encodeStateVector(tempdoc);
// creating undo command encompassing all changes since taking snapshot
const changesSinceSnapshotUpdate = Y.encodeStateAsUpdate(this.yDoc, snapshotStateVector);
// In our specific implementation, everything we care about is in a single root Y.Map, which makes
// it easy to track with a Y.UndoManager. To be honest, your mileage may vary if you don't know which root types need to be tracked
const um = new Y.UndoManager(tempdoc.getMap(ROOT_YJS_MAP), { trackedOrigins: new Set([YJS_SNAPSHOT_ORIGIN]) });
Y.applyUpdate(tempdoc, changesSinceSnapshotUpdate, YJS_SNAPSHOT_ORIGIN);
um.undo();
// applying undo command to this.ydoc
const revertChangesSinceSnapshotUpdate = Y.encodeStateAsUpdate(tempdoc, currentStateVector);
Y.applyUpdate(this.yDoc, revertChangesSinceSnapshotUpdate, YJS_SNAPSHOT_ORIGIN);
}
It won’t work exactly as-is because it was part of a class and some things like this.yDoc
and YJS_SNAPSHOT_ORIGIN
are defined outside the function, but you should be able to piece together how it works. I hope.