Prisma Adapter
@rudderjs/orm-prisma is the Prisma-backed adapter for @rudderjs/orm. It wraps a PrismaClient, registers itself with ModelRegistry, and binds the raw client into the DI container so other packages (@rudderjs/auth, @rudderjs/notification) can consume it without further wiring.
Install
pnpm add @rudderjs/orm @rudderjs/orm-prisma @prisma/client
pnpm add -D prisma::: tip Prisma 7+ — pick a generator Prisma 7 ships two client generators. Rudder supports both, but they're not interchangeable in browser-sandboxed environments:
| Generator | Schema declaration | When to use |
|---|---|---|
prisma-client-js (legacy default) |
provider = "prisma-client-js" |
Standard Node.js apps. The framework imports PrismaClient from @prisma/client automatically. |
prisma-client (Prisma 7+) |
provider = "prisma-client" + custom output |
Self-contained ESM client, no engine binaries downloaded by prisma generate. Required for WebContainer / StackBlitz / Bolt.new — the legacy generator's postinstall fails because binaries.prisma.sh doesn't ship CORS headers. |
The new-generator setup is documented under Browser-sandboxed runtimes (WebContainer) below. :::
For SQLite (local development) also install:
pnpm add better-sqlite3 @prisma/adapter-better-sqlite3
pnpm add -D @types/better-sqlite3| Driver | Required packages | Notes |
|---|---|---|
sqlite |
better-sqlite3 |
Local file-based database; default for development |
postgresql |
pg or native bindings |
Standard PostgreSQL |
mysql |
mysql2 |
MySQL / MariaDB |
libsql |
@libsql/client |
Turso / libSQL — SQLite-compatible schema |
The driver defaults to 'sqlite'. Set it explicitly to 'postgresql', 'mysql', or 'libsql' for those databases — there is no DATABASE_URL-scheme auto-detection.
MySQL uses @prisma/adapter-mariadb under the hood (wire-compatible with both MySQL 5.7+ and MariaDB 10.x), so a single 'mysql' driver value covers both engines. Install the adapter once: pnpm add mariadb @prisma/adapter-mariadb. Available in @rudderjs/orm-prisma 1.8.0+.
Multi-file schema
Rudder uses Prisma's multi-file schema feature: instead of one prisma/schema.prisma, schemas live in prisma/schema/*.prisma. Each concern gets its own file, and packages can publish their own schema shards.
prisma/
├── schema/
│ ├── base.prisma # generator + datasource
│ ├── user.prisma # User model
│ ├── auth.prisma # published by @rudderjs/auth
│ └── notification.prisma # published by @rudderjs/notification
└── prisma.config.ts # points to prisma/schema/// prisma.config.ts
import path from 'node:path'
import { defineConfig } from 'prisma/config'
export default defineConfig({
earlyAccess: true,
schema: path.join(__dirname, 'prisma', 'schema'),
})// prisma/schema/base.prisma
generator client {
provider = "prisma-client-js"
previewFeatures = ["prismaSchemaFolder"]
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}// prisma/schema/user.prisma
model User {
id String @id @default(cuid())
name String
email String @unique
role String @default("user")
createdAt DateTime @default(now())
}After editing the schema:
pnpm rudder db:push # sync to database
pnpm rudder db:generate # regenerate the Prisma clientRegister the provider
DatabaseProvider handles connection, ModelRegistry.set(), and DI binding:
// bootstrap/providers.ts
import { DatabaseProvider } from '@rudderjs/orm-prisma'
export default [
DatabaseProvider, // first
// ...other providers
]DatabaseProvider binds the raw PrismaClient to DI as 'prisma' so other packages (@rudderjs/auth, @rudderjs/notification) can auto-discover it. Auto-discovery picks up DatabaseProvider automatically when @rudderjs/orm-prisma is installed — the explicit import is only needed when you skip auto-discovery.
The User model
import { Model } from '@rudderjs/orm'
export class User extends Model {
static table = 'user' // matches the Prisma accessor — prismaClient.user
id!: string
name!: string
email!: string
role!: string
createdAt!: Date
}static table is the Prisma accessor, not the SQL table name:
| Prisma model | Accessor (static table) |
|---|---|
model User |
'user' |
model BlogPost |
'blogPost' |
model Customer @@map("customers") |
'customer' (the model name, not the @@map value) |
Cross-repo client (advanced)
For cross-repo workspaces where @prisma/client is generated in a different package, pass the pre-built client through config/database.ts via the PrismaClient field — the adapter picks it up from there:
// config/database.ts
import { Env } from '@rudderjs/support'
import { PrismaClient } from '@my-org/database'
export default {
default: 'sqlite',
PrismaClient, // forwarded to the adapter
connections: {
sqlite: {
driver: 'sqlite' as const,
url: Env.get('DATABASE_URL', 'file:./dev.db'),
},
},
}This is the path the Pilotiq playgrounds use to consume their own generated client across pnpm-linked workspaces.
Browser-sandboxed runtimes (WebContainer)
To boot a Rudder app inside StackBlitz / Bolt.new / any WebContainer-backed environment, switch to Prisma 7's new prisma-client generator. The legacy prisma-client-js generator is incompatible with WebContainer because its prisma generate step downloads the schema-engine binary from https://binaries.prisma.sh, which doesn't ship CORS headers — the install hangs with socket hang up.
The new generator emits a self-contained ESM client with no engine binary downloads at install time:
// prisma/schema/base.prisma
generator client {
provider = "prisma-client"
output = "../generated/prisma" // relative to schema file → prisma/generated/prisma/
runtime = "nodejs"
moduleFormat = "esm"
}
datasource db {
provider = "sqlite"
}Pass the generated PrismaClient class via the database config (the framework can't find it at @prisma/client since the new generator emits to a custom path):
// config/database.ts
import { Env } from '@rudderjs/support'
import { PrismaClient } from '../prisma/generated/prisma/client.js'
export default {
default: 'libsql',
PrismaClient,
connections: {
libsql: {
driver: 'libsql' as const,
url: Env.get('DATABASE_URL', 'file:./prisma/dev.db'),
},
},
}// package.json
{
"scripts": {
"postinstall": "prisma generate"
}
}Add prisma/generated/ to .gitignore — the path is reproducible from the schema and regenerated on every pnpm install.
Pair the new generator with a JS driver adapter (@prisma/adapter-libsql + @libsql/client) so SQL execution stays in pure JS. The Rust query engine is never loaded; the WASM query compiler shipped in @prisma/client/runtime/client handles query planning at request time.
For the canonical reference, see the playground-web/ variant in the framework repo — a sibling of playground/ that runs end-to-end inside StackBlitz.
Upgrading to @rudderjs/orm-prisma@2.0
The 2.0 release changes the precedence semantics of mixed .where() + .orWhere() chains. The 1.x adapter constrained every .orWhere() alternative by the prior AND chain — opposite of Eloquent and out of step with @rudderjs/orm-drizzle. The 2.0 adapter emits the Laravel-parity shape, so the same chain now means what it syntactically looks like:
Post.where('a').where('b').orWhere('c').get()
// 1.x on Prisma: WHERE a AND b AND c ← constrained, often 0 rows
// 2.0 on Prisma: WHERE (a AND b) OR c ← Laravel parityThis affects both regular queries and vector-search queries built with whereVectorSimilarTo(). AND-only chains and OR-only chains are unaffected — only mixed AND+OR queries change.
If you rely on the 1.x behaviour for a specific query, restructure it with whereGroup:
// 2.0 equivalent of the 1.x constrained semantics:
Post.where('a').where('b').whereGroup(g => g.orWhere('c')).get()There is no compatibility flag — the long-deprecation strategy is the major-version bump itself. Apps still on @rudderjs/orm-prisma@1.x keep the legacy behaviour until they upgrade. See Precedence in mixed where + orWhere chains for the full semantics + edge cases (soft-delete interaction, nested whereGroup).
Pitfalls
Prisma has no delegate for table "x". You setstatic tableto the SQL table name (e.g.'oauth_clients') instead of the accessor ('oAuthClient'). Use the accessor.- Stale client after schema edit. Run
pnpm rudder db:generate. TypeScript types in your app go stale until the client is regenerated. db:pushin production. Push can drop columns silently. Usepnpm rudder migratefor tracked migrations — it delegates toprisma migrate deployunderNODE_ENV=production, andprisma migrate devotherwise.- Multi-file schema not detected. Confirm
previewFeatures = ["prismaSchemaFolder"]is in your generator block, and thatprisma.config.tspoints to the directory.