page.tsx181 lines · main
1import { DocsShell } from '../../components/shell';
2
3export const metadata = { title: 'schema dsl' };
4
5export default function SchemaPage() {
6 return (
7 <DocsShell>
8 <h1 className="font-mono text-2xl tracking-tight">schema dsl</h1>
9 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
10 declare your project&apos;s postgres schema in typescript. <code>briven deploy</code> diffs
11 the file against the currently-deployed schema, generates a migration, and applies it
12 transactionally on the data plane.
13 </p>
14
15 <Section title="two paths">
16 <p>
17 briven gives you two ways to build your database — they hit the same postgres schema
18 and you can mix them freely:
19 </p>
20 <ul className="list-disc pl-5">
21 <li>
22 <strong>cli + git</strong> — the canonical path. write <code>briven/schema.ts</code>{' '}
23 below, commit it, <code>briven deploy</code>. migrations are diff-based and
24 transactional. best for production work and code review.
25 </li>
26 <li>
27 <strong>dashboard / studio</strong> — open <code>/dashboard/projects/&lt;p&gt;/studio</code>,
28 click <em>+ new table</em>, pick column types, set primary keys / foreign keys /
29 indexes. best for prototyping and one-off changes. studio has a{' '}
30 <em>copy as schema.ts</em> button that emits the equivalent of this file from a
31 live database, so anything you build by clicking can graduate to git later.
32 </li>
33 </ul>
34 </Section>
35
36 <Section title="hello world">
37 <p>
38 A schema lives at <code>briven/schema.ts</code> and exports a default value built with
39 the <code>schema()</code> helper. each table is constructed with <code>table()</code>,
40 and columns are declared with the typed builders.
41 </p>
42 <Snippet>{`import { bigint, schema, table, text } from '@briven/cli/schema';
43
44export default schema({
45 users: table({
46 columns: {
47 id: text().primaryKey(),
48 email: text().notNull(),
49 createdAt: bigint().notNull(),
50 },
51 indexes: [{ columns: ['email'], unique: true }],
52 }),
53});`}</Snippet>
54 </Section>
55
56 <Section title="column types">
57 <p>imported from <code>@briven/cli/schema</code>:</p>
58 <ul className="list-disc pl-5">
59 <li>
60 <code>text()</code> — variable-length utf-8. the default for strings.
61 </li>
62 <li>
63 <code>varchar(n)</code> — bounded text. n is required.
64 </li>
65 <li>
66 <code>integer()</code> — int4, the default for whole numbers.
67 </li>
68 <li>
69 <code>bigint()</code> — int8. use for timestamps-as-millis-since-epoch and money in
70 cents.
71 </li>
72 <li>
73 <code>boolean()</code>
74 </li>
75 <li>
76 <code>timestamp()</code> — timestamptz. accepts a default like{' '}
77 <code>.default(&apos;now()&apos;)</code>.
78 </li>
79 <li>
80 <code>uuid()</code> — pg uuid. consider <code>text()</code> if you also use ulids.
81 </li>
82 <li>
83 <code>jsonb()</code> — typed via the column generic:{' '}
84 <code>jsonb&lt;{`{ enabled: boolean }`}&gt;()</code>.
85 </li>
86 <li>
87 <code>vector(n)</code> — pgvector embedding column. n is the dimension.
88 </li>
89 </ul>
90 </Section>
91
92 <Section title="constraints">
93 <p>chained on a column builder:</p>
94 <ul className="list-disc pl-5">
95 <li>
96 <code>.primaryKey()</code> — at most one column per table; implies notNull + unique.
97 </li>
98 <li>
99 <code>.notNull()</code>
100 </li>
101 <li>
102 <code>.unique()</code> — single-column unique. for multi-column use{' '}
103 <code>indexes</code>.
104 </li>
105 <li>
106 <code>.default(value)</code> — a literal or postgres expression. quoted strings need
107 the inner quotes (<code>.default(&quot;&apos;EUR&apos;&quot;)</code> renders as{' '}
108 <code>DEFAULT &apos;EUR&apos;</code>).
109 </li>
110 <li>
111 <code>.references(table, column, opts?)</code> — foreign key. <code>opts.onDelete</code>{' '}
112 accepts <code>&apos;cascade&apos;</code> | <code>&apos;set null&apos;</code> |{' '}
113 <code>&apos;restrict&apos;</code> | <code>&apos;no action&apos;</code>.
114 </li>
115 </ul>
116 </Section>
117
118 <Section title="indexes">
119 <p>
120 declared on the table, not on individual columns. multi-column indexes (compound or
121 unique) live here:
122 </p>
123 <Snippet>{`uins: table({
124 columns: {
125 id: text().primaryKey(),
126 uin: bigint().notNull(),
127 ownerId: text().references('users', 'id'),
128 status: text().notNull(),
129 },
130 indexes: [
131 { columns: ['uin'], unique: true },
132 { columns: ['status'] },
133 { columns: ['ownerId', 'status'] },
134 ],
135}),`}</Snippet>
136 </Section>
137
138 <Section title="diff + apply">
139 <p>
140 <code>briven deploy</code> walks the schema, compares it to the previous snapshot, and
141 emits one of these change kinds: <code>create_table</code>, <code>drop_table</code>,{' '}
142 <code>add_column</code>, <code>drop_column</code>. drops are refused unless you pass{' '}
143 <code>--confirm-destructive</code>; the cli also prints a pre-migration snapshot tag so
144 you can roll back with <code>pg_restore</code> from the audit trail if something goes
145 wrong.
146 </p>
147 <p>
148 on apply, every table gets an implicit <code>NOTIFY</code> trigger that fires on
149 insert/update/delete — that&apos;s what powers the realtime <code>useQuery</code> story
150 on the client side.
151 </p>
152 </Section>
153
154 <Section title="reserved names">
155 <p>
156 briven prefixes its own platform tables with <code>_briven_</code> on every project
157 schema. you cannot define a table whose name starts with <code>_briven_</code>.
158 </p>
159 </Section>
160 </DocsShell>
161 );
162}
163
164function Section({ title, children }: { title: string; children: React.ReactNode }) {
165 return (
166 <section className="mt-10">
167 <h2 className="font-mono text-lg">{title}</h2>
168 <div className="mt-2 space-y-3 font-mono text-sm text-[var(--color-text-muted)]">
169 {children}
170 </div>
171 </section>
172 );
173}
174
175function Snippet({ children }: { children: string }) {
176 return (
177 <pre className="overflow-x-auto rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 font-mono text-xs">
178 <code>{children}</code>
179 </pre>
180 );
181}