page.tsx250 lines · main
1import { DocsShell } from '../../components/shell';
2
3export const metadata = { title: 'examples' };
4
5interface Example {
6 id: string;
7 title: string;
8 description: string;
9 schema: string;
10 notes?: string;
11}
12
13const EXAMPLES: readonly Example[] = [
14 {
15 id: 'todo',
16 title: 'todo app',
17 description:
18 'one-user todo list. optional starter via `briven setup --template todo-app` (or `briven init --template todo-app` for local files only). minimal example to see reactive queries in action: every insert / update / delete from the cli or studio re-runs the active subscriptions on the client.',
19 schema: `import { boolean, schema, table, text, timestamp } from '@briven/cli/schema';
20
21export default schema({
22 todos: table({
23 id: text().primaryKey(),
24 body: text().notNull(),
25 done: boolean().default('false').notNull(),
26 createdAt: timestamp().default('now()').notNull(),
27 }),
28});`,
29 },
30 {
31 id: 'blog',
32 title: 'blog with comments',
33 description:
34 'multi-user blog. posts belong to authors; comments belong to posts and can reply to other comments (self-FK on parent_id). createdAt is indexed on both tables for "newest first" feeds.',
35 schema: `import { schema, table, text, timestamp, boolean } from '@briven/cli/schema';
36
37export default schema({
38 authors: table({
39 id: text().primaryKey(),
40 email: text().notNull(),
41 displayName: text().notNull(),
42 createdAt: timestamp().default('now()').notNull(),
43 }, {
44 indexes: [{ columns: ['email'], unique: true }],
45 }),
46
47 posts: table({
48 id: text().primaryKey(),
49 authorId: text().notNull().references('authors', 'id'),
50 title: text().notNull(),
51 body: text().notNull(),
52 published: boolean().default('false').notNull(),
53 createdAt: timestamp().default('now()').notNull(),
54 }, {
55 indexes: [{ columns: ['authorId', 'createdAt'] }],
56 }),
57
58 comments: table({
59 id: text().primaryKey(),
60 postId: text().notNull().references('posts', 'id'),
61 authorId: text().notNull().references('authors', 'id'),
62 parentId: text().nullable().references('comments', 'id'),
63 body: text().notNull(),
64 createdAt: timestamp().default('now()').notNull(),
65 }, {
66 indexes: [{ columns: ['postId', 'createdAt'] }],
67 }),
68});`,
69 },
70 {
71 id: 'chat',
72 title: 'chat / dm app',
73 description:
74 'real-time chat with rooms and members. members table is the join — a user belongs to a room with a role. messages reference both the room and the author so realtime fan-out scopes per-room.',
75 schema: `import { schema, table, text, timestamp } from '@briven/cli/schema';
76
77export default schema({
78 users: table({
79 id: text().primaryKey(),
80 email: text().notNull(),
81 displayName: text().notNull(),
82 }, {
83 indexes: [{ columns: ['email'], unique: true }],
84 }),
85
86 rooms: table({
87 id: text().primaryKey(),
88 name: text().notNull(),
89 createdAt: timestamp().default('now()').notNull(),
90 }),
91
92 roomMembers: table({
93 roomId: text().notNull().references('rooms', 'id'),
94 userId: text().notNull().references('users', 'id'),
95 role: text().default("'member'").notNull(),
96 joinedAt: timestamp().default('now()').notNull(),
97 }, {
98 indexes: [
99 { columns: ['roomId', 'userId'], unique: true },
100 { columns: ['userId'] },
101 ],
102 }),
103
104 messages: table({
105 id: text().primaryKey(),
106 roomId: text().notNull().references('rooms', 'id'),
107 authorId: text().notNull().references('users', 'id'),
108 body: text().notNull(),
109 createdAt: timestamp().default('now()').notNull(),
110 }, {
111 indexes: [{ columns: ['roomId', 'createdAt'] }],
112 }),
113});`,
114 notes:
115 'tip: the realtime subscriber filters by roomId, so server-side fan-out only pushes a message to the people in that room.',
116 },
117 {
118 id: 'ecommerce',
119 title: 'tiny e-commerce',
120 description:
121 'products, carts, orders. money in integer cents (no floats!), stock tracked at the product level, orders cascade to order_items so deleting an order cleans up its line items.',
122 schema: `import { bigint, integer, schema, table, text, timestamp } from '@briven/cli/schema';
123
124export default schema({
125 products: table({
126 id: text().primaryKey(),
127 name: text().notNull(),
128 descriptionMd: text().notNull(),
129 priceCents: integer().notNull(),
130 stock: integer().default('0').notNull(),
131 createdAt: timestamp().default('now()').notNull(),
132 }),
133
134 customers: table({
135 id: text().primaryKey(),
136 email: text().notNull(),
137 displayName: text().notNull(),
138 }, {
139 indexes: [{ columns: ['email'], unique: true }],
140 }),
141
142 orders: table({
143 id: text().primaryKey(),
144 customerId: text().notNull().references('customers', 'id'),
145 status: text().default("'pending'").notNull(),
146 totalCents: bigint().notNull(),
147 placedAt: timestamp().default('now()').notNull(),
148 }, {
149 indexes: [{ columns: ['customerId', 'placedAt'] }],
150 }),
151
152 orderItems: table({
153 orderId: text().notNull().references('orders', 'id', { onDelete: 'cascade' }),
154 productId: text().notNull().references('products', 'id'),
155 quantity: integer().notNull(),
156 unitPriceCents: integer().notNull(),
157 }, {
158 indexes: [{ columns: ['orderId', 'productId'], unique: true }],
159 }),
160});`,
161 notes:
162 'always store money as integer cents. floats lose precision and break sums. the cli\'s integer() maps to int4 (up to ~$21M); use bigint() for totals that span many orders.',
163 },
164 {
165 id: 'multi-tenant',
166 title: 'multi-tenant SaaS',
167 description:
168 'org → projects → resources. every resource references the org it belongs to so a single WHERE clause enforces tenant isolation. consider also using row-level security if your runtime queries the database directly.',
169 schema: `import { schema, table, text, timestamp } from '@briven/cli/schema';
170
171export default schema({
172 orgs: table({
173 id: text().primaryKey(),
174 name: text().notNull(),
175 createdAt: timestamp().default('now()').notNull(),
176 }),
177
178 users: table({
179 id: text().primaryKey(),
180 email: text().notNull(),
181 }, {
182 indexes: [{ columns: ['email'], unique: true }],
183 }),
184
185 orgMembers: table({
186 orgId: text().notNull().references('orgs', 'id', { onDelete: 'cascade' }),
187 userId: text().notNull().references('users', 'id'),
188 role: text().default("'member'").notNull(),
189 }, {
190 indexes: [{ columns: ['orgId', 'userId'], unique: true }],
191 }),
192
193 resources: table({
194 id: text().primaryKey(),
195 orgId: text().notNull().references('orgs', 'id', { onDelete: 'cascade' }),
196 name: text().notNull(),
197 payload: text().notNull(),
198 createdAt: timestamp().default('now()').notNull(),
199 }, {
200 indexes: [{ columns: ['orgId', 'createdAt'] }],
201 }),
202});`,
203 notes:
204 'every query in your functions should start with `WHERE orgId = ?` where ? comes from the authenticated user\'s org membership. think of it like a partition key — make sure it\'s the leading column on every index.',
205 },
206];
207
208export default function ExamplesPage() {
209 return (
210 <DocsShell>
211 <h1 className="font-mono text-2xl tracking-tight">examples</h1>
212 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
213 copy-paste schemas for common app shapes. every example is a real{' '}
214 <code>briven/schema.ts</code> — drop it in your project, run{' '}
215 <code>briven deploy</code>, and you have a working data model. or click-build the
216 equivalent in studio if you&apos;d rather start from the dashboard.
217 </p>
218
219 <nav className="mt-6 flex flex-wrap gap-2 font-mono text-xs">
220 {EXAMPLES.map((e) => (
221 <a
222 key={e.id}
223 href={`#${e.id}`}
224 className="rounded-md border border-[var(--color-border)] px-3 py-1 text-[var(--color-text-muted)] hover:text-[var(--color-text)]"
225 >
226 {e.title}
227 </a>
228 ))}
229 </nav>
230
231 {EXAMPLES.map((e) => (
232 <section key={e.id} id={e.id} className="mt-10 scroll-mt-20">
233 <h2 className="font-mono text-lg tracking-tight">{e.title}</h2>
234 <p className="mt-1 font-mono text-sm text-[var(--color-text-muted)]">
235 {e.description}
236 </p>
237 <pre className="mt-4 overflow-x-auto rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-code-bg)] p-4 font-mono text-xs text-[var(--color-code-text)]">
238 <code>{e.schema}</code>
239 </pre>
240 {e.notes ? (
241 <p className="mt-2 font-mono text-xs text-[var(--color-text-subtle)]">
242 <span className="text-[var(--color-text)]">note: </span>
243 {e.notes}
244 </p>
245 ) : null}
246 </section>
247 ))}
248 </DocsShell>
249 );
250}