page.tsx155 lines · main
1import Link from 'next/link';
2
3import { DocsShell } from '../../components/shell';
4
5export const metadata = { title: 'realtime' };
6
7function Snippet({ children }: { children: string }) {
8 return (
9 <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)]">
10 <code>{children}</code>
11 </pre>
12 );
13}
14
15export default function RealtimePage() {
16 return (
17 <DocsShell>
18 <h1 className="font-mono text-2xl tracking-tight">realtime</h1>
19 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
20 every <code>query()</code> function is reactive by default. clients subscribe over
21 a WebSocket; when the rows your query touched change, the server re-invokes the
22 function and pushes the new result. no extra code on your side — write a normal
23 SQL query and the platform handles the rest.
24 </p>
25
26 <section className="mt-8">
27 <h2 className="font-mono text-lg tracking-tight">how it works</h2>
28 <ol className="mt-3 flex list-decimal flex-col gap-2 pl-5 font-mono text-sm text-[var(--color-text-muted)]">
29 <li>
30 <strong>subscribe</strong> — the client opens a WS to{' '}
31 <code>wss://api.briven.tech/v1/projects/&lt;id&gt;/realtime</code>, sends{' '}
32 <code>{`{ type: 'subscribe', function, args }`}</code>, receives the initial
33 result + a subscription id.
34 </li>
35 <li>
36 <strong>register</strong> — the platform invokes the query once, captures the
37 list of tables it touched (e.g. <code>['posts', 'users']</code>), and stores
38 the subscription under those table keys.
39 </li>
40 <li>
41 <strong>notify</strong> — every <code>mutation()</code> publishes the tables it
42 wrote to via Postgres LISTEN/NOTIFY. the realtime service hears the NOTIFY and
43 looks up every subscription that touched that table.
44 </li>
45 <li>
46 <strong>re-invoke + push</strong> — the platform re-runs each matching
47 subscription with the original args, diffs against the last sent result, and
48 pushes a <code>{`{ type: 'result' }`}</code> frame only when the value changed.
49 </li>
50 <li>
51 <strong>unsubscribe</strong> — the client sends{' '}
52 <code>{`{ type: 'unsubscribe', id }`}</code> or just closes the socket. the
53 server cleans up subscription state and UNLISTENs when no subscriber remains
54 for a table.
55 </li>
56 </ol>
57 </section>
58
59 <section className="mt-10">
60 <h2 className="font-mono text-lg tracking-tight">touched-tables tracking</h2>
61 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
62 the runtime instruments <code>ctx.db</code> and records every table the query
63 read from. the list is stored on the invocation envelope (visible in the
64 dashboard logs tab as &quot;touched&quot;) and used to register the subscription.
65 you can&apos;t opt out — every table you read becomes a re-invoke trigger.
66 </p>
67 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
68 for very hot tables you don&apos;t actually want to subscribe to (e.g. an
69 audit-log read inside an otherwise stable query), consider splitting that read
70 into a separate action() call so it doesn&apos;t register the subscription.
71 </p>
72 </section>
73
74 <section className="mt-10">
75 <h2 className="font-mono text-lg tracking-tight">when not to use it</h2>
76 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
77 realtime carries a cost — every active subscription holds an open WS and a
78 server-side subscription record. for one-shot reads (e.g. a page that loads data
79 once on navigation), call the function via HTTP invoke instead:
80 </p>
81 <Snippet>{`POST https://api.briven.tech/v1/projects/p_xxx/invoke
82Authorization: Bearer brk_xxx
83Content-Type: application/json
84
85{ "function": "getStaticPage", "args": { "slug": "about" } }`}</Snippet>
86 <p className="mt-3 font-mono text-sm text-[var(--color-text-muted)]">
87 the SDK clients (<code>useQuery</code> et al) always subscribe — pass{' '}
88 <code>{`{ reactive: false }`}</code> to make them one-shot HTTP calls instead.
89 </p>
90 </section>
91
92 <section className="mt-10">
93 <h2 className="font-mono text-lg tracking-tight">concurrency limits</h2>
94 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
95 concurrent subscription caps live on your tier. free: 100 concurrent. pro: 1,000.
96 team: 10,000. the limit is per-project, not per-user — adjust your client&apos;s
97 subscription pattern if many users will be online simultaneously.
98 </p>
99 </section>
100
101 <section className="mt-10">
102 <h2 className="font-mono text-lg tracking-tight">debugging</h2>
103 <ul className="mt-2 flex list-disc flex-col gap-2 pl-5 font-mono text-sm text-[var(--color-text-muted)]">
104 <li>
105 <strong>dashboard logs tab</strong> — filter by your query function name; the{' '}
106 <em>touched</em> field shows which tables the platform registered.
107 </li>
108 <li>
109 <strong>per-function stats</strong> — count + p50/p99 over the last 24h on the
110 functions tab. high count + flat p99 = you have a chatty subscription.
111 </li>
112 <li>
113 <strong>prometheus</strong> — <code>briven_realtime_active_subs</code> +{' '}
114 <code>briven_realtime_active_channels</code> if you self-host; the operator
115 grafana dashboard has both as graphs.
116 </li>
117 </ul>
118 </section>
119
120 <section className="mt-10">
121 <h2 className="font-mono text-lg tracking-tight">protocol reference</h2>
122 <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]">
123 the SDK wraps this; you only need to know it if you&apos;re writing your own
124 client. JSON-over-WS, no binary frames.
125 </p>
126 <Snippet>{`// → client
127{ "type": "subscribe", "function": "listTodos", "args": { "filter": "open" } }
128
129// ← server (initial)
130{ "type": "result", "id": "sub_01HZ…", "data": [{ "id": "td_…", "body": "ship realtime" }] }
131
132// ← server (push, fires whenever the result diffs)
133{ "type": "result", "id": "sub_01HZ…", "data": [...] }
134
135// → client (terminate)
136{ "type": "unsubscribe", "id": "sub_01HZ…" }`}</Snippet>
137 </section>
138
139 <p className="mt-10 font-mono text-xs text-[var(--color-text-subtle)]">
140 see also:{' '}
141 <Link href="/functions" className="text-[var(--color-text-link)] underline-offset-2 hover:underline">
142 functions
143 </Link>{' '}
144 ·{' '}
145 <Link href="/sdks" className="text-[var(--color-text-link)] underline-offset-2 hover:underline">
146 client sdks
147 </Link>{' '}
148 ·{' '}
149 <Link href="/api" className="text-[var(--color-text-link)] underline-offset-2 hover:underline">
150 http api
151 </Link>
152 </p>
153 </DocsShell>
154 );
155}