Collections and rules
A collection is a real SQLite table. Its rules are compiled into the same query that fetches its rows, not checked afterwards.
Fields
The field types are text, number, bool, email, url, date, json, select, relation and file. A collection may declare composite and unique indexes. A collection with "extends": "_users" holds app-specific profile fields, keyed by the same id as the user.
Five rules
"rules": {
"list": "@request.auth.id != null",
"view": "@request.auth.id != null",
"create": "author = @request.auth.id",
"update": "author = @request.auth.id",
"delete": "author = @request.auth.id || @request.auth.staff = true"
}- No rule means superusers only;
""means anyone. - A list rule filters: callers get only the rows they may see, and totals stay correct.
- View, update and delete return 404 rather than 403, so rules can’t be used to probe whether a row exists.
- After a write, the row is re-checked inside the transaction. A write that would put a row out of the writer’s own reach is rolled back.
Fields can have their own rules too, e.g. who may read a grade or change a status. They’re enforced in the same SQL.
The API
GET /api/collections/todos/records?filter=done=false&sort=-created
POST /api/collections/todos/records
PATCH /api/collections/todos/records/:id
DELETE /api/collections/todos/records/:id
// server-sent events, filtered by your rules
GET /api/realtime?subscribe=todosimport { Sluurp } from "sluurp";
const sluurp = new Sluurp();
await sluurp.collection("todos").create({ title: "Milk", author: sluurp.auth.id });
const open = await sluurp.collection("todos").list({ filter: "done = false" });Filters are parsed and compiled to SQL with bound parameters. A filter referencing an unknown field is rejected before any SQL is built.
Views
A view collection’s rows come from a SQL SELECT: a report that joins or groups other collections, read like any collection. You can list, filter, sort, expand and aggregate it, and its own list and view rules decide who sees what. It’s read-only: creating, updating or deleting its records is refused.
{
"name": "absence_counts",
"type": "view",
"query": "SELECT user AS id, count(*) AS absences FROM absences GROUP BY user",
"rules": { "list": "@request.auth.staff = true", "view": "@request.auth.staff = true" }
}- Write collection names in the query; Sluurp maps them to their tables.
- The query must be one
SELECTthat only reads, and each row needs anid. - Fields come from the columns the query returns: numbers where the values are numbers, text otherwise. Declare a field in
schemato give it a type of your own, such as a relation, soexpandworks through it. - The query reads the underlying tables directly, not through their rules. The view’s own rules are what apply, so write them for whoever the view is meant for.
- A collection a view reads can’t be deleted while the view exists. To change a view’s query, delete the view and make it again.
- A view isn’t kept current by sync: a synced list of it is its rows as they were when read.
Projects
Each project is a separate database file, and no query spans two. An app is pinned to one project, so its functions can’t touch another app’s data.