A desktop database client accumulates context over the course of a session. Tabs open on tables, queries being written, schema views, unsaved SQL drafts. When the application restarts, whether from a crash, an update, or a deliberate close, you don't want to rebuild everything by hand.
QoreDB's crash recovery meets this need with a minimalist approach: continuously serialize a snapshot of the useful state, store it locally, and offer it at the next startup if the corresponding connection still exists. No background service, no auxiliary database, no remote synchronization.
What makes up a session's state
The snapshot that QoreDB persists contains only what can't be reconstructed from the backend: the current workspace identifier, the active connection identifier, the list of open tabs with their type and title, the active tab, the query drafts per tab, and the display state of the table and database browsers (the data, structure or info view).
Deliberately, query results aren't saved. A result is large, potentially sensitive, and has its own validity period on the server side. Replaying it at restart is more honest than restoring it from a stale local cache. Tabs of type plugin-output are also excluded from the snapshot: they depend on a plugin's lifecycle, and it's cleaner to let them disappear than to attempt a partial rehydration.
A save debounced at 600 milliseconds
The frontend triggers a save on every change to the observable state: opening a tab, typing in the SQL editor, switching the active tab, modifying a browser view. Without precautions, this would write to localStorage on every keystroke. The SessionProvider therefore applies a 600 ms debounce: the previous save is cancelled if a new one arrives before the deadline.
This window is a deliberate compromise. Too short, and you write for nothing and degrade typing on slow machines. Too long, and you lose the last few seconds of work in a crash. 600 ms is short enough for a user to perceive the loss as negligible, and long enough to absorb the normal pace of typing.
Why localStorage rather than a file on the Rust side
QoreDB runs in a Tauri WebView. It would have been possible to push the state to the Rust backend on every snapshot, which would have written it to a dedicated file. That brings nothing here. The session state is pure UI: it only makes sense within the window, it's read only by the frontend, and it doesn't survive a change of machine. Routing it through a Tauri call would add a round-trip on every debounce with no benefit.
localStorage offers a synchronous API, persistent across WebView restarts, and local to the OS user. The key used is qoredb_crash_recovery. The old keys qoredb_crash_recovery_v1 and qoredb_crash_recovery_v2 are read if the current key is absent, then migrated to the new format before being deleted. The migration is silent, with no user intervention.
Restoring a session at startup
When the SessionProvider mounts, the useRecovery hook attempts to read the snapshot. Three cases arise. If the referenced connection no longer exists in the workspace vault, the user is informed and can dismiss the snapshot. If the snapshot was created in a different workspace than the one currently open, a dedicated message says so: this is a legitimate case when you have several workspaces and a crash happened in another one. If the connection exists, a banner offers restoration.
Restoration calls the Tauri command connectSavedConnection, retrieves a new sessionId on the Rust side, then recomposes the tabs from the snapshot. The SQL drafts are re-injected into the editor via initialQuery, the active tab is restored, and the snapshot is immediately erased once restoration succeeds. If the reconnection fails, the state stays in localStorage: a new attempt will be offered at the next startup.
The user can also dismiss the snapshot manually. This is deliberate: we don't decide on their behalf to relaunch a connection that might point to a production database. The act of clicking to restore is the explicit confirmation.
TTL and privacy settings
A snapshot has a lifespan. By default, any snapshot older than 24 hours is considered stale and deleted on read. The value is configurable in the settings via the ttlHours field. This bound prevents a crash forgotten a week ago from offering to restore a state that no longer makes sense.
A second setting, saveQueryDrafts, controls the saving of the SQL editor's contents. When it's disabled, the tab structure is still persisted, but the text of in-progress queries is not. This is useful in two contexts: shared machines, and queries that occasionally contain sensitive literals. The setting is local, written to a separate localStorage key, and requires no arbitration on the backend side.
What this design implies in practice
QoreDB's crash recovery is entirely local to the machine and to the OS user. No state is sent or stored outside the application's WebView. This is consistent with the tool's local-first promise: you recover your session after a crash without any remote service having to be involved.
The mechanism is designed for single-user desktop use. The snapshot carries a projectId and a connectionId that are only valid for the current installation. This lack of portability is deliberate: a session state restored on another machine wouldn't have access to the encrypted connections of the local vault and would therefore have no way to reconnect.
Reliability earned through simplicity
A useful crash recovery doesn't need to be sophisticated. It needs to write frequently, read robustly, never block the application at startup, and ask for confirmation before relaunching a connection. These four properties fit in two TypeScript files totaling a few hundred lines, plus an integration in the SessionProvider. No local SQLite database, no background service, no versioned schema.
The result is a mechanism you understand by reading the code, that tests like a pure module, and that stays reliable because it has very little surface where it can fail. It's exactly the kind of component you want on a critical path: invisible when all is well, present when you need it.
Stay updated on new releases
Subscribe to get product releases, new drivers notifications, and technical tutorials.

