Declaring $data
An app says what its data is in its migrations, and the layer holds every write to that. The examples use the docs' seeded books, reviews and shelves.
A schema, and what its fields promise
A field is required unless it says
nullable; it can keep to a list of
values, be unique, or have a
default. Try each: the layer refuses or fills in, and the
page never checks anything itself.
// migrations/000-declare-schemas.json (abridged)
{ "op": "createSchema", "schema": "DataBook", "fields": {
"title": { "type": "string" },
"author": { "type": "string" }, // required
"pages": { "type": "integer", "nullable": true }, // may be left out
"genre": { "type": "string",
"values": ["fiction", "science", "history", "poetry"] },
"handle": { "type": "slug", "nullable": true, "unique": true },
"state": { "type": "string", "default": "on the shelf" },
"reviews": { "type": "hasMany", "target": "DataReview", "via": "book" } } }
These are writes, and a write is always somebody's. Log in or create an account to try them.
A relation is a field whose type is another schema
There is no join to write. A
belongsTo
reads back as one object, a
hasMany
as a list, and both come back in the same query.
// DataReview: "book": { "type": "belongsTo", "target": "DataBook" }
// DataBook: "shelf": { "type": "belongsTo", "target": "DataShelf" }
// "reviews": { "type": "hasMany", "target": "DataReview", "via": "book" }
await this.$data({
"_schema": "DataBook",
"_where": {
"handle": {
"$in": [
"dune",
"odyssey"
]
}
},
"_select": {
"title": true,
"shelf": {
"_select": [
"name"
]
},
"reviews": {
"_select": [
"stars",
"body"
],
"_limit": 2
}
}
})
A clock time is not an instant
A timestamp
is one moment everybody shares. A datetime
(and date, time) is what a
clock or calendar shows in a named zone: the dentist at 2pm in Toronto stays at 2pm when the
clocks change.
// An Appointment schema might declare:
"at": { "type": "timestamp" } // an instant everybody shares
"when": { "type": "datetime" } // a clock time in a named zone
await this.$data.create('Appointment', {
at: '2026-06-15T18:00:00Z', // the same moment everywhere
when: '2026-06-15 14:00:00 America/Toronto', // 2pm in Toronto, even if
}) // Toronto's rules change
A migration beside a seed
Each file in
migrations/
runs once, in name order, when the app boots. An applied migration is never edited: a change
is a new file. Files in
seeds/
are rows the app ships, such as the seven books on this page; each names itself with a key,
Schema/id, which is how
$data('DataBook/dune')
finds it.
migrations/ // run once each, in name order, at boot
000-declare-schemas.json // createSchema DataBook, DataReview, …
050-g3-data-shelves.json // createSchema DataShelf, addField DataBook.shelf
054-g3-shelves-grow.json // addField DataShelf.material
seeds/ // rows the app ships, synced at boot
DataBook.json
// seeds/DataBook.json — each row names itself: Schema/id
[ { "_key": "DataBook/dune", "title": "Dune", "author": "Frank Herbert",
"genre": "fiction", "pages": 412, "shelf": "DataShelf/front-room" }, … ]