page.tsx232 lines · main
| 1 | import { PmTabs } from '../../components/pm-tabs'; |
| 2 | import { DocsShell } from '../../components/shell'; |
| 3 | import { pmExec, pmInstall, pmJoin, pmPlain } from '../../lib/pm'; |
| 4 | |
| 5 | export const metadata = { |
| 6 | title: 'quickstart', |
| 7 | }; |
| 8 | |
| 9 | const SCAFFOLD = pmJoin( |
| 10 | pmPlain('$ mkdir my-app && cd my-app'), |
| 11 | pmExec('briven setup --name my-app'), |
| 12 | ); |
| 13 | |
| 14 | const INSTALL_CLIENT = pmInstall('@briven/react'); |
| 15 | |
| 16 | const DEPLOY = pmExec('briven deploy'); |
| 17 | |
| 18 | export default function QuickstartPage() { |
| 19 | return ( |
| 20 | <DocsShell> |
| 21 | <h1 className="font-mono text-2xl tracking-tight">quickstart</h1> |
| 22 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 23 | from nothing to a live reactive query in five minutes. |
| 24 | </p> |
| 25 | |
| 26 | <ol className="mt-8 flex flex-col gap-6 font-mono text-sm text-[var(--color-text-muted)]"> |
| 27 | <Step n={1} title="wire a cloud project"> |
| 28 | Install the CLI (or use <code>npx @briven/cli</code>). Then pick one path: |
| 29 | <ul className="mt-2 list-disc pl-5 space-y-1"> |
| 30 | <li> |
| 31 | <strong className="text-[var(--color-text)]">new project:</strong>{' '} |
| 32 | <code>briven setup --name my-app</code> (or interactive{' '} |
| 33 | <code>briven setup</code>) |
| 34 | </li> |
| 35 | <li> |
| 36 | <strong className="text-[var(--color-text)]">existing project:</strong>{' '} |
| 37 | <code>briven connect p_…</code> — never{' '} |
| 38 | <code>setup --project</code> |
| 39 | </li> |
| 40 | </ul> |
| 41 | Full detail:{' '} |
| 42 | <a className="underline" href="/connect"> |
| 43 | connect |
| 44 | </a> |
| 45 | . |
| 46 | <PmTabs commands={SCAFFOLD} /> |
| 47 | <div className="mt-3 rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-3 text-xs"> |
| 48 | <strong className="text-[var(--color-text)]">dashboard-only:</strong> sign in at{' '} |
| 49 | <a className="underline" href="https://briven.tech"> |
| 50 | briven.tech |
| 51 | </a> |
| 52 | , use studio, then <em>copy as schema.ts</em> when you want CLI + git. |
| 53 | </div> |
| 54 | <p className="mt-2 text-[var(--color-text-subtle)]"> |
| 55 | optional starter: <code>--template=todo-app</code> or <code>--template=chat</code>{' '} |
| 56 | (templates are not the product — just files). |
| 57 | </p> |
| 58 | </Step> |
| 59 | <Step n={2} title="edit the schema + function"> |
| 60 | <p> |
| 61 | Open <code>briven/schema.ts</code>: |
| 62 | </p> |
| 63 | <Snippet>{`import { schema, table, text, timestamp } from '@briven/schema'; |
| 64 | |
| 65 | export default schema({ |
| 66 | notes: table({ |
| 67 | columns: { |
| 68 | id: text().primaryKey(), |
| 69 | body: text().notNull(), |
| 70 | createdAt: timestamp().notNull().defaultNow(), |
| 71 | }, |
| 72 | indexes: [{ columns: ['createdAt'] }], |
| 73 | }), |
| 74 | });`}</Snippet> |
| 75 | <p> |
| 76 | Open <code>briven/functions/notes.ts</code>: |
| 77 | </p> |
| 78 | <Snippet>{`import { mutation, query } from '@briven/cli/server'; |
| 79 | |
| 80 | export const list = query({ |
| 81 | args: {}, |
| 82 | handler: async (ctx) => { |
| 83 | return ctx.db('notes') |
| 84 | .select(['id', 'body', 'createdAt']) |
| 85 | .orderBy('createdAt', 'desc') |
| 86 | .limit(100); |
| 87 | }, |
| 88 | }); |
| 89 | |
| 90 | export const create = mutation({ |
| 91 | args: { body: 'string' }, |
| 92 | handler: async (ctx, args) => { |
| 93 | const id = crypto.randomUUID(); |
| 94 | await ctx.db('notes').insert({ |
| 95 | id, |
| 96 | body: args.body, |
| 97 | createdAt: new Date(), |
| 98 | }); |
| 99 | return { id }; |
| 100 | }, |
| 101 | });`}</Snippet> |
| 102 | </Step> |
| 103 | <Step n={3} title="deploy"> |
| 104 | <PmTabs commands={DEPLOY} /> |
| 105 | <p className="mt-1"> |
| 106 | The CLI prints a schema diff, applies it transactionally on the data plane, uploads the |
| 107 | function bundle, and signals the runtime to swap in the new isolate. New row appears |
| 108 | under <em>deployments</em> on the dashboard. |
| 109 | </p> |
| 110 | </Step> |
| 111 | <Step n={4} title="wire it up from your app"> |
| 112 | <PmTabs commands={INSTALL_CLIENT} /> |
| 113 | <p className="mt-1"> |
| 114 | In your app (Next.js, Vite, Remix — anything React): |
| 115 | </p> |
| 116 | <Snippet>{`// app/providers.tsx |
| 117 | 'use client'; |
| 118 | import { BrivenProvider, createClient } from '@briven/react'; |
| 119 | |
| 120 | const client = createClient({ |
| 121 | projectId: 'p_01HZ...', |
| 122 | apiKey: process.env.NEXT_PUBLIC_BRIVEN_PUBLIC_KEY!, // 'brk_pub_...' |
| 123 | }); |
| 124 | |
| 125 | export function Providers({ children }: { children: React.ReactNode }) { |
| 126 | return <BrivenProvider client={client}>{children}</BrivenProvider>; |
| 127 | } |
| 128 | |
| 129 | // app/notes/page.tsx |
| 130 | 'use client'; |
| 131 | import { useMutation, useQuery } from '@briven/react'; |
| 132 | |
| 133 | export default function Notes() { |
| 134 | const { data, isLoading } = useQuery('notes:list', {}); |
| 135 | const create = useMutation('notes:create'); |
| 136 | |
| 137 | if (isLoading) return <p>loading…</p>; |
| 138 | return ( |
| 139 | <div> |
| 140 | {data?.map(n => <p key={n.id}>{n.body}</p>)} |
| 141 | <button onClick={() => create.mutate({ body: 'hello' })}>add</button> |
| 142 | </div> |
| 143 | ); |
| 144 | }`}</Snippet> |
| 145 | <p className="mt-1"> |
| 146 | On <em>add</em>, briven inserts the row, the realtime service picks up the postgres{' '} |
| 147 | NOTIFY for the <code>notes</code> table, re-invokes every active{' '} |
| 148 | <code>notes:list</code> subscription, and pushes the fresh result — no polling, no |
| 149 | manual cache invalidation. |
| 150 | </p> |
| 151 | </Step> |
| 152 | </ol> |
| 153 | |
| 154 | <h2 className="mt-12 font-mono text-lg">what just happened</h2> |
| 155 | <ul className="mt-3 list-inside list-disc font-mono text-sm text-[var(--color-text-muted)]"> |
| 156 | <li> |
| 157 | <code>briven deploy</code> compiled your TypeScript schema to a postgres migration and ran it |
| 158 | transactionally — if any column add failed, no rows changed. |
| 159 | </li> |
| 160 | <li> |
| 161 | Your function bundle was uploaded to the runtime and assigned a fresh Deno isolate. Cold |
| 162 | start budget: 200ms p50. |
| 163 | </li> |
| 164 | <li> |
| 165 | On every <code>useQuery</code> on the client, briven's realtime service registered a{' '} |
| 166 | LISTEN on every postgres table the function read — so any subsequent mutation that touches{' '} |
| 167 | those tables triggers an automatic re-invoke. |
| 168 | </li> |
| 169 | </ul> |
| 170 | |
| 171 | <h2 className="mt-12 font-mono text-lg">what to read next</h2> |
| 172 | <ul className="mt-3 flex flex-col gap-2 font-mono text-sm"> |
| 173 | <NextLink |
| 174 | href="/schema" |
| 175 | title="schema" |
| 176 | body="every column type, constraint, index, default, and diff semantic" |
| 177 | /> |
| 178 | <NextLink |
| 179 | href="/functions" |
| 180 | title="functions" |
| 181 | body="query/mutation/action wrappers, Ctx, db builder, lifecycle, error handling" |
| 182 | /> |
| 183 | <NextLink |
| 184 | href="/cli" |
| 185 | title="cli" |
| 186 | body="every command — deploy, dev (watch + hot reload), env, db, logs, invoke, projects" |
| 187 | /> |
| 188 | <NextLink |
| 189 | href="/migration" |
| 190 | title="migration" |
| 191 | body="moving from convex, supabase, postgres/drizzle/prisma, firebase, hasura, nextauth" |
| 192 | /> |
| 193 | </ul> |
| 194 | </DocsShell> |
| 195 | ); |
| 196 | } |
| 197 | |
| 198 | function Step({ n, title, children }: { n: number; title: string; children: React.ReactNode }) { |
| 199 | return ( |
| 200 | <li className="flex gap-4"> |
| 201 | <span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-[var(--color-primary)] text-[var(--color-text-inverse)]"> |
| 202 | {n} |
| 203 | </span> |
| 204 | <div className="min-w-0 flex-1"> |
| 205 | <p className="font-mono text-[var(--color-text)]">{title}</p> |
| 206 | <div className="mt-1 space-y-2">{children}</div> |
| 207 | </div> |
| 208 | </li> |
| 209 | ); |
| 210 | } |
| 211 | |
| 212 | function Snippet({ children }: { children: string }) { |
| 213 | return ( |
| 214 | <pre className="overflow-x-auto rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 font-mono text-xs"> |
| 215 | <code>{children}</code> |
| 216 | </pre> |
| 217 | ); |
| 218 | } |
| 219 | |
| 220 | function NextLink({ href, title, body }: { href: string; title: string; body: string }) { |
| 221 | return ( |
| 222 | <li> |
| 223 | <a |
| 224 | href={href} |
| 225 | className="block rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 transition hover:border-[var(--color-border)]" |
| 226 | > |
| 227 | <p className="font-mono text-[var(--color-text)]">{title}</p> |
| 228 | <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]">{body}</p> |
| 229 | </a> |
| 230 | </li> |
| 231 | ); |
| 232 | } |