Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 109 additions & 2 deletions pages/docs/tutorial/time_travel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: "time travel in Loro"

In Loro, you can call `doc.checkout(frontiers)` to jump to the version specified
by the
frontiers([Learn more about frontiers](/docs/advanced/version_deep_dive)).
frontiers([Learn more about frontiers](/docs/advanced/version_deep_dive#frontiers)).

Note that using `doc.checkout(frontiers)` to jump to a specific version places
the document in a detached state, preventing further edits. To learn more, see
Expand All @@ -16,7 +16,114 @@ To continue editing, reattach the document to the latest version using
`doc.attach()`. This design is temporary and will be phased out once we have a
more refined version control API in place.

Below is an example demonstrating Time Travel functionality.
## Read-only Time Travel

Below we demonstrate how to implement simple, read-only time-travel. You could,
for example, combine this with a slider in a UI to allow users to view the document
over time.

### Enable Timestamps

Before this example will work, it is important that the edits made to the document
have had [timestamp storage](/docs/advanced/timestamp) enabled:

```ts
doc.setRecordTimestamp(true);
```

This makes sure that all changes to the document will have a timestamp added to it.
We will use this timestamp to sort changes so that the ordering will match user
intuition.

### Implementing Time Travel

The first step is to load our document. Here we assume that you have a snapshot from your database
or API.

```ts
// Get the snapshot for your doc from your database / API
let snapshot = fetchSnapshot();

// Import into a new document
const doc = new LoroDoc();
doc.import(snapshot);
```

Next we must collect and sort the timestamps for every change in the document. We want uesrs to be
able to drag a slider to select a timestamp out of this list.

```ts
// Collect all changes from the document
const changes = doc.getAllChanges();

// Get the timestamps for all changes
const timestamps = Array.from(
new Set(
[...changes.values()]
.flat() // Flatten changes from all peers into one list
.map((x) => x.timestamp) // Get the timestamp from each peer
.filter((x) => !!x)
)
);

// Sort the timestamps
timestamps.sort((a, b) => a - b);
```

Next we need to make a helper function that will return a list of
[Frontiers](/docs/advanced/version_deep_dive#frontiers) for any timestamp.

For each peer that has edited a document, there is a list of changes by that peer. Each change has a
`counter`, and a `length`. That `counter` is like an always incrementing version number for the
changes made by that peer.

A change's `counter` is the starting point of the change, and the `length` indicates how much the
change incremented the counter before the end of the change.

The frontiers are the list of counters that we want to checkout from each peer. Since we are going
for a timeline view, we want to get the highest counter that we know happned before our timestamp
for each peer.

Here we make a helper function to do that.

```ts
const getFrontiersForTimestamp = (
changes: Map<string, Change>,
ts: number
): { peer: string; counter: number }[] => {
const frontiers = [] as { peer: string; counter: number }[];

// Record the highest counter for each peer where it's change is not later than
// our target timestamp.
changes.forEach((changes, peer) => {
let counter = -1;
for (const change of changes) {
if (change.timestamp <= ts) {
counter = Math.max(counter, change.counter + change.length - 1);
}
}
if (counter > -1) {
frontiers.push({ counter, peer });
}
});
return frontiers;
};
```

Finally, all we can get the index from our slider, get the timestamp from our list, and then
checkout the calculated frontiers.

```ts
let sliderIdx = 3;
const timestamp = timestamps[sliderIdx - 1];
const frontiers = getFrontiersForTimestamp(changes, timestamp);

doc.checkout(frontiers);
```

## Time Travel With Editing

Below is a more complete example demonstrating Time Travel functionality with a node editor.

<iframe
src="https://loro-react-flow-example.vercel.app/"
Expand Down