Is there a way to revert to a specific version?

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.

9 Likes