Skip to content

How Querybara works

The Querybara desktop app is an Electron app split into several processes, each with one job. The page you work in has no Node.js and no database drivers. Each open connection gets a process of its own, long jobs run in another, and the main process keeps the local store, the secrets and the scheduler. This page explains who does what and how data moves between them.

How Querybara worksThe Querybara window is a React page without Node.js behind a sandboxed preload. It talks to the main process over a MessagePort with zod-validated messages. Each open connection runs in its own utility process with a direct port to the page; secrets go only from main to those hosts; long jobs run in a job runner process.WindowReact rendererno Node.js · CSP 'self'Preloadsandboxed · forwards portsMain processstore · keychain · dialogsShop · productionconnection hostCatalog · Mongoconnection hostCache · Redisconnection hostJob runnerstarted on demandrpcportsecretsecretsecretrowsprogress

How Querybara works

  1. The window is a React page without Node.js. Its preload is sandboxed and only forwards message ports.
  2. The page talks to the main process over a MessagePort: typed calls, each message validated with zod.
  3. Opening a connection starts a utility process for it, and main hands the page a port straight to that host.
  4. Passwords and keys go from main to the hosts only. The page never holds them.
  5. Result rows go from the host straight to the page, never through main.
  6. Imports, exports, compares and backups run in a job runner process started on demand; main relays their progress.
Process What it does
Main App lifecycle, the window, the local store, the main RPC contract, the scheduler, and supervision
Preload Sandboxed. Hands RPC ports to the page and exposes nothing else but platform and version strings
Renderer The React interface. A sandboxed web page without Node.js
Connection host One utility process per open connection. Loads the driver, opens the sessions and any SSH tunnel
Job runner One utility process, started on demand, for jobs: imports, exports, backups, transfers, compares and more

Database drivers never load into main or the renderer. They run only in connection hosts and the job runner.

Main opens the local store, answers the page’s requests on the main contract (profiles, settings, history, jobs, schedules, updates), starts connection hosts and the job runner, and checks SSH host keys for both. It also holds the secrets: saved passwords are unsealed here and nowhere else in the app. The scheduler and the desktop notifications run here too.

On Windows and Linux the page draws the window’s title bar and its menu bar; each menu item runs in main, as the native menu’s would.

The renderer is served from Querybara’s own app://querybara/ origin, with a Content Security Policy that allows nothing remote. It runs sandboxed, without Node.js, with context isolation.

The preload is a dozen lines with no logic of its own. It forwards message ports from main to the page after checking their shape, and the page accepts a port only from its own window and origin. No ipcRenderer method is reachable from the page.

Opening a connection starts a utility process for it. The host receives the resolved profile from main, loads the driver, opens the SSH tunnel or proxy route when the profile has one, and serves the connection’s RPC contract directly to the page. Every tab of a connection uses the same host, and the tunnel’s SSH session is shared between them.

A crashed host closes its ports, so the page learns about it at once, and main reports the restart or failure. The page reconnects by asking main for the connection again, which hands it a new port.

Long work runs in one job runner process: imports, exports, Run SQL File, data transfers, backups and restores, structure and data compares and their applies. It also answers requests that belong to no job: the wizards’ previews and column matching, and the reading of a Redis dump file for Dump analysis. Main starts it when the first job or request arrives and stops it after 30 seconds with nothing running.

Jobs run side by side, each with its own driver session and, for a profile with SSH or a proxy, its own tunnel. A job survives closing its tab. If the runner exits, the jobs it was running fail and the next job starts a new runner. Cancel aborts the job at the next batch, and an import rolls back its transaction.

The page never talks to the runner directly. Main relays progress, logs and results, since job traffic is progress, not result rows.

Every RPC channel is a MessagePort. Main creates a channel, serves its end, and transfers the other end to the page through the preload. The RPC is typed (@querybara/ipc), and every message is validated with zod on arrival. Streams, progress events and cancellation work the same way on every channel.

Channel Carries
Page ↔ main The main contract: profiles, settings, history, jobs, schedules, updates
Page ↔ connection host Queries, result rows, browsing, edits for that connection
Main ↔ connection host Control: connect, check, attach, shutdown; ready, failed, host keys
Main ↔ job runner Control: start, cancel, requests; progress, logs, done, host keys

The control channels between main and the hosts or the runner never carry anything towards the page.

Secrets travel only from main to a connection host or the job runner, inside the resolved profile that opens a session. They never go to the page. The local store keeps saved passwords sealed by the OS keychain; see Security model.

Life of a queryA SQL statement travels from the query tab through the statement splitter and the safety check to the connection host process, through an optional SSH tunnel to the database; result rows stream back from the host directly to the results grid, bypassing the main process.Query tabeditorSplitter; · DELIMITER · $$Safety checkwrite rulesConnection hostone process eachSSH tunnelwhen the profile has onePostgreSQLlarchwoodMain processnot on the row pathResults grid1,000 rowsConfirm: productionselect … from shop.ordersSQLstmtstmtstmtstmt#10421 Oak dining tablecancelcancel

Life of a query

  1. You run all of the editor, the statement at the cursor, or the selection.
  2. The splitter cuts the text into statements. It understands DELIMITER, dollar quoting and nested comments, so a function body stays whole.
  3. Each statement passes the safety check: a risky write, or any write on a production profile, waits for you to confirm it.
  4. The statement goes to the connection’s own host process, then through the profile’s SSH tunnel if it has one, to the server.
  5. Rows stream from the host straight to the page, 1,000 at a time, into the results grid. They never pass through the main process.
  6. Cancel asks the server itself to stop the statement, and the session stays usable.
  1. You run all, the statement at the cursor or the selection. The page’s statement splitter cuts the script into statements, respecting DELIMITER, dollar quoting and comments.
  2. The safety check decides whether a statement needs a confirmation, for example a risky write on a production profile, and the page asks you before it runs.
  3. The statement goes to the connection’s host over the connection’s port.
  4. The host runs it on its session, through the SSH tunnel when the profile has one.
  5. Rows stream from the host straight to the page, 1,000 at a time, into the result grid. They never pass through main.
  6. Cancel sends a server-side cancel for the running statement.

Structure and data compares run as jobs in the runner, which opens both connections. Main keeps each finished comparison, and the page asks main for the script of the operations you selected.

An apply runs only the script you reviewed: the page sends its SHA-256, and the runner generates the script again from the kept comparison and refuses to run a different one. The write rules are checked twice, in main before the job starts and in the runner before each statement.

Data compares write their row differences to a folder under the system’s temporary folder, so a large difference never sits in memory.

The scheduler lives in main and runs schedules only while Querybara is open. Nothing is installed in the operating system.

  • It keeps each enabled schedule’s next run time, and wakes at the soonest one and at least once a minute. It also wakes when the computer resumes from sleep or the screen is unlocked, so a sleep or a clock change is noticed at once.
  • Backups, SQL files and exports run as jobs in the job runner, after the same checks as a job you start yourself: the connection’s write rules, and the files and folders you picked. A saved comparison runs in the job runner as the Compare button runs it.
  • A scheduled run cannot ask for a password. Each password the connection needs must be saved, or typed earlier in the session; otherwise the run fails and says so. The passphrase of an encrypted backup schedule is kept in the secret store, sealed like a saved password.
  • A run more than two minutes late was missed, for example because Querybara was closed or the computer was asleep. Each schedule says whether a missed run is caught up once or skipped. A schedule that is still running when it is due again skips that run.
  • Every run is recorded in the schedule’s history, skipped ones included. Main sends a desktop notification when a run fails or a comparison finds differences, or as the schedule says.

Closing Querybara with schedules on asks first, on every platform; there is no tray icon. The question is not asked when no schedule is on, when the system shuts down, restarts or logs out, or when Querybara restarts to install an update. On macOS, closing the last window leaves Querybara running, so only quitting asks.

The shared packages never import Electron. querybara, the command-line tool, runs the same drivers, sync engine, transfer and backup code in a single Node.js process, and opens its own tunnels. See the CLI reference.

Documents Querybara 0.1.1 · built frombc9f5aa