Workhorse
Integrations

Prisma

Adapt a Prisma client and enqueue jobs inside caller-owned interactive transactions.

Your application already writes through Prisma. When a request creates an account 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-prisma closes that gap: it converts Prisma clients and interactive transaction clients into the core Queryable protocol, so Queue.enqueue runs inside the same transaction as your Prisma writes.

Workhorse keeps its schema lifecycle outside Prisma migrations. Prisma owns your models; Workhorse owns its versioned SQL functions.

Create the adapter

Install the integration beside Prisma Client.

pnpm add @stablemates/workhorse-prisma @prisma/client

Pass the client to createPrismaAdapter. The close callback is optional because the adapter does not disconnect caller-owned clients by default.

import { PrismaClient } from "@prisma/client";
import { createPrismaAdapter } from "@stablemates/workhorse-prisma";

const prisma = new PrismaClient();
const workhorse = createPrismaAdapter(prisma, {
  close: () => prisma.$disconnect(),
});

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 an interactive transaction

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

await prisma.$transaction(async (tx) => {
  const account = await tx.account.create({ data: { email } });
  await workhorse.forTransaction(tx).enqueue("account.created", {
    accountId: account.id,
  });
});

If the callback throws, Prisma 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

Prisma does not expose a stable node-postgres pool. 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 PrismaQueryError, which retains the statement, original cause, and nested PostgreSQL code when Prisma provides one. 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 Prisma client connected. Add that hook only when the adapter owns the client, because adapter.close may run during process shutdown or failed startup.

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

Next


Exact adapter boundary and transaction ownership: architecture reference.