Storage

TypeORM

@postel/typeorm — run Postel's storage through your existing TypeORM DataSource (Postgres, MySQL, or SQLite).

View as Markdown

@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 typeorm
npm install @postel/typeorm typeorm
yarn add @postel/typeorm typeorm
bun add @postel/typeorm typeorm

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

lib/postel.ts
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

OptionNotes
dataSourceThe 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.
autoMigrateRun migrations on first use (default true).
clockInject 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.

On this page