page.tsx246 lines · main
1import { DocsShell } from '../../components/shell';
2
3export const metadata = {
4 title: 'migration',
5};
6
7export default function MigrationPage() {
8 return (
9 <DocsShell>
10 <h1 className="font-mono text-2xl tracking-tight">migration</h1>
11 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
12 moving from convex, supabase, raw postgres, prisma, drizzle, or firebase to briven.
13 </p>
14
15 <div className="mt-6 rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 font-mono text-xs text-[var(--color-text-muted)]">
16 every sentence on this page is proven by a real migration. the per-source detail (§{' '}
17 <em>convex</em>, § <em>supabase</em>, …) lands incrementally as each first migration
18 clears. if a step on your source isn&apos;t covered yet, ping us — the missing part is
19 a known gap, not a deliberate omission.
20 </div>
21
22 <h2 className="mt-12 font-mono text-xl tracking-tight">five principles</h2>
23 <ol className="mt-4 flex flex-col gap-3 font-mono text-sm text-[var(--color-text-muted)]">
24 <Principle n={1} title="read before write">
25 read this entire page once before running any command. migrations that go sideways
26 almost always do so because someone skipped this step.
27 </Principle>
28 <Principle n={2} title="parallel-run, don't switch">
29 for at least 48 hours, the old system and briven run side-by-side, on the same
30 data, serving the same traffic. no cutover before the parallel-run window.
31 </Principle>
32 <Principle n={3} title="back up twice">
33 two independent backups to two independent destinations before you touch anything.
34 verify both before proceeding.
35 </Principle>
36 <Principle n={4} title="schema first, data second, functions third, traffic last">
37 in that order, always. inverting the order leaves windows where something is
38 half-migrated and a write goes to the wrong place.
39 </Principle>
40 <Principle n={5} title="one product at a time">
41 never migrate two things in parallel. the cognitive load of one migration is enough.
42 </Principle>
43 </ol>
44
45 <h2 className="mt-12 font-mono text-xl tracking-tight">the ten-step playbook</h2>
46 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
47 every migration follows these ten steps in order. specific commands vary per source —
48 per-source detail pages cover those.
49 </p>
50 <ol className="mt-6 flex flex-col gap-4 font-mono text-sm text-[var(--color-text-muted)]">
51 <Step
52 n={1}
53 title="inventory the source"
54 body="list every table, view, function, trigger, extension, env var, and external service the source project depends on. count rows per table. document the auth model in plain language. write it all into a migration-inventory.md so later steps don't surprise you."
55 />
56 <Step
57 n={2}
58 title="set up the briven project"
59 body="install @briven/cli, then briven setup --name my-app for a NEW project, or briven connect p_… for an EXISTING one. setup never attaches; connect never creates. optional: setup --template for starter files only."
60 />
61 <Step
62 n={3}
63 title="back up the source twice"
64 body="non-negotiable. two backups, two destinations, both restored to a temp database and row-counted to verify. don't proceed to step 4 until both verify."
65 />
66 <Step
67 n={4}
68 title="port the schema"
69 body="translate the source schema into briven/schema.ts using the briven schema dsl. table-by-table — don't try to do it in one pass. preserve foreign-key relationships and indexes."
70 />
71 <Step
72 n={5}
73 title="port the functions"
74 body="every server-side function (convex query/mutation, supabase edge function, prisma RPC) becomes a file under briven/functions/. wrap each with query() or mutation() from @briven/cli/server."
75 />
76 <Step
77 n={6}
78 title="set up env vars"
79 body="briven env set <key> <value> for every secret the source project uses. encrypted at rest with the platform key. the runtime injects them into ctx.env at cold start."
80 />
81 <Step
82 n={7}
83 title="copy the data"
84 body="pg_dump from the source, pg_restore into the briven data plane via the project's dsn (briven db shell-token issues a short-lived dsn). row-count every table — it must match step 1."
85 />
86 <Step
87 n={8}
88 title="parallel-run for 48 hours"
89 body="point a fraction of read traffic at briven, keep writes on the source. observe error rates, p50/p99 latency, and any function failures. divergence here is the time to surface migration bugs — not after the cutover."
90 />
91 <Step
92 n={9}
93 title="cut over writes"
94 body="flip the dns or the client config. writes now go to briven; the source becomes read-only. keep the source running for at least another 7 days as a rollback target."
95 />
96 <Step
97 n={10}
98 title="decommission"
99 body="after 7 days of green metrics, archive the source database to cold storage and tear down the running source. keep the archive for 90 days minimum."
100 />
101 </ol>
102
103 <h2 className="mt-12 font-mono text-xl tracking-tight">per-source guides</h2>
104 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
105 each source has its own path through the ten steps. these expand as the first
106 migration of each kind clears.
107 </p>
108 <ul className="mt-6 flex flex-col gap-2 font-mono text-sm">
109 <SourceItem
110 name="convex"
111 status="documented"
112 href="/migration/convex"
113 summary="union-of-literal fields → text() with app-level validation; v.id() → text().references(); _creationTime → explicit created_at."
114 />
115 <SourceItem
116 name="supabase"
117 status="documented"
118 href="/migration/supabase"
119 summary="row-level-security policies don't carry over — express them as guards in function code. edge functions port 1:1; storage cps to MinIO."
120 />
121 <SourceItem
122 name="raw postgres"
123 status="documented · straightest path"
124 href="/migration/postgres"
125 summary="schema.sql → briven/schema.ts via the dsl, pg_dump | pg_restore against the briven dsn, port handlers into briven/functions/."
126 />
127 <SourceItem
128 name="drizzle"
129 status="documented"
130 href="/migration/drizzle"
131 summary="schema.ts ports almost 1:1 (drizzle and briven both target postgres with TS-first schema definitions). swap the imports + adapt the column-builder calls; data carries via pg_dump."
132 />
133 <SourceItem
134 name="prisma"
135 status="documented"
136 href="/migration/prisma"
137 summary="schema.prisma → briven/schema.ts via the dsl (we map the field decorators to briven helpers); pg_dump | pg_restore for data; PrismaClient calls become ctx.db chains."
138 />
139 <SourceItem
140 name="firebase / firestore"
141 status="documented · hardest path"
142 href="/migration/firebase"
143 summary="document model → relational model is a manual remap. plan for an extended parallel-run window (2+ weeks) to catch shape mismatches."
144 />
145 <SourceItem
146 name="mongodb"
147 status="documented"
148 href="/migration/mongodb"
149 summary="collection → table with deliberate jsonb vs flatten decisions per embedded doc; ObjectId → text + ulid for new ids; mongoexport → custom transform → COPY for the data move."
150 />
151 <SourceItem
152 name="hasura"
153 status="documented"
154 href="/migration/hasura"
155 summary="postgres half ports for free; the work is the permissions port — every (role, table, action) triple from hasura metadata becomes a guard in function code."
156 />
157 <SourceItem
158 name="nextauth / auth.js"
159 status="documented"
160 href="/migration/nextauth"
161 summary="schema maps 1:1 (both target Better Auth's shape); provider port is trivial; the work is replacing getServerSession + useSession callsites and choosing preserve-ids vs preserve-sessions cutover."
162 />
163 </ul>
164
165 <h2 className="mt-12 font-mono text-xl tracking-tight">when not to use this</h2>
166 <ul className="mt-4 list-inside list-disc font-mono text-sm text-[var(--color-text-muted)]">
167 <li>moving between briven projects — wait for briven export / import (private beta)</li>
168 <li>moving a briven project between regions — file a support ticket</li>
169 <li>migrating only data without schema changes — use pg_dump / pg_restore directly</li>
170 </ul>
171 </DocsShell>
172 );
173}
174
175function Principle({
176 n,
177 title,
178 children,
179}: {
180 n: number;
181 title: string;
182 children: React.ReactNode;
183}) {
184 return (
185 <li className="flex gap-4">
186 <span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-[var(--color-border)] text-xs">
187 {n}
188 </span>
189 <div>
190 <p className="font-mono text-[var(--color-text)]">{title}</p>
191 <p className="mt-1">{children}</p>
192 </div>
193 </li>
194 );
195}
196
197function Step({ n, title, body }: { n: number; title: string; body: string }) {
198 return (
199 <li className="flex gap-4">
200 <span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-[var(--color-primary)] text-[var(--color-text-inverse)]">
201 {n}
202 </span>
203 <div>
204 <p className="font-mono text-[var(--color-text)]">{title}</p>
205 <p className="mt-1">{body}</p>
206 </div>
207 </li>
208 );
209}
210
211function SourceItem({
212 name,
213 status,
214 summary,
215 href,
216}: {
217 name: string;
218 status: string;
219 summary: string;
220 href?: string;
221}) {
222 const body = (
223 <>
224 <p className="font-mono text-[var(--color-text)]">{name}</p>
225 <p className="mt-1 font-mono text-xs text-[var(--color-text-subtle)]">{status}</p>
226 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">{summary}</p>
227 </>
228 );
229 if (href) {
230 return (
231 <li>
232 <a
233 href={href}
234 className="block rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 transition hover:border-[var(--color-border)]"
235 >
236 {body}
237 </a>
238 </li>
239 );
240 }
241 return (
242 <li className="rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4">
243 {body}
244 </li>
245 );
246}