# How Thunderbolt's Offline-First Architecture Uses SQLite and PowerSync

> Discover how Thunderbolt's offline-first architecture uses SQLite for local data and PowerSync to replicate changes with a cloud PostgreSQL database. Learn about seamless data sync.

- Repository: [Thunderbird/thunderbolt](https://github.com/thunderbird/thunderbolt)
- Tags: architecture
- Published: 2026-04-19

---

**Thunderbolt achieves offline-first functionality by maintaining SQLite as the local source of truth while using PowerSync as a bidirectional replication layer that synchronizes changes with a cloud PostgreSQL database.**

Thunderbolt, the open-source email client from the Thunderbird team, implements a robust offline-first architecture that ensures users can access and modify their data without an internet connection. This article examines how the `thunderbird/thunderbolt` repository combines SQLite for local persistence with PowerSync for seamless synchronization across devices and platforms.

## Core Architecture: SQLite as the Local Source of Truth

Thunderbolt stores **all user data locally in an SQLite database** that lives inside the Tauri desktop or web runtime. This SQLite instance serves as the *single source of truth* for the UI, queries, and any local-only features, ensuring the application functions completely offline.

The architecture abstracts SQLite access through Drizzle ORM, using `wrapPowerSyncWithDrizzle` to provide type-safe queries across the codebase.

### Web vs Desktop SQLite Implementations

Thunderbolt supports multiple SQLite backends depending on the runtime environment:

- **`WaSQLiteDatabase`** ([`src/db/wa-sqlite-database.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/wa-sqlite-database.ts)): WebAssembly-based SQLite implementation used in web browsers
- **`BunSQLiteDatabase`** ([`src/db/bun-sqlite-database.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/bun-sqlite-database.ts)): Native SQLite implementation for Bun runtime on desktop

Both implementations provide the same interface, allowing the rest of the application to remain platform-agnostic.

## PowerSync Integration for Bidirectional Sync

When users enable synchronization, **PowerSync** bridges the local SQLite store and the cloud-side PostgreSQL database. PowerSync operates as a continuous replication layer that streams changes in both directions while maintaining ACID compliance at the local level.

### Initializing the Sync Engine

The `PowerSyncDatabaseImpl` class in [`src/db/powersync/database.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/database.ts) handles initialization and configuration:

```typescript
import { PowerSyncDatabaseImpl } from '@/db/powersync/database'

// Path can be any writable location; "thunderbolt.db" works for both desktop & web
await new PowerSyncDatabaseImpl().initialize('thunderbolt.db')

```

The `initialize()` method creates the SQLite instance and wraps it with PowerSync, as implemented in lines 97-104 of [`src/db/powersync/database.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/database.ts).

### Enabling and Disabling Sync

Sync functionality is toggled through user preferences using `setSyncEnabled()`:

```typescript
import { setSyncEnabled } from '@/db/powersync/database'

await setSyncEnabled(true)   // connects and starts streaming
await setSyncEnabled(false)  // disconnects cleanly

```

This function updates `localStorage` and calls `connectToSync()` or `disconnectFromSync()` (lines 90-115 of [`src/db/powersync/database.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/database.ts)).

## Custom Middleware: TransformableBucketStorage

Thunderbolt extends PowerSync's default behavior through a custom middleware layer called `TransformableBucketStorage`. This adapter intercepts PowerSync's writes and applies transformations before data persists to SQLite.

### Encryption and Data Transformation

The `ThunderboltPowerSyncDatabase` class in [`src/db/powersync/ThunderboltPowerSyncDatabase.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/ThunderboltPowerSyncDatabase.ts) overrides `generateBucketStorageAdapter()` to inject the middleware:

```typescript
import { ThunderboltPowerSyncDatabase } from '@/db/powersync/ThunderboltPowerSyncDatabase'
import { encryptionMiddleware } from '@/db/powersync/middleware/EncryptionMiddleware'

export const getPowerSyncOptions = (path: string) => ({
  database: { dbFilename: path },
  schema: AppSchema,
  transformers: [encryptionMiddleware],    // Applied before SQLite writes
  sync: { /* shared-worker config … */ },
})

```

The `TransformableBucketStorage` (in [`src/db/powersync/TransformableBucketStorage.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/TransformableBucketStorage.ts)) runs registered transformers such as `encryptionMiddleware` to decrypt columns during sync, enabling end-to-end encryption without breaking offline functionality.

### SharedWorker Implementation for Multi-Tab Support

For Chrome, Edge, and Firefox, Thunderbolt uses a custom `SharedWorker` to maintain a single sync connection across multiple tabs. The default PowerSync worker hard-codes `SqliteBucketStorage`, which would bypass the middleware layer.

Thunderbolt's [`ThunderboltSharedSyncImplementation.worker.ts`](https://github.com/thunderbird/thunderbolt/blob/main/ThunderboltSharedSyncImplementation.worker.ts) preserves the middleware pipeline:

```typescript
// Inside src/db/powersync/database.ts – default config
sync: {
  worker: () =>
    new SharedWorker(
      new URL('./worker/ThunderboltSharedSyncImplementation.worker.ts', import.meta.url),
      { type: 'module', name: `shared-sync-${dbFilename}` },
    ),
},

```

This ensures that `TransformableBucketStorage` and encryption middleware remain active even when sync runs in a separate thread, as documented in [`docs/powersync-sync-middleware.md`](https://github.com/thunderbird/thunderbolt/blob/main/docs/powersync-sync-middleware.md).

## Handling Safari and Tauri with Visibility-Based Reconnect

Safari and Tauri environments lack `SharedWorker` support, requiring a fallback mechanism to maintain sync reliability. Thunderbolt implements a visibility-based reconnect strategy in [`src/db/powersync/database.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/database.ts).

The `startVisibilityReconnect()` method (lines 82-104) monitors the page visibility state:

```typescript
// Automatically added by PowerSyncDatabaseImpl.startVisibilityReconnect()
document.addEventListener('visibilitychange', () => {
  // Hidden > 15s → force reconnect
})

```

When the page remains hidden for more than 15 seconds, the system forces a disconnect and reconnect cycle. This prevents stale connections and ensures that users receive the latest updates when returning to the application, working around Safari's limitations while maintaining the offline-first integrity.

## Summary

- **SQLite serves as the single source of truth** for all user data, stored locally via `WaSQLiteDatabase` (web) or `BunSQLiteDatabase` (desktop) to enable complete offline functionality.
- **PowerSync provides bidirectional synchronization** between the local SQLite store and cloud PostgreSQL, streaming changes continuously when connectivity is available.
- **TransformableBucketStorage enables custom middleware** such as `encryptionMiddleware` to transform data (e.g., decryption) before it persists to the local database.
- **Platform-specific optimizations** include a custom `SharedWorker` for multi-tab support in Chrome/Edge/Firefox and visibility-based reconnect logic for Safari and Tauri environments.

## Frequently Asked Questions

### How does Thunderbolt maintain offline-first functionality without an internet connection?

Thunderbolt stores all user data in a local SQLite database using either `WaSQLiteDatabase` for web environments or `BunSQLiteDatabase` for desktop. This SQLite instance acts as the single source of truth for the entire application, allowing users to read, write, and query data regardless of connectivity status. The UI interacts with this local database through Drizzle ORM, ensuring type-safe access to offline data.

### What is the difference between WA-SQLite and Bun-SQLite in Thunderbolt's architecture?

`WaSQLiteDatabase` (implemented in [`src/db/wa-sqlite-database.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/wa-sqlite-database.ts)) uses WebAssembly to run SQLite within browsers, providing a cross-platform solution for web deployments. `BunSQLiteDatabase` (in [`src/db/bun-sqlite-database.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/bun-sqlite-database.ts)) leverages Bun's native SQLite bindings for desktop environments, offering better performance through direct system integration. Both implementations expose identical interfaces, allowing the sync layer to remain platform-agnostic while optimizing for each runtime's capabilities.

### How does TransformableBucketStorage enable end-to-end encryption?

`TransformableBucketStorage` (defined in [`src/db/powersync/TransformableBucketStorage.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/TransformableBucketStorage.ts)) intercepts all data writes from PowerSync before they reach the SQLite database. When configured with transformers like `encryptionMiddleware`, it processes incoming sync data through the transformation pipeline—decrypting encrypted columns or performing other mutations—before persistence. This architecture ensures that sensitive data remains encrypted in transit and at rest in the cloud, while being decrypted locally for offline use, without breaking the sync protocol.

### Why does Thunderbolt use a custom SharedWorker implementation?

The default PowerSync `SharedWorker` hard-codes `SqliteBucketStorage`, which would bypass Thunderbolt's `TransformableBucketStorage` middleware and disable encryption transformations. Thunderbolt's custom [`ThunderboltSharedSyncImplementation.worker.ts`](https://github.com/thunderbird/thunderbolt/blob/main/ThunderboltSharedSyncImplementation.worker.ts) (configured in [`src/db/powersync/database.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/db/powersync/database.ts)) preserves the middleware pipeline when running sync in a separate thread. This implementation enables efficient multi-tab synchronization in Chrome, Edge, and Firefox while maintaining end-to-end encryption and custom data transformations across all browser contexts.