schema dsl
declare your project's postgres schema in typescript. briven deploy diffs the file against the currently-deployed schema, generates a migration, and applies it transactionally on the data plane.
two paths
briven gives you two ways to build your database — they hit the same postgres schema and you can mix them freely:
- cli + git — the canonical path. write
briven/schema.tsbelow, commit it,briven deploy. migrations are diff-based and transactional. best for production work and code review. - dashboard / studio — open
/dashboard/projects/<p>/studio, click + new table, pick column types, set primary keys / foreign keys / indexes. best for prototyping and one-off changes. studio has a copy as schema.ts button that emits the equivalent of this file from a live database, so anything you build by clicking can graduate to git later.
hello world
A schema lives at briven/schema.ts and exports a default value built with the schema() helper. each table is constructed with table(), and columns are declared with the typed builders.
import { bigint, schema, table, text } from '@briven/cli/schema';
export default schema({
users: table({
columns: {
id: text().primaryKey(),
email: text().notNull(),
createdAt: bigint().notNull(),
},
indexes: [{ columns: ['email'], unique: true }],
}),
});column types
imported from @briven/cli/schema:
text()— variable-length utf-8. the default for strings.varchar(n)— bounded text. n is required.integer()— int4, the default for whole numbers.bigint()— int8. use for timestamps-as-millis-since-epoch and money in cents.boolean()timestamp()— timestamptz. accepts a default like.default('now()').uuid()— pg uuid. considertext()if you also use ulids.jsonb()— typed via the column generic:jsonb<{ enabled: boolean }>().vector(n)— pgvector embedding column. n is the dimension.
constraints
chained on a column builder:
.primaryKey()— at most one column per table; implies notNull + unique..notNull().unique()— single-column unique. for multi-column useindexes..default(value)— a literal or postgres expression. quoted strings need the inner quotes (.default("'EUR'")renders asDEFAULT 'EUR')..references(table, column, opts?)— foreign key.opts.onDeleteaccepts'cascade'|'set null'|'restrict'|'no action'.
indexes
declared on the table, not on individual columns. multi-column indexes (compound or unique) live here:
uins: table({
columns: {
id: text().primaryKey(),
uin: bigint().notNull(),
ownerId: text().references('users', 'id'),
status: text().notNull(),
},
indexes: [
{ columns: ['uin'], unique: true },
{ columns: ['status'] },
{ columns: ['ownerId', 'status'] },
],
}),diff + apply
briven deploy walks the schema, compares it to the previous snapshot, and emits one of these change kinds: create_table, drop_table, add_column, drop_column. drops are refused unless you pass --confirm-destructive; the cli also prints a pre-migration snapshot tag so you can roll back with pg_restore from the audit trail if something goes wrong.
on apply, every table gets an implicit NOTIFYtrigger that fires on insert/update/delete — that's what powers the realtime useQuery story on the client side.
reserved names
briven prefixes its own platform tables with _briven_ on every project schema. you cannot define a table whose name starts with _briven_.