Asking $data
$data
is how a controller reads and writes rows. A read is one query document; a write is
create, update
or delete. Everything here runs against the docs' own store:
seven DataBook rows the app seeds, which everybody may read, and
the books you write, which only you may.
A query is a document
Pick a question. The controller sends the document and its DBQL spelling, which is the same question written as text, and both answer the same rows. A relation reads nested in the same query.
{
"_schema": "DataBook",
"_select": [
"title",
"author",
"pages"
],
"_order": [
{
"field": "pages",
"direction": "desc"
}
],
"_limit": 4
}
DataBook { title author pages _order: [{ field: pages, direction: desc }] _limit: 4 }
[
{
"title": "SPQR",
"author": "Mary Beard",
"pages": 608
},
{
"title": "The Odyssey",
"author": "Homer",
"pages": 541
},
{
"title": "Dune",
"author": "Frank Herbert",
"pages": 412
},
{
"title": "Cosmos",
"author": "Carl Sagan",
"pages": 365
}
]
The document and the DBQL answered the same rows.
const { rows } = await this.$data({
_schema: 'DataBook',
_select: ['title', 'author', 'pages'], // what comes back
_where: { genre: { $in: ['science', 'history'] } }, // which rows
_order: [{ field: 'pages', direction: 'desc' }], // in what order
_limit: 4, // how many
})
// The same question as DBQL text:
await this.$data("DataBook { title author pages _limit: 4 }")
// One row by its key:
await this.$data('DataBook/dune')
Writes, and a batch
A write answers the row as stored. The layer stamps
_owner
and _createdAt
itself, whatever the write says, and a delete retires the row: it leaves every read, and the
store keeps it. A batch lands whole or not at all.
A write is always somebody's. Log in or create an account to write books of your own.
const row = await this.$data.create('DataBook', { title, author, genre })
// row is what was stored: its _key, the default state, _owner and _createdAt
await this.$data.update(row._key, { pages: row.pages + 100 })
await this.$data.delete(row._key)
// A batch lands whole or not at all; _as names a row for a later op:
await this.$data.batch([
{ _op: 'create', _schema: 'DataBook', _as: 'book', title, author, genre },
{ _op: 'create', _schema: 'DataReview', book: '$book', stars: 5, body },
])
// A refused write rejects with an envelope to branch on:
try { await this.$data.create('DataBook', { title, author, genre: 'cookery' }) }
catch (e) { e.code /* 'invalid_value' */; e.field /* 'genre' */ }
Searching in words
A schema declares which fields are searchable, and a search is one more
$data
call: a misspelling still matches, an unfinished word matches as a prefix, and the facets count
the matches by genre. Every number is about rows you may read.
const answer = await this.$data({
_op: 'search',
_schema: 'DataBook',
_search: words, // fuzzy and prefix-matching
_select: ['title', 'author', 'genre'],
_facets: ['genre'], // counts per genre
})
answer.rows // best match first
answer.total // how many rows you may read that match
answer.facets // { genre: { fiction: 2, … } }
3 matching
-
The OdysseyHomer · poetry
-
The Selfish GeneRichard Dawkins · science
-
The Left Hand of DarknessUrsula K. Le Guin · fiction
{
"genre": {
"fiction": 1,
"poetry": 1,
"science": 1
}
}
Over HTTP
The same $data, identity and $can
answer plain HTTP under /@cnc/v1 at every app's address, so
browser code or another program can use them too. Each button asks from your browser, as you:
your session cookie, if you are signed in, is the credential.