Capsule API
Lakebed Native accepts the same server shapes as web Lakebed and the native array style.
Server (lakebed/server)
import {
capsule, query, mutation, endpoint,
table, string, boolean, number, json, text
} from "lakebed/server";
Web-style schema (recommended for cross-platform)
export default capsule({
name: "my-app",
schema: {
todos: table({
title: text(),
done: boolean().default(false),
userId: string(),
}),
},
queries: {
listTodos: query((ctx) =>
ctx.db.todos.where("userId", ctx.auth.userId ?? "guest").all()
),
},
mutations: {
addTodo: mutation((ctx, args: { title: string }) =>
ctx.db.todos.insert({ title: args.title, userId: ctx.auth.userId ?? "guest", done: false })
),
},
});
Native array style (still supported)
const todos = table("todos", { title: text(), done: boolean() });
export default capsule({
name: "my-app",
tables: [todos],
queries: [query("listTodos", (ctx) => ctx.db.table("todos").all())],
mutations: [mutation("addTodo", (ctx, args) => ctx.db.table("todos").insert(args))],
});
Context (web parity)
| Field | Web Lakebed | Native |
|---|---|---|
| User id | ctx.auth.userId |
ctx.auth.userId or ctx.auth.user?.id |
| Display name | ctx.auth.displayName |
ctx.auth.displayName or ctx.auth.user?.name |
| Avatar | — | ctx.auth.picture or ctx.auth.user?.avatarUrl |
| DB | ctx.db.todos |
ctx.db.todos or ctx.db.table("todos") |
Rows get auto id, createdAt, and updatedAt on insert/update. Field .default(value) is applied on insert.
Shared imports
Extensionless imports work in dev and build:
import { cleanTitle } from "../shared/habit";
HTTP / WS (local dev)
GET /health,GET /api/statusPOST /query,POST /api/query—{ name, args }POST /mutation,POST /api/mutationGET /auth,POST /auth/as,?lakebed_guest=aliceon any routeWS /ws— live query subscriptions +auth-changedpushes
Client (lakebed-native/client)
import {
useAuth, useQuery, useMutation, useConnection,
authAs, signInWithGoogle, signOut, configureClient,
} from "lakebed-native/client";
useQuery(name, args?)
Returns { data, loading, error, refetch }:
data—undefineduntil the first successful resultloading—trueduring initial fetch andrefetch()error— set when fetch/subscription fails; WS paths may auto-refetch()- Local dev: WebSocket subscription with HTTP fallback after 1.5s
- Hosted
*.lakebed.app: WebSocket for no-arg queries; HTTP for parameterized queries
const { data: todos, loading, error, refetch } = useQuery<Todo[]>("listTodos");
if (loading) return <Spinner />;
if (error) return <ErrorState title="Could not load" message={error.message} />;
useMutation(name, opts?)
Returns { mutate, loading, error }. Pass { onError, onSuccess } for UI handling.
useAuth()
Returns web-parity fields plus nested user:
{
user: AuthUser | null;
userId: string | null;
displayName: string | null;
picture: string | null;
isGuest: boolean;
loading: boolean;
error: Error | null;
refresh: () => Promise<void>;
}
Updates automatically on auth-changed (local WS) and hosted auth refresh. After authAs(), call refresh() if needed on HTTP-only paths.
Connection / offline
useConnection() — { status: "connected" | "disconnected" | "connecting", error, retry } for dead server / offline UX.
configureClient({ serverUrl: "http://localhost:3000" });
Assets
Place images under assets/ and reference by URI in dev, or use remote URLs:
import { Image } from "lakebed-native/ui";
<Image source={{ uri: "https://example.com/photo.jpg" }} />
Typecheck
npx lakebed-native typecheck
Runs tsc --noEmit on app/ and shared/.