# Drizzle



`@postel/drizzle` is an **ORM** adapter: hand Postel the Drizzle `db` you already use and it issues its storage queries through it, so outbox writes share your connection and transactions.

<Install packages="@postel/drizzle drizzle-orm" />

Plus your database driver — `pg`, `mysql2`, or `better-sqlite3`.

```ts title="lib/postel.ts"
import { drizzle } from "drizzle-orm/node-postgres";
import { Postel } from "@postel/core";
import { DrizzleStorage } from "@postel/drizzle";
import { config } from "./config.js";

const db = drizzle(config.databaseUrl);

export const postel = Postel({
  outbound: {
    storage: DrizzleStorage({ db, dialect: "postgres" }),
  },
});
```

## Options [#options]

| Option        | Notes                                                                                                                                                                                   |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `db`          | The Drizzle database you already built — any driver (`drizzle-orm/node-postgres`, `drizzle-orm/better-sqlite3`, …). Postel issues its queries through it.                               |
| `dialect`     | `"postgres"`, `"mysql"`, or `"sqlite"` — selects the reservation strategy (`FOR UPDATE SKIP LOCKED` on Postgres/MySQL, ordered on SQLite), the capability flags, and the column codecs. |
| `autoMigrate` | Run migrations on first use (default `true`).                                                                                                                                           |
| `clock`       | Inject a clock for deterministic time in tests.                                                                                                                                         |

`drizzle-orm` is a peer dependency. Postel runs through Drizzle's `sql` tag, so no Postel-specific table definitions are required in your schema. The `dialect` names a SQL *family*, so wire-compatible engines (MariaDB, PlanetScale, libSQL/Turso, …) work via the matching dialect — see [compatible databases](/docs/storage#dialects-and-compatible-databases).

## Shared transactions [#shared-transactions]

Pass a Drizzle transaction to `send()` so the outbox insert commits atomically with your business writes:

```ts
await db.transaction(async (tx) => {
  await tx.insert(orders).values({ /* ... */ });
  await postel.outbound.send({ type: "order.created", data: { /* ... */ } }, { tx });
});
```

## Migrations [#migrations]

`autoMigrate` (default `true`) runs the [canonical migrations](/docs/storage/schema) through your connection, version-gated and idempotent — or run your own tooling against that schema.

### The `/schema` export [#the-schema-export]

If you'd rather have Postel's tables live in your own Drizzle schema file — so `drizzle-kit generate` / `push` / `studio` see them — import the fragment instead of relying on `autoMigrate`:

```ts title="db/schema.ts"
export { pgTenants, pgEndpoints, pgMessages, pgAttempts } from "@postel/drizzle/schema";
```

The export is namespaced by dialect prefix (`pg*`, `mysql*`, `sqlite*`) for all seven canonical tables (`tenants`, `endpoints`, `endpointSecrets`, `messages`, `attempts`, `endpointStateTransitions`, `receivedMessages`). Merge the ones matching your dialect into your schema and run migrations with your own tooling; pass `autoMigrate: false` to `DrizzleStorage` so Postel doesn't also try.
