page.tsx179 lines · main
| 1 | import { DocsShell } from '../../components/shell'; |
| 2 | |
| 3 | export const metadata = { title: 'client sdks' }; |
| 4 | |
| 5 | interface Sdk { |
| 6 | pkg: string; |
| 7 | framework: string; |
| 8 | pitch: string; |
| 9 | setup: string; |
| 10 | useExample: string; |
| 11 | notes?: string; |
| 12 | } |
| 13 | |
| 14 | const SDKS: readonly Sdk[] = [ |
| 15 | { |
| 16 | pkg: '@briven/react', |
| 17 | framework: 'react', |
| 18 | pitch: |
| 19 | 'reference client. `useQuery` re-runs on row changes via the realtime WS; `useMutation` is the same shape as TanStack Query so the api curve is short.', |
| 20 | setup: `import { BrivenProvider, createClient } from '@briven/react'; |
| 21 | |
| 22 | const client = createClient({ |
| 23 | url: 'https://api.briven.tech', |
| 24 | projectId: 'p_xxx', |
| 25 | apiKey: process.env.NEXT_PUBLIC_BRIVEN_KEY, |
| 26 | }); |
| 27 | |
| 28 | export function Providers({ children }: { children: React.ReactNode }) { |
| 29 | return <BrivenProvider client={client}>{children}</BrivenProvider>; |
| 30 | }`, |
| 31 | useExample: `'use client'; |
| 32 | import { useQuery, useMutation } from '@briven/react'; |
| 33 | |
| 34 | export function Notes() { |
| 35 | const { data, isLoading, error } = useQuery<'listNotes', { id: string; body: string }[]>( |
| 36 | 'listNotes', |
| 37 | {}, |
| 38 | ); |
| 39 | const create = useMutation<'createNote', { body: string }, { id: string }>('createNote'); |
| 40 | |
| 41 | if (isLoading) return <p>loading…</p>; |
| 42 | if (error) return <p>{error.message}</p>; |
| 43 | return ( |
| 44 | <> |
| 45 | <button onClick={() => create.mutate({ body: 'hello' })}>add</button> |
| 46 | <ul>{data?.map((n) => <li key={n.id}>{n.body}</li>)}</ul> |
| 47 | </> |
| 48 | ); |
| 49 | }`, |
| 50 | notes: |
| 51 | 'useQuery accepts a stable args object — pass primitives, not new objects on every render, or the subscription identity will churn.', |
| 52 | }, |
| 53 | { |
| 54 | pkg: '@briven/svelte', |
| 55 | framework: 'svelte 5', |
| 56 | pitch: |
| 57 | "queries return a Readable store that fires while you're subscribed; the WS closes when the last consumer drops. svelte's reference-counted store contract does the lifecycle for us.", |
| 58 | setup: `import { setBrivenClient, createClient } from '@briven/svelte'; |
| 59 | |
| 60 | const client = createClient({ |
| 61 | url: 'https://api.briven.tech', |
| 62 | projectId: 'p_xxx', |
| 63 | apiKey: import.meta.env.VITE_BRIVEN_KEY, |
| 64 | }); |
| 65 | |
| 66 | setBrivenClient(client);`, |
| 67 | useExample: `<script lang="ts"> |
| 68 | import { query, mutation } from '@briven/svelte'; |
| 69 | const notes = query<'listNotes', { id: string; body: string }[]>('listNotes', {}); |
| 70 | const create = mutation<'createNote', { body: string }, { id: string }>('createNote'); |
| 71 | </script> |
| 72 | |
| 73 | {#if $notes.isLoading}loading…{/if} |
| 74 | {#if $notes.error}{$notes.error.message}{/if} |
| 75 | <button onclick={() => create.mutate({ body: 'hello' })}>add</button> |
| 76 | <ul> |
| 77 | {#each $notes.data ?? [] as n (n.id)}<li>{n.body}</li>{/each} |
| 78 | </ul>`, |
| 79 | }, |
| 80 | { |
| 81 | pkg: '@briven/vue', |
| 82 | framework: 'vue 3', |
| 83 | pitch: |
| 84 | 'composables version of the same surface. `useQuery` returns refs; `watch(stableKey(args))` re-subscribes when arg identity changes. `onScopeDispose` closes the sub on component unmount.', |
| 85 | setup: `// main.ts |
| 86 | import { createApp } from 'vue'; |
| 87 | import { setBrivenClient, createClient } from '@briven/vue'; |
| 88 | |
| 89 | const client = createClient({ |
| 90 | url: 'https://api.briven.tech', |
| 91 | projectId: 'p_xxx', |
| 92 | apiKey: import.meta.env.VITE_BRIVEN_KEY, |
| 93 | }); |
| 94 | |
| 95 | setBrivenClient(client); |
| 96 | createApp(App).mount('#app');`, |
| 97 | useExample: `<script setup lang="ts"> |
| 98 | import { useQuery, useMutation } from '@briven/vue'; |
| 99 | const { data, isLoading, error } = useQuery<'listNotes', { id: string; body: string }[]>( |
| 100 | 'listNotes', |
| 101 | {}, |
| 102 | ); |
| 103 | const { mutate } = useMutation<'createNote', { body: string }, { id: string }>('createNote'); |
| 104 | </script> |
| 105 | |
| 106 | <template> |
| 107 | <p v-if="isLoading">loading…</p> |
| 108 | <p v-else-if="error">{{ error.message }}</p> |
| 109 | <button @click="mutate({ body: 'hello' })">add</button> |
| 110 | <ul><li v-for="n in data ?? []" :key="n.id">{{ n.body }}</li></ul> |
| 111 | </template>`, |
| 112 | }, |
| 113 | ]; |
| 114 | |
| 115 | export default function SdkPage() { |
| 116 | return ( |
| 117 | <DocsShell> |
| 118 | <h1 className="font-mono text-2xl tracking-tight">client sdks</h1> |
| 119 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 120 | three first-party clients with the same surface across react, svelte, and vue. each |
| 121 | manages the realtime websocket lifecycle for you — subscribe on first read, re-run |
| 122 | on row changes, close when the last consumer drops. you don't talk to the WS |
| 123 | directly. |
| 124 | </p> |
| 125 | |
| 126 | <nav className="mt-6 flex flex-wrap gap-2 font-mono text-xs"> |
| 127 | {SDKS.map((s) => ( |
| 128 | <a |
| 129 | key={s.pkg} |
| 130 | href={`#${encodeURIComponent(s.pkg)}`} |
| 131 | className="rounded-md border border-[var(--color-border)] px-3 py-1 text-[var(--color-text-muted)] hover:text-[var(--color-text)]" |
| 132 | > |
| 133 | {s.pkg} |
| 134 | </a> |
| 135 | ))} |
| 136 | </nav> |
| 137 | |
| 138 | <section className="mt-8 rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 font-mono text-xs text-[var(--color-text-muted)]"> |
| 139 | <strong className="text-[var(--color-text)]">key shape:</strong> the api key lives on |
| 140 | the client. browser clients use a <code>readonly</code>-scope key ( |
| 141 | <code>brk_</code>); server SDKs / SSR use a <code>developer</code>-scope key. |
| 142 | rotate via the api keys tab in your project — old keys revoke immediately. |
| 143 | </section> |
| 144 | |
| 145 | {SDKS.map((s) => ( |
| 146 | <section |
| 147 | key={s.pkg} |
| 148 | id={s.pkg} |
| 149 | className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8" |
| 150 | > |
| 151 | <div className="flex items-baseline justify-between gap-3"> |
| 152 | <h2 className="font-mono text-lg tracking-tight"> |
| 153 | <code>{s.pkg}</code> |
| 154 | </h2> |
| 155 | <span className="font-mono text-xs text-[var(--color-text-subtle)]">{s.framework}</span> |
| 156 | </div> |
| 157 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">{s.pitch}</p> |
| 158 | |
| 159 | <h3 className="mt-5 font-mono text-sm">setup</h3> |
| 160 | <pre className="mt-2 overflow-x-auto rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-code-bg)] p-3 font-mono text-xs text-[var(--color-code-text)]"> |
| 161 | <code>{s.setup}</code> |
| 162 | </pre> |
| 163 | |
| 164 | <h3 className="mt-5 font-mono text-sm">use</h3> |
| 165 | <pre className="mt-2 overflow-x-auto rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-code-bg)] p-3 font-mono text-xs text-[var(--color-code-text)]"> |
| 166 | <code>{s.useExample}</code> |
| 167 | </pre> |
| 168 | |
| 169 | {s.notes ? ( |
| 170 | <p className="mt-3 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 171 | <span className="text-[var(--color-text)]">note: </span> |
| 172 | {s.notes} |
| 173 | </p> |
| 174 | ) : null} |
| 175 | </section> |
| 176 | ))} |
| 177 | </DocsShell> |
| 178 | ); |
| 179 | } |