Aviary
State stores

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-prisma

1. 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:

schema.prisma
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-tables

PrismaStateStore takes your PrismaClient (or a Nest PrismaService that extends it):

app.module.ts
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.

On this page