page.tsx232 lines · main
1import { PmTabs } from '../../components/pm-tabs';
2import { DocsShell } from '../../components/shell';
3import { pmExec, pmInstall, pmJoin, pmPlain } from '../../lib/pm';
4
5export const metadata = {
6 title: 'quickstart',
7};
8
9const SCAFFOLD = pmJoin(
10 pmPlain('$ mkdir my-app && cd my-app'),
11 pmExec('briven setup --name my-app'),
12);
13
14const INSTALL_CLIENT = pmInstall('@briven/react');
15
16const DEPLOY = pmExec('briven deploy');
17
18export 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
65export 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
80export 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
90export 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';
118import { BrivenProvider, createClient } from '@briven/react';
119
120const client = createClient({
121 projectId: 'p_01HZ...',
122 apiKey: process.env.NEXT_PUBLIC_BRIVEN_PUBLIC_KEY!, // 'brk_pub_...'
123});
124
125export function Providers({ children }: { children: React.ReactNode }) {
126 return <BrivenProvider client={client}>{children}</BrivenProvider>;
127}
128
129// app/notes/page.tsx
130'use client';
131import { useMutation, useQuery } from '@briven/react';
132
133export 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&apos;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
198function 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
212function 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
220function 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}