Server SDK

cloudbed/server is how a capsule declares its data and behavior. The whole server is one default export:

import { boolean, capsule, mutation, query, string, table } from "cloudbed/server";

const schema = {
  todos: table({
    text: string(),
    done: boolean().default(false),
    ownerId: string(),
  }),
};

export default capsule({
  schema,
  queries: {
    todos: query((ctx) =>
      ctx.db.todos.where("ownerId", ctx.auth.userId).orderBy("createdAt", "desc").all()),
  },
  mutations: {
    addTodo: mutation((ctx, text: string) => {
      if (!text) throw new Error("Text required");
      return ctx.db.todos.insert({ text, ownerId: ctx.auth.userId });
    }),
  },
});

The server is authoritative. Queries decide what each user may read; mutations validate their arguments and re-check ownership before writing. The platform independently re-validates every write against the declared schema on commit, but per-row authorization (which user may touch which row) is your capsule's job — as the ownerId checks above show.

Schema

Every row automatically carries three server-managed fields you never write yourself:

Field Type
id string unique row id
createdAt string ISO timestamp, set on insert
updatedAt string ISO timestamp, maintained on update

Handlers

Each handler invocation is bounded at 10 seconds of wall-clock time.

The context

Every handler receives ctx:

Property What it is
ctx.db Typed database handle, one property per schema table (below)
ctx.auth The calling user: userId, displayName, provider ("guest" | "google"), isGuest, isAuthenticated, and email / emailVerified / picture when signed in
ctx.env Server-only env vars from .env.cloudbed.server (claimed deploys only)
ctx.log info / warn / error loggers; entries are kept by the runtime (see log retention) and exposed on the deploy's inspect API

Before sign-in, users are guests: ctx.auth.userId is guest:local by default, and opening the app with ?guest=<name> acts as the named guest guest:<name> (persisted per tab — useful for testing multi-user behavior). Ownership checks work the same whether or not the user has signed in.

Reading and writing

ctx.db.<table> exposes a small chainable query builder:

ctx.db.todos.where("ownerId", ctx.auth.userId).orderBy("createdAt", "desc").limit(50).all();
ctx.db.todos.get(id);                    // one row by id, or null
ctx.db.todos.insert({ text, ownerId }); // returns the full row (with id, timestamps)
ctx.db.todos.update(id, { done: true }); // partial patch, returns the updated row
ctx.db.todos.delete(id);

where filters by equality, orderBy defaults to "asc", and a query may return at most 1,000 rows — see limits.