page.tsx155 lines · main
| 1 | import Link from 'next/link'; |
| 2 | |
| 3 | import { DocsShell } from '../../components/shell'; |
| 4 | |
| 5 | export const metadata = { title: 'realtime' }; |
| 6 | |
| 7 | function 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 | |
| 15 | export 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/<id>/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 "touched") and used to register the subscription. |
| 65 | you can'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'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'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 |
| 82 | Authorization: Bearer brk_xxx |
| 83 | Content-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'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'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 | } |