Workhorse
Integrations

TypeORM

Adapt a TypeORM data source and enqueue jobs through transactional entity managers.

Your application already writes through TypeORM. When a request saves an entity and enqueues a follow-up job, those two writes must commit together — a job for a row that rolled back is a bug, and a committed row with no job is a silent gap. @stablemates/workhorse-typeorm closes that gap: it converts TypeORM data sources and transactional entity managers into the core Queryable protocol, so Queue.enqueue runs inside the same transaction as your entity writes.

Workhorse keeps its schema lifecycle outside TypeORM migrations. TypeORM owns your entities; Workhorse owns its versioned SQL functions.

Create the adapter

Install the integration beside TypeORM.

pnpm add @stablemates/workhorse-typeorm typeorm

Pass an initialized data source to createTypeOrmAdapter. The close callback is optional because the adapter does not destroy caller-owned data sources by default.

import { createTypeOrmAdapter } from "@stablemates/workhorse-typeorm";

const workhorse = createTypeOrmAdapter(dataSource, {
  close: () => dataSource.destroy(),
});

The returned WorkhorseAdapter exposes database, queue, admin, forTransaction, adminForTransaction, createWorker, and close — one stable surface for request handlers, operator tools, and worker processes.

Enqueue inside a transaction

This is the pattern the adapter exists for. Call forTransaction with the callback's entity manager. The queue sends its SQL through that manager, so the job follows the surrounding commit or rollback.

await dataSource.transaction(async (manager) => {
  const account = await manager.save(Account, { email });
  await workhorse.forTransaction(manager).enqueue("account.created", {
    accountId: account.id,
  });
});

If the callback throws, TypeORM rolls back both writes. Nothing outside the transaction ever sees a half-committed pair. The adapter's default queue remains outside that transaction — use it for enqueues that stand alone.

Enable notification-assisted workers

TypeORM does not expose its internal node-postgres pool as a stable API. Supply a separate caller-owned pool as notificationPool when workers should reserve a connection for LISTEN/NOTIFY and pick up new jobs immediately. Without it, workers use bounded polling and keep the same durable behavior — jobs still run, just on the polling cadence.

Database execution failures become TypeOrmQueryError, which retains the statement, original cause, and PostgreSQL driver code. Typed Workhorse conflicts — idempotency, checkpoint, and wait errors — remain their core error classes.

Keep resource ownership explicit

Unless you supply close, the adapter leaves the data source initialized. Add that hook only when the adapter owns the data source, because adapter.close may run during process shutdown or failed startup.

Install the Workhorse schema with the core deployment tools. TypeORM migrations should not manage Workhorse's versioned SQL functions — the schema is a versioned protocol, not application entities.

Next


Exact adapter boundary and transaction ownership: architecture reference.