Client SDK

cloudbed/client is the browser half: Preact hooks wired to your server's queries and mutations over a WebSocket, plus built-in auth and a small router. The client entry (client/index.tsx) renders into #app:

import { render } from "preact";
import { useAuth, useMutation, useQuery } from "cloudbed/client";

function App() {
  const auth = useAuth();
  const todos = useQuery("todos") ?? [];
  const addTodo = useMutation("addTodo");

  return (
    <div>
      <p>Welcome, {auth.displayName}</p>
      <button onClick={() => addTodo("New task")}>Add</button>
      <ul>{todos.map((t) => <li key={t.id}>{t.text}</li>)}</ul>
    </div>
  );
}

render(<App />, document.getElementById("app")!);

Data hooks

useQuery(name)

Subscribe to a server query by name. Returns undefined until the first result arrives, then the query's value — and re-renders automatically whenever a mutation changes the underlying data. Live updates are pushed by the server; there is nothing to invalidate or refetch.

useMutation(name)

Returns a stable async function that runs the named server mutation with whatever arguments you pass. On failure it rejects with a TransportError carrying { code, message } (the message of any Error your mutation threw).

const addTodo = useMutation("addTodo");
try {
  await addTodo(text);
} catch (err) {
  setError(err.message);
}

Auth

Every capsule gets "Sign in with Google" with zero registration. Tokens are scoped to your app's origin and user ids are pairwise per app, so capsules can't correlate a user across apps. Before sign-in, users are guests (guest:local, or guest:<name> via a ?guest=<name> URL parameter) — ctx.auth.userId works either way on the server.

The redirect round-trip is handled for you: the SDK completes the flow on /auth/callback and returns the user to the page they started from.

Router

A minimal client-side router, included so a capsule needs no other dependencies:

import { Link, Route, Router, Routes, useParams } from "cloudbed/client";

function App() {
  return (
    <Router>
      <nav><Link to="/">Home</Link></nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/todo/:id" element={<Todo />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </Router>
  );
}

function Todo() {
  const { id } = useParams();
  // ...
}