page.tsx246 lines · main
| 1 | import { DocsShell } from '../../components/shell'; |
| 2 | |
| 3 | export const metadata = { |
| 4 | title: 'migration', |
| 5 | }; |
| 6 | |
| 7 | export 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'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 | |
| 175 | function 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 | |
| 197 | function 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 | |
| 211 | function 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 | } |