Browser client

Every Sluurp server serves its client, already in each app's import map: import { Sluurp } from "sluurp". Nothing to install. It wraps the REST API and manages auth.

app.ts
import { Sluurp } from "sluurp";

const sluurp = new Sluurp();
const todos = sluurp.collection("todos");

const { items } = await todos.list({ filter: "done = false", sort: "-created" });
const todo = await todos.create({ title: "Milk" });
await todos.update(todo.id, { done: true }, { reason: "bought" });

new Sluurp() talks to the page’s own server. Pass an origin, new Sluurp("https://school.example.com"), for another, and { project } for a non-default project.

Collections

sluurp.collection(name) has:

list({ page, perPage, sort, filter, asOf })One page: { items, page, perPage, totalItems, totalPages }
listAll(options)All pages as one array (served from sync after the first call)
getOne(id), getFirst(filter)A single record
create(data), update(id, data), delete(id)Writes. Each accepts { reason }, stored with the change
history(id), version(id, seq), restore(id, seq)Past versions, if history is on
changes(since)All changes after a sequence number, in order

Files

upload(id, field, file)Upload a File or Blob to a field
createWithFile(data, field, file)Create the record and upload in one step
fileUrl(id, field, { w, h, fit, format })A URL for an <img>, resized
srcset(id, field, [400, 800, 1200])A srcset at those widths
fileObjectUrl(id, field), fileText(id, field)Fetch a private file with auth

Signing in

On the auth collection, usually users:

app.ts
const users = sluurp.collection("users");
await users.authWithPassword(email, password);

sluurp.authStore.isValid;   // signed in?
sluurp.authStore.record;    // the user
sluurp.logout();

Also: signUp, requestPasswordReset, requestSigninLink (magic link), verifyTwoFactor, authRefresh, and oauthUrl("google") with captureOAuthToken(sluurp) on the return page. The session is stored in localStorage and shared across the site’s pages. sluurp.onAuthFailure fires when the server rejects the token.

Permissions

app.ts
const may = await sluurp.permissions();
may.can("grades", "update");        // for some rows at least
may.certainly("grades", "delete");  // for every row

Evaluates the collections’ rules up front, so you can show only buttons that will work.

Errors

Failed calls throw a SluurpError with status, message (from the server), body, and the helpers isAuthError (401) and isForbidden (403).

Live

sluurp.socket({ subscribe: ["messages"] }) opens a WebSocket that streams every change to those collections, filtered by your rules. on(type, listener) subscribes; join(topic) / leave(topic) handle presence; emit(topic, event, data) broadcasts without storing. For lists that stay current automatically, use Sync.

Instant first render

kept renders immediately from the last response cached in this browser, then updates from the server’s response if it differs (stale-while-revalidate):

app.js
sluurp.kept("feeds", () => sluurp.social.feeds(), (r) => feeds.set(r.items));

A reload then shows the complete screen in the first frame instead of filling in piece by piece. The cache is per user, per browser, and cleared on logout().

And the rest

sluurp.conversation(id) and sluurp.conversations for chat, sluurp.pages for Pages, sluurp.payments and sluurp.billing for Payments, sluurp.ai for AI, sluurp.social for feeds. sluurp.send(path, { method, body, query }) calls any endpoint with auth and app headers attached.