page.tsx181 lines · main
| 1 | import { DocsShell } from '../../components/shell'; |
| 2 | |
| 3 | export const metadata = { title: 'schema dsl' }; |
| 4 | |
| 5 | export 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'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/<p>/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 | |
| 44 | export 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('now()')</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<{`{ enabled: boolean }`}>()</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("'EUR'")</code> renders as{' '} |
| 108 | <code>DEFAULT 'EUR'</code>). |
| 109 | </li> |
| 110 | <li> |
| 111 | <code>.references(table, column, opts?)</code> — foreign key. <code>opts.onDelete</code>{' '} |
| 112 | accepts <code>'cascade'</code> | <code>'set null'</code> |{' '} |
| 113 | <code>'restrict'</code> | <code>'no action'</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'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 | |
| 164 | function 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 | |
| 175 | function 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 | } |