anolilab/lunora/lunora-realtime
Claude Code and agent skill by anolilab/lunora
- community
Checking that the SKILL.md file is still in the repository.
Wires Lunora's live data into a client app. Covers `LunoraClient` and the per-framework provider, reactive `useQuery`/`useSubscription`, `useMutation` with optimistic updates, `usePaginatedQuery`, `shardKey` routing, connection status, presence, the React/Vue/Solid/Svelte/Angular/React Native adapters, and the `@lunora/db` TanStack DB binding. Use when building UI that reads or writes Lunora functions, when asked to "show live data", "make this update in real time", "add optimistic updates", "add infinite scroll/pagination", or "show offline/reconnecting state", when editing components that import `@lunora/react`/`vue`/`solid`/`svelte`/`angular`/`react-native` or `lunorash/client`, when a live query stays `undefined`, freezes, or doesn't update, or when choosing between a live query, a stream, a live topic (`defineLiveStream`, `useLiveTopic`, `ctx.live.publish`, SSE), a whisper, a room and a socket, such as "push an event to the browser" or "notify the page when X happens".
Install
npx vskill@latest install anolilab/lunora/lunora-realtimeSKILL.md
Lunora Realtime
Consume Lunora functions reactively from the client. Queries are live subscriptions over WebSocket — a useQuery re-renders as soon as a mutation changes the rows it reads — and mutations can paint optimistically with automatic rollback.
Choosing a Channel
Decide by what you are sending. If the client could get it by running a query, use a live query: it is typed, runs under the caller's identity, and works across shards. Reach past it only for the rest.
| You want | Use | |
|---|---|---|
| Rows that stay fresh | useQuery (WebSocket, typed through api.*) | |
| A write / a side effect | useMutation / useAction | |
| Chunks of one long call (model tokens) | query.stream() + useStream (durable to resume) | |
| Chunks of one HTTP request | httpRoute.get(path).stream() + useHttpStream | |
| A server-published event that is not a row | live topic: defineLiveStream + useLiveTopic | |
| A client-to-clients ephemeral message (cursors) | whisper (client.whisper; authorize with onWhisper) | |
| A room with server join / message / leave handlers | defineRoom + client.connectRoom | |
| A dedicated socket at your own path | defineWebSocket + client.connectWebSocket(path) | |
| Who is online | usePresence |
Live topics are typed from their declaration: each topic's key and payload are validators on the stream, and codegen emits _generated/live.ts with a typed reference per topic. Full guide: the "Choosing a data channel" docs page.
When Not to Use
- Writing the server functions themselves — that's
lunora-functions. - Initial project/provider scaffolding — that's
lunora-quickstart. - Sign-in UI and the
Authenticatedgates — that'slunora-setup-auth.
Provider Setup
Create the LunoraClient once at module scope; it owns the WebSocket, the optimistic cache, and the offline queue, so creating it inside a component reopens the socket on every render.
The adapters differ in how they register the client and name their primitives:
| Package | Register the client | Live read / write | |
|---|---|---|---|
@lunora/react | <LunoraProvider client={client}> | useQuery / useMutation | |
@lunora/vue | app.use(createLunora(client)) (or provideLunora(client)) | useQuery / useMutation | |
@lunora/solid | <LunoraProvider client={client}> | createQuery / createMutation | |
@lunora/svelte | setLunoraClient(client) in the root layout | query / mutation stores | |
@lunora/angular | provideLunora() in ApplicationConfig | liveQuery / mutate, injectLunoraClient() | |
@lunora/react-native | createLunoraClient({ url, storage }) + LunoraProvider | re-exports @lunora/react hooks |
The rest of this skill uses the React names; the others mirror the same semantics (usePaginatedQuery → createPaginatedQuery / paginatedQuery, useConnectionStatus → createConnectionStatus / connectionStatus, …). React Native passes storage: AsyncStorage so the offline queue survives a restart; @lunora/react-native/auth bridges a better-auth Expo session. @lunora/astro and @lunora/nuxt (Nitro) integrate Lunora into the SSR server.
Live Queries
useQuery(reference, args, options?) opens a subscription and returns the value, or undefined while it loads. References come from codegen and are typed end to end, so no cast is needed:
undefinedmeans loading and[]means loaded-and-empty; branch on both.- Pass
"skip"asargsto disable the query (no network call, no
subscription) — e.g. until a required id is known.
- Pass
onErrorto see a server-pushed error (an RLS denial, a query that starts
throwing). Without it the error is dropped and the value silently freezes at its last good result.
shardKey: for a.shardBy(...)table, pin every read and write to the
shard's key ({ shardKey: channelId } on both useQuery and the mutation call). Without it the runtime has to fan out across every shard.
- The subscription re-runs only when
argsor the rows it read change. Keep
args narrow so unrelated writes don't re-push data (see lunora-performance-audit).
useSubscriptionis the push-only variant (no initial HTTP fetch) returning
{ data, error }. For SSR, @lunora/react/server exports preloadQuery, fetchQuery, and lunoraQueryOptions; hand the result to usePreloadedQuery on the client.
- References in folders are nested:
lunora/billing/invoices.ts→
api.billing.invoices.<fn>. Client code only ever imports api; internal functions are not callable from the client.
Authorization & Live Queries
Live queries are identity-aware. At the WebSocket upgrade the runtime stamps the caller's verified identity onto the socket, and every subscription re-run executes under that identity, so:
.use(rls(...))andctx.auth.userIdbehave the same in a subscription as in
a one-shot read. Shape subscriptions AND-merge their predicate with the table's RLS read policy.
- An anonymous socket carries no identity, so an RLS/
ctx.authquery fails
closed (empty or denied) rather than leaking rows.
- When the credential carries an expiry, the server sends
TOKEN_EXPIREDand
closes with code 4001. LunoraClient reconnects and re-resolves identity automatically; surface it in the UI only if a re-login is required.
Use .shardBy(...) and narrow args for performance, rls() / ctx.auth for authorization. They compose.
Mutations + Optimistic Updates
useMutation(reference) returns { mutate, pending, data, error, reset }. Destructure it — the hook result itself isn't callable. mutate returns a promise that rejects on failure. Pass optimisticUpdate to paint the next state immediately; if the server rejects the call the cache rolls back.
optimisticUpdatenames the query it patches because nothing can infer that
todos.add affects todos.list. store.getAllQueries(fn) lists every subscribed args variant of a query so you can patch each one.
- The provisional row must match the query's element shape, including
_id/_creationTime, or the UI flickers when the real row lands.
- The per-call
optimistic: (current) => nextshortcut only patches a
subscription under the mutation's own reference and args (a counter, a doc by id). For the add-updates-list shape above it is a silent no-op.
pendingstaystrueuntil every overlapping call from this hook settles;
use it to disable the submit button.
- Offline, mutations are queued by
LunoraClientand replayed on reconnect,
keyed by client id so they aren't applied twice.
Pagination, Connection, Presence
The server query takes paginationOpts and returns .paginate(...) (see lunora-functions). The hook supplies the cursor:
Pages are live ranges: inserts and deletes inside a loaded page grow or shrink it in place without duplicating rows. There is no total count; subscribe to a separate count query for "42 of 1,203". useInfiniteQuery is the TanStack-style alternative.
useConnectionStatus— live socket state for an offline/reconnecting banner.usePresence— who's-here with heartbeat (pairs with thepresenceregistry
item).
useAuth+Authenticated/Unauthenticated/AuthLoading— see
lunora-setup-auth.
Live Topics
For an event with no query behind it ("a comment was posted"), the server publishes to a declared topic and the browser follows topics over one shared SSE connection (shared when the connection options match). Declare the stream and its topics once (defineLiveStream({ id, topics: { post: { key: { postId: v.id("posts") }, payload: v.object({...}) } }, onSubscribe })); codegen mounts its connect and control routes on the worker through .build() or .buildFrameworkWorker(host). Add liveStreamRouter yourself only when you compose the host with withLunora or withFrameworkWorker; the generated .buildFrameworkWorker(host) already wraps it. Follow topics with the adapter's hook (all return { lastEvent, error }). onSubscribe receives the parsed { ctx, topic, key, raw }; do not split a string. A subscribe for an undeclared name, wrong arity or a non-canonical spelling is denied before onSubscribe runs. Codegen writes _generated/live.ts:
| Package | Primitive | |
|---|---|---|
@lunora/react, vue | useLiveTopic | |
@lunora/solid | createLiveTopic | |
@lunora/svelte | liveTopic (a store) | |
@lunora/angular | liveTopic (signals) | |
@lunora/react-native | re-exports useLiveTopic |
Svelte's liveTopic and Angular's liveTopic read the client when called: call them during component init (Angular: or pass client and destroyRef). Pass shardKey in the options to follow a .shardBy shard.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingpackages/cli/skills/lunora-realtime/SKILL.md
Popularity
- 7-day trend score
- 5
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/anolilab/lunora/lunora-realtime)