TypeORM
@postel/typeorm — run Postel's storage through your existing TypeORM DataSource (Postgres, MySQL, or SQLite).
@postel/typeorm is an ORM adapter: hand Postel the TypeORM DataSource you already use and it issues its storage queries through it (via QueryRunners + raw SQL), so outbox writes share your connection and transactions. No Postel entities are required in your schema.
pnpm add @postel/typeorm typeormnpm install @postel/typeorm typeormyarn add @postel/typeorm typeormbun add @postel/typeorm typeormPlus your database driver — pg, mysql2, or better-sqlite3.
import { DataSource } from "typeorm";
import { Postel } from "@postel/core";
import { TypeOrmStorage } from "@postel/typeorm";
import { config } from "./config.js";
const dataSource = new DataSource({ type: "postgres", url: config.databaseUrl });
await dataSource.initialize();
export const postel = Postel({
outbound: {
storage: TypeOrmStorage({ dataSource, dialect: "postgres" }),
},
});Options
| Option | Notes |
|---|---|
dataSource | The initialized TypeORM DataSource you already built. Postel talks to it through QueryRunners + raw SQL. |
dialect | "postgres", "mysql", or "sqlite" — selects the reservation strategy, capability flags, and column codecs. |
autoMigrate | Run migrations on first use (default true). |
clock | Inject a clock for deterministic time in tests. |
typeorm is a peer dependency. On Postgres and MySQL workers reserve rows under FOR UPDATE SKIP LOCKED; on SQLite in a single statement. MySQL and SQLite poll for dispatch (no LISTEN/NOTIFY); Postgres pushes. The dialect names a SQL family, so wire-compatible engines (MariaDB, PlanetScale, libSQL/Turso, …) work via the matching dialect — see compatible databases.
Shared transactions
Open the transaction with TypeORM as usual and pass the transaction's QueryRunner to send() as tx, so the outbox insert commits atomically with your business writes:
await dataSource.transaction(async (em) => {
await em.save(order);
await postel.outbound.send(
{ type: "order.created", data: { /* ... */ } },
{ tx: em.queryRunner },
);
});Migrations
autoMigrate (default true) runs the canonical migrations through your connection, version-gated and idempotent — or run your own tooling against that schema.