# Kysely



`@postel/kysely` is an **ORM/query-builder** adapter: you hand Postel the `Kysely` instance you already use, and it issues its queries (and transactions) through it. That means an outbox insert shares your connection and composes with your own writes — no second pool.

<Install packages="@postel/kysely kysely" />

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

```ts title="lib/postel.ts"
import { Kysely, PostgresDialect } from "kysely";
import { Pool } from "pg";
import { Postel } from "@postel/core";
import { KyselyStorage } from "@postel/kysely";
import { config } from "./config.js";

const db = new Kysely<DB>({
  dialect: new PostgresDialect({
    pool: new Pool({ connectionString: config.databaseUrl }),
  }),
});

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

## Options [#options]

| Option        | Notes                                                                                                                                                                                                              |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `db`          | Your `Kysely` instance.                                                                                                                                                                                            |
| `dialect`     | `"postgres"`, `"mysql"`, or `"sqlite"` — selects the reservation strategy (`FOR UPDATE SKIP LOCKED` on Postgres/MySQL, ordered on SQLite), the capability flags (`notify` on for Postgres), and the column codecs. |
| `autoMigrate` | Run migrations on first use (default `true`).                                                                                                                                                                      |
| `clock`       | Inject a clock for deterministic time in tests.                                                                                                                                                                    |

`kysely` is a peer dependency. Postel runs through Kysely's `sql` tag, so your `Kysely<DB>` needs no Postel-specific table typing. 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).

On **MySQL**, build your Kysely instance with a callback-style `mysql2` pool — `import { createPool } from "mysql2"`, as Kysely's `MysqlDialect` documents. A `mysql2/promise` pool leaves Kysely's `getConnection`/`end` callbacks unfired, so every query hangs; this is a Kysely requirement, not Postel-specific.

## Shared transactions [#shared-transactions]

Open a Kysely transaction and pass it to `send()` — the outbox insert commits atomically with your business writes:

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

## 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.
