drizzle → briven
port a drizzle-orm + postgres project onto briven. follow the ten-step playbook on /migration — this page covers only the drizzle-specific parts.
db.select(...)chains with briven's ctx.db(...) chains (same query-builder shape).schema port — direct mappings
drizzle column helpers map to briven helpers as follows:
// drizzle/schema.ts
import { pgTable, text, boolean, timestamp, integer, jsonb } from 'drizzle-orm/pg-core';
export const posts = pgTable('posts', {
id: text('id').primaryKey(),
authorId: text('author_id').notNull().references(() => users.id),
title: text('title').notNull(),
published: boolean('published').notNull().default(false),
views: integer('views').notNull().default(0),
metadata: jsonb('metadata').$type<{ tags: string[] }>(),
createdAt: timestamp('created_at').notNull().defaultNow(),
});
// 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(),
published: boolean().notNull().default('false'),
views: bigint().notNull().default('0'),
metadata: jsonb<{ tags: string[] }>().nullable(),
createdAt: timestamp().notNull().default('now()'),
},
}),
});- drizzle
integer()→ brivenbigint()(briven defaults to int8 for numeric counters to head off overflow). - drizzle
jsonb<T>().$type<T>()→ brivenjsonb<T>(). type assertion lives on the column builder in both. - drizzle
.defaultNow()→ briven.default('now()')(we accept a string-literal sql default;now()is recognised verbatim). - drizzle
.references(() => users.id)→ briven.references('users', 'id'). drizzle's closure form preserves circular-ref ordering; briven resolves by name so the order doesn't matter. - drizzle uses
snake_casecolumn names in the second arg; briven derives the sql name from the property name (camelCase → snake_case) so you can drop the extra arg.
indexes
drizzle defines indexes via the third tuple arg on pgTable. briven uses an inline indexes array on the table def.
// drizzle
export const posts = pgTable('posts', { /* columns */ }, (t) => ({
authorIdx: index('posts_author_idx').on(t.authorId),
publishedAuthorIdx: index().on(t.published, t.authorId),
}));
// briven
posts: table({
columns: { /* ... */ },
indexes: [
{ columns: ['authorId'], unique: false },
{ columns: ['published', 'authorId'], unique: false },
],
});drizzle's named indexes don't round-trip — briven generates names from (table, columns) so renaming a column auto-renames the index too. if a drizzle index was named for a specific reason (e.g. partial indexes via raw sql), open an issue; partial indexes are a known gap.
data export from drizzle's postgres
drizzle is just a query builder on top of postgres, so the export is the same as the raw-postgres playbook:
pg_dump --format=custom --no-owner --no-privileges \
"$DRIZZLE_DATABASE_URL" > drizzle-dump-$(date +%Y%m%d).dump
pg_restore --no-owner --no-privileges \
-d "$BRIVEN_PROJECT_DSN" drizzle-dump-$(date +%Y%m%d).dumpbriven's migration applies a clean schema first; pg_restorewrites the data into the same tables. if the drizzle source had columns briven's dsl can't model yet (e.g. partial indexes, enum types, custom types), restore will warn — fix at the data level rather than retroactively changing the briven schema.
functions port — query builder is nearly identical
drizzle db and briven ctx.db share the postgres-query-builder shape (both lean on knex semantics). the port is a search/replace on the import + the handle name.
// before — drizzle handler
import { db } from './db';
import { posts, users } from './schema';
import { and, eq, desc } from 'drizzle-orm';
export async function recentPostsByAuthor(authorId: string, limit = 50) {
return db
.select({ id: posts.id, title: posts.title, createdAt: posts.createdAt })
.from(posts)
.where(and(eq(posts.authorId, authorId), eq(posts.published, true)))
.orderBy(desc(posts.createdAt))
.limit(limit);
}
// 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));
});- drizzle
db.select({a, b})→ brivenctx.db('table').select(['a', 'b']). - drizzle's
and / eq / descoperators → briven uses object-literal where clauses + string-keyedorderBy(knex-style). drizzle's richer operators (e.g.ilike,arrayContains) land on briven via raw fragments — see /functions. - drizzle
insert / update / delete→ brivenctx.db(table).insert / update / delete— same shape.
auth port
drizzle ships no auth — you're running it next to better-auth, lucia, next-auth, or a hand-rolled session table. briven ships better-auth integrated; map your existing user / session columns into briven's control-plane shape via the nextauth → briven guide (the same column-mapping applies whichever lib generated the rows).
reactivity (new capability)
drizzle queries are one-shot. once on briven, wrap a read as a query() and the same call from @briven/react's useQuery auto-refetches on table-level NOTIFYs. no extra code on the function side.