sqlib Docs HomeGet startedGitHub
Guides

Notebooks: prose, cells and named results

A notebook is a document you write about a library: prose interleaved with runs, in the order that makes sense, with each run's result bound to a name the cells below can use. It is the library's front page — the first entry on the rail, at /notebook — and it replaced a screen that rendered every query in the library as a cell whether or not anyone had asked for it. The contents of the library are what you import here; what you write around them is the point.

Notebooks are listed in the sidebar like every other asset the rail holds, and they are kept the way a query is:

A notebook also travels as a file — see §4.

1. What a cell is

A notebook starts empty. Add offers four kinds:

Kind Holds Runs through
Markdown prose, headings, links, lists nothing
Query a query reference, plus its arguments POST /execute
Query group a group reference POST /execute
Rule set a rule set reference, plus a base graph POST /rule-sets/:id/execute

A run cell carries the same run sentence the query screen writes, minus its "with" clause — a notebook's arguments are the cells above it. What is left is against (the store this cell runs against, defaulting to the library's) and as (the format the result comes back in), both per cell: pulling from a warehouse and then reasoning over an ephemeral copy is the ordinary case. A rule set has neither; it runs in process over the graph it is handed.

A new notebook opens the Details tab with its name selected, so you can name it first. If you do not, it takes the first heading of its first markdown cell as its name.

Working with cells:

A rule-set cell takes its base graph from a graph value above it or from a saved data graph in the library. Its value is the triples the rules infer, without the base graph; to keep both, merge them in a query cell that runs over the two graphs (§3). A rule set that stops at its iteration limit or on a cycle still binds its value, but the cell shows a warning and Run all stops there.

On a phone, the notebook list opens from the button at the left of the header, and the outline and values sit below the document.

Everything runs through the REST API, which is why a rule-set cell works here and cannot exist in an export bundle: the bundle is for calling a triplestore with no sqlib in the request path, so it carries compiled queries and SELECT-chaining groups and nothing else. The two are different artefacts with different jobs.

A cell references an entity — it never holds a copy of a query's text — and tracks that entity's current version. Editing is a link back to the editor, so a notebook is safe to hand to someone who cannot write to the library.

2. Values

Every run binds its result to a name: out1, out2, … , renameable in the cell (renaming rewrites the references). A value is one of three types, and the type decides where it can go next:

Type From Stat chip
Rows SELECT, a group ending in one 1,204 rows · 4 cols · 88 KB
Graph CONSTRUCT, DESCRIBE, a rule set 18,332 triples · 2.1 MB
Boolean ASK true

Values live in the browser session. The document is the story, not the run, so a notebook you re-open shows cells that have not run — the same contract a Jupyter notebook with cleared outputs offers.

Save promotes a value to a real library entity: a graph becomes a data graph version, rows become a tuple set version (source format query-results). That is the upgrade path from "I was poking at this" to "this is an input other things use".

3. Chaining

A cell's parameter slot can be filled from a value bound above it. Pick the value in the slot's source control; the columns are paired with the slot's variables by name where the names match and by position otherwise — the same rule a query group edge uses, so a chain means the same thing in both places.

Under a wired slot, the cell shows the pairing: one row per slot variable, for example ?s ← @out1.?s. A note lists the columns that no variable takes (not passed: ?p ?o). These columns stop at this cell.

To change the pairing, pick another column for a variable. Any column can fill any variable, so ?s can take @out1.?o. Pick nothing (unbound) to leave the variable unbound. The choice is saved in the slot as columns, keyed by the slot variable. Match by name removes the choices. Before the upstream cell runs, the control lists the columns its query projects. A promoted group keeps the chosen pairing, but it refuses a variable left unbound.

Set what happens when the upstream is empty: skip this cell (the default), run with no rows, or stop.

What each wire can carry:

Chaining is by value: a result comes back to the browser and goes out again in the next request. A large value (64 KiB or more) is the exception. When a cell's result is read by a cell below, the run asks the server to keep a copy for a short time, and the next request names that copy instead of sending the content again. The copy is kept only in the server's memory, only for the user who ran the cell, and for 15 minutes after its last use. If the copy has gone, for example after a restart, the notebook sends the value by content instead, with no action from you.

A notebook is still the wrong shape for production. A pipeline that matters belongs in a query group, which the server runs in one call with no browser in the path.

Re-running a cell marks the cells below it stale rather than re-running them. The notebook says the result below came from an older input and leaves the decision to you.

4. Sharing a notebook

A saved notebook is shared by its link. Anyone who can read the library opens it at its current version.

Export writes a .sqlibnb file: a small JSON document of the title, the library it names, and the cells. No results — it is the story, not the run. It is small enough to keep in git beside the library, and a changed argument is a one-line diff.

{
  "format": "sqlib-notebook/1",
  "title": "Finding unlinked assets",
  "library": "urn:sqlib:library:estates",
  "cells": [
    { "kind": "markdown", "id": "md1", "source": "## 1. Candidates\n\n…" },
    { "kind": "query", "id": "c1", "query": "urn:sqlib:query:unlinked-assets", "out": "candidates" },
    { "kind": "ruleset", "id": "c2", "ruleSet": "urn:sqlib:rule-set:closure",
      "out": "closure", "inputGraph": { "from": "value", "ref": "candidates" } }
  ]
}

Import reads one back and switches to the library it names.

The boundary is worth stating plainly: the notebook travels, the entities it names do not. Sending someone a file works when they have the library — the same server, a replica, or an instance holding the same library. A cell whose entity is missing renders as a broken reference rather than vanishing.

5. Versions

A cell floats to its entity's current version by default, so a notebook tracks the library it documents. A document may pin a version instead ("version": 3 on the cell), which is what you want for a notebook attached to a release.

6. Exports

Export saves the notebook's own file (.sqlibnb). The library's exports, the bundle the sqlib CLI runs and the runnable page, are on the main page: see Exporting a library.

A cell that has run also offers Save as test…: hold a query, its arguments and a result and you are holding a test case. It records the call that ran — a slot fed from a value was filled with rows the cell no longer shows.

7. Promote to a query group

Promote to group writes the notebook's run cells as one query group: the same chain, run by the server in one call with no browser in the path. It is the way out when a notebook has become a pipeline someone depends on.

The dialog says what does not carry over before you confirm: typed values (the slot becomes a parameter of the group, filled when it is run), LIMIT and OFFSET values, and "skip this cell if empty", which becomes "match nothing" because a group node always runs.

A notebook that does not have the shape of a group is refused, naming the cell: a run cell whose value nothing below uses (a group returns one result), a group cell (groups do not nest), a rule set reading a saved data graph, or a reference to a value no cell above binds.