prisma → briven
port a prisma + postgres project onto briven. follow the ten-step playbook on /migration — this page covers only the prisma-specific parts.
query() / mutation() is typed via its Argsinterface — and trade prisma client's ORM-level helpers for a thin postgres query builder. the schema port is mechanical; the functions port is the place to think.schema port — prisma DSL → briven DSL
prisma uses its own schema language (.prisma files). map decorators to briven column-builder calls:
// schema.prisma
model Post {
id String @id @default(cuid())
authorId String
title String
body String
published Boolean @default(false)
views Int @default(0)
metadata Json?
createdAt DateTime @default(now())
author User @relation(fields: [authorId], references: [id])
@@index([authorId])
@@index([published, authorId])
}
// briven/schema.ts
import { bigint, boolean, jsonb, schema, table, text, timestamp } from '@briven/cli/schema';
export default schema({
posts: table({
columns: {
id: text().primaryKey(),
authorId: text().notNull().references('users', 'id'),
title: text().notNull(),
body: text().notNull(),
published: boolean().notNull().default('false'),
views: bigint().notNull().default('0'),
metadata: jsonb<Record<string, unknown>>().nullable(),
createdAt: timestamp().notNull().default('now()'),
},
indexes: [
{ columns: ['authorId'], unique: false },
{ columns: ['published', 'authorId'], unique: false },
],
}),
});@id→.primaryKey(). prisma's@default(cuid())/@default(uuid())don't carry over — briven mints ids in function code viaulid('prefix')from@briven/shared. ULIDs sort lexicographically by creation time, which is usually what you want anyway.Int→bigint(). prisma mapsIntto int4 by default; briven defaults numeric columns to int8 to head off overflow. if you need int4 specifically, file an issue.Json?→jsonb<T>().nullable(). give the column a type arg so the function code gets typed reads.DateTime @default(now())→timestamp().notNull().default('now()').@relation(fields: […], references: […])→.references('table', 'column')on the fk column. briven doesn't generate the reverse-side accessor — query throughctx.dbon the related table directly.@@index([a, b])→ entry in the table'sindexesarray. partial / expression indexes (e.g.@@index([a], where: { … })) are a known gap.
enums
prisma enum declarations have no first-class briven equivalent today. two paths, depending on how strict you want the constraint:
- application-side — column stays
text(), the function code validates against a TypeScript union literal. less strict but flexible; matches how convex / nextauth migrations land. - database-side — apply the enum as a check-constraint via a raw-sql migration after
briven deploy. briven preserves untouched user objects on re-deploy, so the constraint survives.
data export from prisma's postgres
prisma is one of several clients pointing at postgres — the dump/restore is the same as the raw-postgres playbook:
pg_dump --format=custom --no-owner --no-privileges \
"$PRISMA_DATABASE_URL" > prisma-dump-$(date +%Y%m%d).dump
pg_restore --no-owner --no-privileges --data-only \
-d "$BRIVEN_PROJECT_DSN" prisma-dump-$(date +%Y%m%d).dumprun briven deploy first so the briven schema is in place, then restore --data-only. that keeps briven's id naming + index naming in sync with what the briven dsl declared, instead of inheriting prisma's names.
prisma's migration history table (_prisma_migrations) doesn't carry — briven tracks its own migrations in _briven_migrations. drop the prisma table after the cutover.
functions port — PrismaClient calls → ctx.db chains
prisma client is generated; briven's ctx.db is a thin knex-style builder. the port pattern is: replace prisma.model.op with the equivalent builder chain.
// before — prisma handler
import { prisma } from './db';
export async function recentPostsByAuthor(authorId: string, limit = 50) {
return prisma.post.findMany({
where: { authorId, published: true },
select: { id: true, title: true, createdAt: true },
orderBy: { createdAt: 'desc' },
take: Math.min(limit, 200),
});
}
// after — briven/functions/recentPostsByAuthor.ts
import { brivenError, query, type Ctx } from '@briven/cli/server';
interface Args { authorId: string; limit?: number }
export default query(async (ctx: Ctx, args: Args) => {
if (!args.authorId)
throw new brivenError('validation_failed', 'authorId required', { status: 400 });
return ctx
.db('posts')
.select(['id', 'title', 'createdAt'])
.where({ authorId: args.authorId, published: true })
.orderBy('createdAt', 'desc')
.limit(Math.min(args.limit ?? 50, 200));
});findMany/findFirst/findUnique→ builder chain ending in.first()for unique lookups.create/update/delete→insert / update / delete.upsertneeds an explicitonConflictin raw sql today (known gap; an upsert helper is queued).include/selectwith nested relations → no eager-join shortcut yet. either run two queries inside the same function (the function executes in one transaction, so the consistency is the same) or write a raw query for the joined shape.- prisma
$transaction→ not needed; everymutation()body runs in a single transaction by default.
auth port
prisma is unopinionated about auth — the user table is whatever you built. if it lines up with better-auth's columns (id, email, name, image, …) the nextauth → briven guide maps cleanly; if it's a custom shape, port the columns onto better-auth's expected shape before flipping traffic.
reactivity (new capability)
prisma queries are one-shot; the typical pattern is polling or websockets-on-the-side. on briven the same query() used over http auto-becomes reactive when consumed via @briven/react's useQuery. table-level NOTIFYs trigger re-runs — no code change needed.