Solid
Use Lunora with SolidJS — a provider, live createQuery/createMutation primitives, and the SSR-seed → live reactive-loader handoff.
Last updated:
@lunora/solid is the SolidJS adapter, an idiomatic layer over
@lunora/client. Solid's fine-grained signals map
directly onto Lunora's per-subscription deltas, so a live query is a signal
the socket writes to: only the components that read the accessor re-render.
This page is the task-oriented guide; for the full reference see
@lunora/solid.
Preview. The Solid adapter exposes the same hydrate-then-subscribe handoff as React, but it is preview maturity: not yet proven end-to-end against a running app. Solid lands first after React because its signals map most directly onto Lunora deltas. See Bring your framework.
Install
pnpm add @lunora/solidnpm install @lunora/solidyarn add @lunora/solidbun add @lunora/solidProvide the client
Wrap the tree in <LunoraProvider client={...}> (required at the root). Read it
anywhere below with useLunora().
import { LunoraClient } from "lunorash/client";
import { LunoraProvider } from "@lunora/solid";
import { render } from "solid-js/web";
import App from "./App";
const client = new LunoraClient({ url: window.location.origin });
render(
() => (
<LunoraProvider client={client}>
<App />
</LunoraProvider>
),
document.getElementById("root")!,
);Live queries
createQuery(fn, args) subscribes and returns a reactive accessor
(undefined until the first frame, then updated on every delta). Pass args as
a plain value or an accessor; an accessor re-subscribes when it changes. Pass
"skip" to short-circuit.
import { createQuery } from "@lunora/solid";
import { For, createSignal } from "solid-js";
import { api } from "./lunora/_generated/api";
function Messages() {
const [channelId, setChannelId] = createSignal("general");
// Accessor args: changing `channelId()` re-subscribes.
const messages = createQuery(api.messages.list, () => ({ channelId: channelId() }));
return <For each={messages() ?? []}>{(m) => <li>{m.text}</li>}</For>;
}Pass { shardKey } in options to target a specific shard when the function is
.shardBy(...)-partitioned. The subscription tears down when the owning reactive
scope is disposed.
Mutations
createMutation(fn) returns a MutationHandle of accessors:
{ data, error, pending, mutate, reset }. pending is ref-counted, and the
mutation engages the client's offline queue when the socket is down. You pass
optimistic updates per call.
import { createMutation } from "@lunora/solid";
import { api } from "./lunora/_generated/api";
function Composer(props: { channelId: string }) {
const send = createMutation(api.messages.send);
return (
<button
disabled={send.pending()}
onClick={() =>
void send.mutate(
{ channelId: props.channelId, text: "hi" },
{
optimisticUpdate: (store) => {
const current = store.getQuery(api.messages.list, { channelId: props.channelId }) ?? [];
store.setQuery(api.messages.list, { channelId: props.channelId }, [...current, { _id: "tmp", text: "hi" }]);
},
},
)
}
>
Send
</button>
);
}Reactive loaders
Run the query on the server with the socket-free @lunora/solid/server entry
inside a SolidStart route loader, hand the serializable Preloaded token to the
client, and hydratePreloaded seeds the accessor synchronously. The first
read returns the server value with no loading flash and no Suspense fallback,
then the live subscription attaches after mount.
import { createServerClient, preloadQuery } from "@lunora/solid/server";
import { api } from "./lunora/_generated/api";
// Per request — never reuse a client across requests (token leakage).
const client = createServerClient({ url: process.env.LUNORA_URL!, token });
const preloaded = await preloadQuery(client, api.posts.list, {});import { hydratePreloaded } from "@lunora/solid";
import type { Preloaded } from "@lunora/solid";
import { For } from "solid-js";
function Posts(props: { preloaded: Preloaded<Array<{ _id: string; title: string }>> }) {
// Seeded from SSR on the first read, then live.
const posts = hydratePreloaded(props.preloaded);
return <For each={posts()}>{(p) => <li>{p.title}</li>}</For>;
}Effects do not run during SSR, so the subscription is strictly client-side: the seed is the only value the server render sees. See Reactive loaders for the full handoff.
See also
- @lunora/solid — the full primitive + server reference
- Bring your framework — composition + adapter maturity
- Reactive loaders — the SSR-seed → live handoff
- Real-time — how subscriptions and deltas work