The mechanical engine

Use of semantics declared what the schema means; Nodes & types and Nodes & Relations are the things that meaning describes — a node on its own, and nodes wired to each other. This page is the other half of the pairing: the executor that reads those declarations and does the work. It compiles, it derives, it cascades, and it carries no type-specific code — it asks the semantic engine what a property is and acts generically.

Acting on meaning

The mechanical engine is three jobs, each of which reads the semantics rather than hardcoding it:

  • Compiling reads a property’s kind to decide what to do with a reference — embed a child in full (compose), project a small identity (link), or pull values back from the other end (aggregate).
  • Deriving reads the inverse to know where an aggregate lands. Inbound Reviews become a review[] array and an aggregateRating on the Place’s inverse property — values no editor typed. One shared routine does this for the live compile and the editor’s preview alike, so the two can never drift apart.
  • The cascade reads the topology index and the kinds to know exactly which documents a change must rebuild, and stops the moment it reaches nodes that don’t depend on it.

Special cases stop being special

Because the rules are read rather than written, the hard cases collapse into one. A polymorphic range, an inbound roll-up, a cardinality limit, the choice to embed versus project — each is the same general operation parameterised by a different declaration, not a separate branch in the code. Add a new type or a new property and the engine already knows what to do with it; there is no matching if to go and write. That is the whole payoff of putting meaning in one place: the machinery gets simpler, not more complex, as the schema grows.

The compiled–queried line, precisely

The engine is also where the compiled-versus-queried split gets its exact definition. The compiler materialises only what is schema-true of a node as a single entity: its own properties, its composed children, and its direct inbound (scope: this node). Anything that needs looking across many nodes — every review of every place in a region — is a different question, answered live and never baked.

Cardinality is what keeps that honest. A node has many reviews, but a review names one itemReviewed; the same review never lives on two nodes, so rolling reviews upward is a scoped derive, not a second attribution. This is why the compiled JSON-LD stays trustworthy for a search engine while richer cross-node roll-ups still render in the HTML.

Borrowed, not invented

None of this is novel machinery. A reverse index instead of a stored back-list is how Datomic reads relationships; a derived value kept in step with its source is incremental view maintenance; an entity store paired with a separate query layer is Wikidata’s shape; a declarative relationship directive is Gatsby’s @link; a class-driven picker is SHACL. CTXR already had the primitives — typed nodes, a topology index, a compiler — and the two engines simply name the meaning they were always acting on, so the mechanics can stop guessing.

With the data model and the engine that drives it in hand, the rest of the chapter is hands-on building on top of them — starting with The build folder.