Prisma
The Prisma StateStore adapter. Add the durable models to your schema.prisma, prisma generate, and pass your PrismaClient to PrismaStateStore. Schema is owned by Prisma Migrate — there is no auto-schema.
@dudousxd/nestjs-durable-store-prisma persists runs and step checkpoints through Prisma. Prisma
generates a client per schema, so the adapter can't ship a concrete client — instead you add its
models to your schema.prisma, generate, and hand the adapter your PrismaClient.
pnpm add @dudousxd/nestjs-durable-store-prisma1. Add the durable models
Copy the durable models from the package's prisma/schema.prisma into your own schema. They map to
the durable_* tables and only ever reference each other:
model DurableWorkflowRun {
id String @id
workflow String
workflowVersion String @map("workflow_version")
status String
input Json?
output Json?
error Json?
wakeAt BigInt? @map("wake_at")
lockedBy String? @map("locked_by")
lockedUntil DateTime? @map("locked_until")
awaitingDecisionTaskId String? @map("awaiting_decision_task_id")
recoveryAttempts Int? @map("recovery_attempts")
tags Json?
searchAttributes Json? @map("search_attributes")
priority Int?
createdAt DateTime @map("created_at")
updatedAt DateTime @map("updated_at")
attributes DurableRunAttribute[]
@@index([status, wakeAt])
@@index([workflow, status])
@@map("durable_workflow_runs")
}
model DurableRunAttribute {
runId String @map("run_id")
key String
strValue String? @map("str_value")
numValue Float? @map("num_value")
run DurableWorkflowRun @relation(fields: [runId], references: [id], onDelete: Cascade)
@@id([runId, key])
@@index([key, numValue])
@@index([key, strValue])
@@map("durable_run_attributes")
}
model DurableStepCheckpoint {
runId String @map("run_id")
seq Int
name String
kind String
stepId String @map("step_id")
status String
input Json?
output Json?
error Json?
events Json?
attempts Int
workerGroup String? @map("worker_group")
parallelGroup String? @map("parallel_group")
wakeAt BigInt? @map("wake_at")
enqueuedAt DateTime? @map("enqueued_at")
startedAt DateTime @map("started_at")
finishedAt DateTime @map("finished_at")
@@id([runId, seq])
@@map("durable_step_checkpoints")
}
model DurableSignalWaiter {
token String @id
runId String @map("run_id")
seq Int
parallelGroup String? @map("parallel_group")
@@map("durable_signal_waiters")
}
model DurableBufferedSignal {
id BigInt @id @default(autoincrement())
token String
payload Json?
@@index([token])
@@map("durable_buffered_signals")
}The DurableRunAttribute side-table is what lets search-attribute filters push down into SQL
(an EXISTS on (runId, key)) instead of scanning every run.
2. Generate and wire the store
npx prisma generate
npx prisma migrate dev --name add-durable-tablesPrismaStateStore takes your PrismaClient (or a Nest PrismaService that extends it):
import { PrismaStateStore } from '@dudousxd/nestjs-durable-store-prisma';
import { PrismaService } from './prisma.service';
DurableModule.forRootAsync({
inject: [PrismaService],
useFactory: (prisma: PrismaService) => ({
store: new PrismaStateStore(prisma),
transport,
}),
});Schema is owned by Prisma Migrate
Unlike the MikroORM and TypeORM adapters, this one has no auto-schema — Prisma already owns your
schema and migration history. The autoSchema option is a no-op here; the tables exist because
prisma migrate created them from the models above. Treat the models as the source of truth and
evolve them through Prisma like any other.
ctx.transaction needs Prisma's interactive transactions. The adapter's transaction() is
implemented with this.db.$transaction(async (tx) => …) — Prisma's interactive transaction API.
That's a hard requirement, not just the recommended path: it breaks under a Prisma configuration
that doesn't support interactive transactions in the same process, such as Accelerate or the
Data Proxy. If you're on one of those, ctx.transaction isn't usable through this adapter —
stick to ctx.step for that work.
Tag filtering isn't supported on SQLite. listRuns({ tag }) compiles to a Prisma
array_contains predicate against the tags JSON column, which Prisma's SQLite connector doesn't
support. It works on Postgres/MySQL. The store's own conformance suite reflects this: it disables
the tag-filter case for the Prisma + SQLite combination and only asserts it against a real
Postgres/MySQL database. If you're on SQLite with Prisma, avoid filtering listRuns by tag.
TypeORM
The TypeORM StateStore adapter. Register the durable entities on your DataSource, pass it to TypeOrmStateStore, and run on Postgres, MySQL/MariaDB or SQLite — with auto-schema on boot or an ensureTypeOrmDurableSchema helper for your own migration.
Drizzle
The Drizzle StateStore adapter for SQLite / libSQL (Turso, edge). Build your drizzle db with the package's schema and pass it to DrizzleStateStore. Schema is owned by drizzle-kit — no auto-schema.