C&C Docs
Log in
Platform services

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.

The document
{
  "_schema": "DataBook",
  "_select": [
    "title",
    "author",
    "pages"
  ],
  "_order": [
    {
      "field": "pages",
      "direction": "desc"
    }
  ],
  "_limit": 4
}
As DBQL
DataBook { title author pages _order: [{ field: pages, direction: desc }] _limit: 4 }
Rows answered
[
  {
    "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 Odyssey
    Homer · poetry
  • The Selfish Gene
    Richard Dawkins · science
  • The Left Hand of Darkness
    Ursula 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.

Press a request to send it.