Ferry
Concepts

Undo after a restart

sync.undo() reverts the user's last change, and the history it works from is saved on the device. Close the app, open it tomorrow, and the user can still undo yesterday's last steps, whether or not they have synced. This page is what is saved, where, and when it is dropped.

What a step is

One undoable write (a create, an update, a delete or a whole transaction) is one step. A step is not a snapshot of the data. It keeps the operations of that write, each with what it replaced:

The write was…The step remembers…
An updateThe previous values of the fields it changed
A createThat the row was created
A deleteThe whole row, so undo can bring it back

When the user presses undo, Ferry builds the reverse from the rows as they are now and sends it as a new write. That is why a step never goes stale: if another device changed a different field in the meantime, undo leaves that change alone.

Try it. Edit, undo and redo, then restart the app with persist on and off:

InteractiveThe undo history

Task title: Book flights

Undo stack

empty

Redo stack

empty

The history keeps the last 3 steps here (100 by default). Restart the app to see what persist does: the title survives either way, the history only when it is saved.

Where it lives

The history lives in the client's storage, next to the rows and the outbox, under keys that start with sync.undo. It is written together with the change it belongs to, so a crash cannot leave a write without its undo step.

Which storage you pick decides what survives:

StorageAfter a restart
IndexedDB, SQLite, MMKV, localStorageThe history is still there
Memory (the default of createSync)Nothing: the data is gone as well

The options

sync.tsClient
import { createSync, indexedDbStorage } from 'ferrysync';

import { schema } from './schema';

export const sync = createSync({
  schema,
  storage: indexedDbStorage('tasks'),
  undo: { limit: 50, persist: true },
});
OptionDefaultWhat it does
limit100The most steps kept, undo and redo together. The oldest go first
persisttrueSaves the history in storage. false keeps it in memory only

What leaves the history

  • The oldest step, when the limit is reached.
  • A write the server refuses: there is nothing left to undo.
  • The steps of a space the user loses access to, since undoing them would write somewhere the user can no longer see.
  • Everything, on connect and reset, and after an update that changes the schema version.

Things to know

  • Deleted rows stay on the device. A delete step keeps the whole row so it can come back. If rows are sensitive, set persist: false: it also clears whatever an earlier run stored.
  • The history belongs to the device. Another device never sees it; the undo itself is an ordinary write that syncs like any other.
  • Writes that are not the user's (a migration, a settings sync) should pass { undoable: false } so undo does not offer them.

Next

On this page