Backends
Declare an app's record types once and sync them through the same engine as Issues
A backend is a set of record types an app keeps in a workspace. You declare it in one file, its declaration; the sync service makes its tables, validates every write against it, and syncs its records to every client over the sync protocol. The TypeScript SDK types its records from it.
Issues is a backend too: Atlas's own, declared native and written through
its own ops. Every other backend is written with three generic record ops
(put_record, update_record, delete_record), validated from its
declaration by the same code.
A declaration
{
"backend": "checklist",
"version": 1,
"kind": "declared",
"title": "Checklist",
"permissions": { "read": "atlas.read", "write": "atlas.write" },
"types": {
"list": {
"fields": {
"title": { "type": "text", "required": true, "max": 280 },
"archived": { "type": "bool", "default": false }
}
},
"item": {
"fields": {
"list_id": { "type": "ref", "to": "list", "required": true, "on_delete": "cascade" },
"text": { "type": "text", "required": true, "max": 2000 },
"done": { "type": "bool", "default": false },
"sort_key": { "type": "sort_key" }
},
"index": [["list_id", "sort_key"]]
}
}
}Its JSON Schema is schema/declaration.json in @remotehost/sync-protocol.
Names
- A backend's name matches
^[a-z][a-z0-9_]{1,30}$; a type's or a field's,^[a-z][a-z0-9_]{0,62}$. issues,atlas,public,core,api,internalandprivateare reserved backend names.id,org_id,workspace_id,rev,created_at,updated_atanddeleted_atbelong to every record and are never a type's or a field's.
Kinds
| Kind | Who stores the records |
|---|---|
declared | The sync service, in the workspace's database |
adapter | Your own server, through a backend adapter; it declares changes: "ordered" (it reports changes in order, and syncs live) or "none" |
native | Issues alone; it lists its ops |
Permissions
permissions.read and permissions.write name registered permissions:
atlas.read and atlas.write. A client sees a backend's records only while
it holds the read permission, and writes them only with the write one.
Field types
| Type | Value | Options |
|---|---|---|
text | A string | max characters, at most 8,192 |
long_text | A string | max characters, at most 1 MiB |
int | A 64-bit integer | |
real | A number | |
bool | true or false | |
date | YYYY-MM-DD | |
time | YYYY-MM-DDTHH:MM:SS.sssZ | |
enum | One of values | values, each once |
uuid | A UUID | |
ref | Another record's id | to (a type of the same backend), on_delete: cascade, set_null or restrict |
sort_key | An ordering key | |
json | Any JSON value | max_bytes, at most 64 KiB |
blob | {sha256, size, content_type} | max_bytes, required, at most 5 GiB |
actor | Who acted | Filled by the service; a client never sends one |
Every field may also have:
required: a record must have it (aset_nullreference never is);default: a value of the field's type (none foruuid,ref,bloboractor);unique: no two records share it (text,int,date,time,enum,uuid,refandsort_keyonly).
A type's unique lists unique fields too, and its index lists the field
lists to index, each once.
Writing records
{"op": "put_record", "backend": "checklist", "type": "list", "id": "0190f6b0-…", "fields": {"title": "Groceries"}}
{"op": "update_record", "backend": "checklist", "type": "list", "id": "0190f6b0-…",
"set": {"title": "Shopping"}, "expect": {"title": "Groceries"}}
{"op": "delete_record", "backend": "checklist", "type": "list", "id": "0190f6b0-…"}id is a UUIDv7 the client makes. expect applies the update only while
those fields hold those values. A delete applies each reference's
on_delete; a cascade over 1,000 records is refused cascade_too_large. The
sync protocol lists every
refusal.
Versions
A declaration's version starts at 1, and a new version only adds. It may add
types, optional fields, enum values and indexes, raise a max, or make a field
optional. It never removes or renames a type or field, narrows one, retargets
a reference, or changes unique, a default, the kind or the permissions.
Each earlier version is kept beside it (history/<n>.json), and each is
checked against the one before it. Clients on an older version keep working.
Enabling a backend
A backend is off in a workspace until it is enabled:
POST /v1/workspaces/{id}/backends/{name}/enable on the sync service, by
someone who manages the workspace (workspaces.manage). It answers 200
with workspace_id, backend and group_id, the same again once it is on.
A client then names it in hello's backends to receive its records; a
connection that asked for it before it was enabled is sent its group in
groups {add} once it has records.