Add Realtime
Turn a normal browser query into a live view with the same schema and policy model.
Realtime in Kizaki is query-based. You write a normal read query, and the platform keeps it current. No separate subscription schema. No duplicated access rules.
Building The Query
Start with a browser SDK query. This is the same shape you would use for a one-shot fetch:
import { select } from "@kizaki/sdk/browser";
import { Message } from "@kizaki/schema";
const messagesQuery = select(Message)
.fields(Message.id, Message.text, Message.authorId, Message.createdAt)
.orderBy(Message.createdAt, "desc")
.limit(50);.fields() narrows the result to the columns your screen needs. .limit() keeps the live query focused on recent messages rather than the full table. Both matter for performance: every live update delivers the complete result set, so a smaller set means faster updates.
Subscribing To Live Updates
Pass the query to useQuery from @kizaki/react. The hook handles the initial fetch and opens a WebSocket subscription for live updates.
import { useQuery, useMutation } from "@kizaki/react";
import { sendMessage } from "@kizaki/client";
function Chat() {
const { data: messages, status } = useQuery(messagesQuery);
const send = useMutation(sendMessage, { invalidate: [messagesQuery] });
if (status === "loading") return <p>Loading...</p>;
return (
<div>
<ul>
{messages.map((m) => (
<li key={m.id}>{m.text}</li>
))}
</ul>
<button onClick={() => send.mutate({ text: "Hello" })}>Send</button>
</div>
);
}useMutation wraps an exposed function from @kizaki/client. The invalidate option refetches the messages query after a successful write, so the sender sees their own message immediately.
What To Expect
When another user sends a message, the live query includes it automatically. No polling. No manual refresh. The platform detects that the underlying data changed and pushes an updated result set to every connected client whose query is affected.
If a user loses read access (for example, they are removed from a channel) messages they can no longer see disappear from the result. The live query re-evaluates against current policies, and the UI reflects only what the user is authorized to read.
Authorization Stays In The Schema
The same @grant and @deny rules that govern one-shot reads govern every live query refresh. There is no separate subscription authorization model.
You do not need client-side filtering logic, subscription permissions, or a second set of access rules. The schema is the single source of truth for what each user can see, whether they are loading a page for the first time or watching it update in real time.
@grant read to role("member") {
where: member(channelId)
}A member of a channel sees messages in that channel on first load and on every subsequent live update.
Recommended Usage Pattern
- Use live queries for lists, dashboards, and activity-heavy screens.
- Keep writes behind exposed functions and generated client calls.
- Let invalidation and live updates refresh the UI instead of rebuilding your own subscription architecture.
For screens that change infrequently, pass { live: false } to useQuery to avoid holding an unnecessary WebSocket subscription.
Next
After the browser loop is working, learn the safe schema workflow in Evolve Your Schema.