README.md93 lines · main
1# realtime-chat
2
3a tiny multi-room chat showing off briven's reactive query primitives:
4`useQuery('listMessages', { roomId })` re-runs automatically whenever the
5`messages` table changes for that room. no polling, no manual cache
6invalidation, no websocket plumbing in your code.
7
8## what's in here
9
10```
11realtime-chat/
12├─ briven.json
13├─ briven/
14│ ├─ schema.ts two tables: rooms, messages
15│ └─ functions/
16│ ├─ listRooms.ts reactive · all rooms newest-first
17│ ├─ listMessages.ts reactive · messages in a room, newest-first
18│ ├─ createRoom.ts mutation · creates a room
19│ └─ sendMessage.ts mutation · validates room exists, inserts
20└─ README.md you are here
21```
22
23## try it in 60 seconds
24
25```sh
26cp -r examples/realtime-chat my-chat
27cd my-chat
28briven link
29briven deploy
30
31# create a room and post a message
32briven invoke createRoom --body '{"name":"general"}'
33briven invoke sendMessage --body '{"roomId":"rm_<id>","authorName":"j","body":"hello"}'
34briven invoke listMessages --body '{"roomId":"rm_<id>"}'
35```
36
37## react
38
39```tsx
40import { BrivenProvider, useQuery, useMutation } from '@briven/react';
41
42function ChatRoom({ roomId, me }: { roomId: string; me: string }) {
43 const { data: messages = [] } = useQuery('listMessages', { roomId });
44 const send = useMutation('sendMessage');
45 const [draft, setDraft] = useState('');
46
47 return (
48 <>
49 <ul>
50 {messages.map((m) => (
51 <li key={m.id}>
52 <strong>{m.authorName}:</strong> {m.body}
53 </li>
54 ))}
55 </ul>
56 <form
57 onSubmit={async (e) => {
58 e.preventDefault();
59 if (!draft.trim()) return;
60 await send({ roomId, authorName: me, body: draft });
61 setDraft('');
62 }}
63 >
64 <input value={draft} onChange={(e) => setDraft(e.target.value)} />
65 <button type="submit">send</button>
66 </form>
67 </>
68 );
69}
70```
71
72When `sendMessage` commits on the server, briven re-runs every active
73`listMessages` query that read the touched row. Subscribers in *every* tab,
74*every* device, get re-rendered without any extra code in the client.
75
76## why this is a good learning example
77
78- shows a **two-table schema** with a foreign-key relationship enforced in
79 application code (`sendMessage` validates `room` exists before insert)
80- shows **input validation** that returns proper status codes
81- shows **per-room reactivity** — only subscribers of the same room get the
82 re-run, not every connected client
83- maps cleanly to the chat patterns you'd build on Convex/Firestore but on a
84 database you can `pg_dump` whenever you want
85
86## next steps
87
88- **pagination**: add `before` (cursor) to `listMessages` and switch the
89 client to infinite scroll
90- **typing indicators**: a `typing` ephemeral table or a presence channel via
91 the realtime websocket
92- **auth**: wire `ctx.auth.userId` into `sendMessage` and reject anonymous
93 posts; reuse the same query function unchanged
Preview

realtime-chat

a tiny multi-room chat showing off briven's reactive query primitives: useQuery('listMessages', { roomId }) re-runs automatically whenever the messages table changes for that room. no polling, no manual cache invalidation, no websocket plumbing in your code.

what's in here

realtime-chat/
├─ briven.json
├─ briven/
│  ├─ schema.ts              two tables: rooms, messages
│  └─ functions/
│     ├─ listRooms.ts        reactive · all rooms newest-first
│     ├─ listMessages.ts     reactive · messages in a room, newest-first
│     ├─ createRoom.ts       mutation · creates a room
│     └─ sendMessage.ts      mutation · validates room exists, inserts
└─ README.md                 you are here

try it in 60 seconds

cp -r examples/realtime-chat my-chat
cd my-chat
briven link
briven deploy

# create a room and post a message
briven invoke createRoom --body '{"name":"general"}'
briven invoke sendMessage --body '{"roomId":"rm_<id>","authorName":"j","body":"hello"}'
briven invoke listMessages --body '{"roomId":"rm_<id>"}'

react

import { BrivenProvider, useQuery, useMutation } from '@briven/react';

function ChatRoom({ roomId, me }: { roomId: string; me: string }) {
  const { data: messages = [] } = useQuery('listMessages', { roomId });
  const send = useMutation('sendMessage');
  const [draft, setDraft] = useState('');

  return (
    <>
      <ul>
        {messages.map((m) => (
          <li key={m.id}>
            <strong>{m.authorName}:</strong> {m.body}
          </li>
        ))}
      </ul>
      <form
        onSubmit={async (e) => {
          e.preventDefault();
          if (!draft.trim()) return;
          await send({ roomId, authorName: me, body: draft });
          setDraft('');
        }}
      >
        <input value={draft} onChange={(e) => setDraft(e.target.value)} />
        <button type="submit">send</button>
      </form>
    </>
  );
}

When sendMessage commits on the server, briven re-runs every active listMessages query that read the touched row. Subscribers in every tab, every device, get re-rendered without any extra code in the client.

why this is a good learning example

  • shows a two-table schema with a foreign-key relationship enforced in application code (sendMessage validates room exists before insert)
  • shows input validation that returns proper status codes
  • shows per-room reactivity — only subscribers of the same room get the re-run, not every connected client
  • maps cleanly to the chat patterns you'd build on Convex/Firestore but on a database you can pg_dump whenever you want

next steps

  • pagination: add before (cursor) to listMessages and switch the client to infinite scroll
  • typing indicators: a typing ephemeral table or a presence channel via the realtime websocket
  • auth: wire ctx.auth.userId into sendMessage and reject anonymous posts; reuse the same query function unchanged