       newText: change.newText
     })
 
-  changes
+  {
+    type: 'transaction',
+    changes,
+    markersBefore: transaction.markerSnapshotBefore
+    markersAfter: transaction.markerSnapshotAfter
+  }
+
+transactionFromSnapshot = ({changes, markersBefore, markersAfter}) ->
+  patch = new Patch
+  for {oldStart, oldEnd, oldText, newStart, newEnd, newText} in changes by -1
+    oldExtent = traversal(oldEnd, oldStart)
+    newExtent = traversal(newEnd, newStart)
+    patch.splice(oldStart, oldExtent, newExtent, oldText, newText)
+
+  # TODO: Return raw patch if there's no markersBefore && markersAfter
+  new Transaction(markersBefore, patch, markersAfter)

checkpointFromSnapshot = ({id, markers}) ->
  new Checkpoint(id, markers, false)

getTransactionSnapshot = (transaction) ->
  {
    type: 'transaction',
    changes: getPatchSnapshot(transaction.patch),
    markersBefore: transaction.markerSnapshotBefore
    markersAfter: transaction.markerSnapshotAfter
  }

transactionFromSnapshot = ({changes, markersBefore, markersAfter}) ->
  patch = new Patch
  for {oldStart, oldEnd, oldText, newStart, newEnd, newText} in changes by -1
    oldExtent = traversal(oldEnd, oldStart)
    newExtent = traversal(newEnd, newStart)
    patch.splice(oldStart, oldExtent, newExtent, oldText, newText)

  # TODO: Return raw patch if there's no markersBefore && markersAfter
  new Transaction(markersBefore, patch, markersAfter)

getPatchSnapshot = (patch) ->
  changes = []
  for change in patch.getChanges() by 1
    changes.push({
      oldStart: change.oldStart,
      oldEnd: change.oldEnd,

    for entry in @redoStack by -1
      switch entry.constructor
        when Checkpoint
          redoStack.unshift(snapshotFromCheckpoint(entry))
        when Transaction
          redoStack.unshift(snapshotFromTransaction(entry))

      break if redoStack.length is maxEntries

