# PowerSync Sync Middleware in Thunderbolt: Architecture and Extension Guide

> Explore the PowerSync sync middleware in Thunderbolt. Learn how this transformation layer converts database changes into actions and discover how to extend its functionality via custom transformer classes.

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

---

**PowerSync sync middleware is the transformation layer in Thunderbolt that converts raw database changes into domain-specific actions, implemented via the `SyncTransformer` interface in [`src/sync/ThunderboltSyncImplementation.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/sync/ThunderboltSyncImplementation.ts) and extensible by registering custom transformer classes.**

The Thunderbolt email client uses PowerSync to maintain real-time synchronization between local SQLite databases and cloud backends. The **PowerSync sync middleware** acts as the critical bridge between PowerSync's low-level change stream and Thunderbolt's domain-specific data models, handling everything from encryption to business logic enforcement.

## What Is PowerSync Sync Middleware?

The PowerSync sync middleware sits between PowerSync's change stream and the application's domain models. It is implemented as a set of **TypeScript classes** that conform to PowerSync's `SyncImplementation` interface.

The middleware handles three primary responsibilities:

- **Transforming raw PowerSync changes** – Converting generic `INSERT`, `UPDATE`, and `DELETE` events into higher-level domain actions that Thunderbolt can process.
- **Applying business-logic rules** – Enforcing soft-delete semantics, filtering transient fields, or merging server-generated timestamps before data reaches the UI.
- **Coordinating with the encrypted store** – When end-to-end encryption is enabled, the middleware decrypts incoming rows and encrypts outgoing ones before they hit the local SQLite database.

The core implementation lives in [`src/sync/ThunderboltSyncImplementation.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/sync/ThunderboltSyncImplementation.ts), which composes several reusable transformers located in `src/sync/middleware/`.

## How to Extend PowerSync Sync Middleware

Extending the middleware involves implementing the `SyncTransformer` interface and registering your transformer in the middleware chain.

### Create a Custom Transformer

A transformer is a class that implements the `SyncTransformer` interface with two optional methods:

- `transformIncoming(change: SyncChange)` – Processes changes from server to client.
- `transformOutgoing(change: SyncChange)` – Processes changes from client to server before upload.

Both methods receive a `SyncChange` object containing the operation type (`INSERT`, `UPDATE`, or `DELETE`), table name, and row data.

### Register the Transformer

Add your transformer instance to the `middleware` array when constructing the `ThunderboltSyncImplementation`. The order matters: transformers are applied sequentially, so place dependencies earlier in the array.

### Update the Sync Rule Configuration

If your transformer introduces logic for a new table or column, edit [`backend/config.yaml`](https://github.com/thunderbird/thunderbolt/blob/main/backend/config.yaml) under `backend/config` so PowerSync knows which tables the middleware should handle.

## Practical Example: Adding a lastEditedBy Field

Suppose you want every row in the `notes` table to automatically receive the current user’s ID on every update.

First, create the transformer:

```typescript
// src/sync/middleware/LastEditedByTransformer.ts
import type { SyncTransformer, SyncChange } from '@powersync/web'

export class LastEditedByTransformer implements SyncTransformer {
  constructor(private readonly getCurrentUserId: () => string) {}

  // Runs for incoming changes (server → client)
  async transformIncoming(change: SyncChange) {
    // No modification needed for incoming data
    return change
  }

  // Runs for outgoing changes (client → server)
  async transformOutgoing(change: SyncChange) {
    if (change.type === 'update' && change.table === 'notes') {
      const userId = this.getCurrentUserId()
      // Inject the field before the row is sent to the server
      change.row.lastEditedBy = userId
    }
    return change
  }
}

```

Then register it in the sync implementation:

```typescript
// src/sync/ThunderboltSyncImplementation.ts
import { ThunderboltSyncImplementation } from '@powersync/web'
import { LastEditedByTransformer } from './middleware/LastEditedByTransformer'

// Function that returns the signed‑in user’s ID
function getCurrentUserId() { /* … */ }

export const sync = new ThunderboltSyncImplementation({
  // Existing transformers …
  middleware: [
    // …other transformers,
    new LastEditedByTransformer(getCurrentUserId),
  ],
})

```

With this transformer registered, every `UPDATE` operation on the `notes` table automatically includes the `lastEditedBy` column, enabling server-side audit trails without modifying business logic elsewhere.

## Key Files and Architecture

Understanding the file structure helps navigate the middleware codebase:

- **[`src/sync/ThunderboltSyncImplementation.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/sync/ThunderboltSyncImplementation.ts)** – The main sync client that wires PowerSync together with the middleware chain. This file constructs the `SyncImplementation` and manages the lifecycle of transformers.
- **`src/sync/middleware/`** – Directory containing built-in transformers such as soft-delete handlers and encryption wrappers. This is where you place custom transformer files.
- **[`docs/powersync-sync-middleware.md`](https://github.com/thunderbird/thunderbolt/blob/main/docs/powersync-sync-middleware.md)** – Architectural documentation covering design goals, extension points, and best practices for the middleware layer.
- **[`backend/config.yaml`](https://github.com/thunderbird/thunderbolt/blob/main/backend/config.yaml)** – PowerSync cloud dashboard configuration defining which tables are synced and their transformation rules. Update this when adding new synced tables.

## Summary

- **PowerSync sync middleware** in Thunderbolt transforms raw database changes into domain-specific actions between PowerSync and the local SQLite store.
- The middleware lives in [`src/sync/ThunderboltSyncImplementation.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/sync/ThunderboltSyncImplementation.ts) and operates through the `SyncTransformer` interface.
- To extend functionality, implement `SyncTransformer` with `transformIncoming` and `transformOutgoing` methods, then register instances in the `middleware` array.
- Transformer order matters; data processes sequentially from first to last in the array.
- Update [`backend/config.yaml`](https://github.com/thunderbird/thunderbolt/blob/main/backend/config.yaml) when introducing new tables or sync rules.

## Frequently Asked Questions

### What is the PowerSync sync middleware responsible for?

The PowerSync sync middleware handles three core tasks: converting raw `INSERT`, `UPDATE`, and `DELETE` events from PowerSync into domain-specific actions, applying business logic like soft-delete semantics or field filtering, and coordinating end-to-end encryption by decrypting incoming rows and encrypting outgoing ones before they reach the local SQLite database.

### How do I add custom transformation logic to the sync pipeline?

Create a TypeScript class that implements the `SyncTransformer` interface with `transformIncoming` for server-to-client changes and `transformOutgoing` for client-to-server changes. Instantiate your class and add it to the `middleware` array in [`src/sync/ThunderboltSyncImplementation.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/sync/ThunderboltSyncImplementation.ts). The transformer operates on `SyncChange` objects containing the operation type, table name, and row data.

### Does the order of transformers in the middleware array matter?

Yes, the order is significant because transformers are applied sequentially. Data flows through the array from first to last, with each transformer receiving the output of the previous one. Place foundational transformers like decryption or validation early in the chain, and application-specific logic like audit fields or business rules later.

### Where is the PowerSync sync middleware configured in Thunderbolt?

The middleware implementation is constructed in [`src/sync/ThunderboltSyncImplementation.ts`](https://github.com/thunderbird/thunderbolt/blob/main/src/sync/ThunderboltSyncImplementation.ts), where the `middleware` array is defined. Sync rules that determine which tables are tracked are configured in [`backend/config.yaml`](https://github.com/thunderbird/thunderbolt/blob/main/backend/config.yaml) under the `backend/config` directory. Architectural documentation resides in [`docs/powersync-sync-middleware.md`](https://github.com/thunderbird/thunderbolt/blob/main/docs/powersync-sync-middleware.md).