DRAFTThis page is not published. Only visible in development mode.

Model Application State

Take one realistic domain - a small task tracker - and model it end to end with ReifyDB's schema primitives: a namespace to own everything, tables for the entities, an enum for a closed value set, a dictionary for an open one, auto-increment sequences for IDs, a transactional view for derived state, and a ring buffer for recent activity. You end up with a working schema and the queries that drive it.

This page is a real database
Every snippet runs against a real ReifyDB engine compiled to WebAssembly. Click Run on each snippet, top to bottom - later snippets build on the state left by earlier ones. If you have not done the quickstart yet, start there.

1. A namespace and the first table

Everything the tracker owns lives in one namespace. One namespace per application domain is the right default: it keeps names short, and dropping or inspecting the whole app stays a one-liner. The first entity is projects. Its id column carries with { auto_increment }, so the database owns ID assignment - your application never coordinates counters:

$ Create the Namespace and the Projects Table
$ ctrl+enter to run

Because the database assigns IDs, you read them back at insert time with returning instead of guessing:

$ Insert Projects and Read the Generated IDs
$ ctrl+enter to run

2. A closed value set: enum

Task priority is a fixed set known at design time. That is exactly what an enum is for: the schema enforces the set, so a typo like "hgih" is a schema error instead of silent bad data. Declare the variants once:

$ Declare Priority as an Enum
$ ctrl+enter to run

3. An open value set: dictionary

Assignee names are the opposite case: an open set you discover at runtime. A dictionary interns each distinct string once and stores a compact integer everywhere it is used. You still read and write plain strings; the encoding is the engine's problem:

$ Create a Dictionary for Assignees
$ ctrl+enter to run

4. The tasks table ties it together

Now the central entity. Each column type is a decision you just made: an auto-increment ID, a plain int8 foreign reference to projects, the enum for priority, and the dictionary-encoded assignee:

$ The Tasks Table Ties It Together
$ ctrl+enter to run

5. Derived state is a view, not a cache

"How many open tasks does each person have?" is the kind of read shape that usually ends up in a cache with invalidation bugs. Here it is a transactional view: declared once, maintained incrementally inside the same transaction as every write that affects it. Define it before inserting data, so it tracks changes from the start:

$ Derived State: Open Tasks per Assignee
$ ctrl+enter to run

6. Recent activity is a ring buffer

An activity feed only ever shows the last few entries, so storing unbounded history is waste. A ring buffer holds a fixed number of rows and evicts the oldest on overflow - retention is schema, not a cleanup job. The tiny capacity here is just to make eviction visible on this page:

$ A Ring Buffer for Recent Activity
$ ctrl+enter to run

7. Load the data

The schema is complete; now exercise it. Enum values are written as variant paths, and assignees are written as plain strings - the dictionary interns new names on the fly:

$ Insert Tasks
$ ctrl+enter to run

Every auto-increment column is backed by a sequence you can reposition, for example to keep public-facing IDs away from the small numbers used while prototyping. The next insert continues from the new value:

$ Reposition the ID Sequence
$ ctrl+enter to run

8. Read it back

A plain read shows how each choice is stored. The enum column comes back as its compact tag (priority_tag: declaration order, so Low is 0 and High is 2), while the dictionary-encoded assignee is decoded back to a string for you:

$ Read the Tasks Back
$ ctrl+enter to run

The dictionary itself is queryable, and it interned exactly the two distinct names it saw:

$ The Dictionary Interned Both Assignees
$ ctrl+enter to run

9. Query the derived state

The workload view is already populated. Nothing recomputed it - the task inserts maintained it as they committed:

$ Query the Workload View
$ ctrl+enter to run

Ad-hoc read shapes stay ordinary queries. Joined columns are prefixed with the join alias, so the project's name arrives as p_name:

$ Join Tasks to Their Project
$ ctrl+enter to run

10. Change the data

Complete a task and record the action in the activity buffer:

$ Complete a Task and Log the Activity
$ ctrl+enter to run

The completed task no longer matches the view's filter, so its assignee's count dropped by one. There is no cache to invalidate, because there is no cache:

$ The View Already Reflects the Change
$ ctrl+enter to run

Meanwhile the activity buffer enforces its own retention. It held one row; three more inserts push it past its capacity of 3, and the oldest entry is evicted:

$ The Ring Buffer Keeps Only the Newest Rows
$ ctrl+enter to run

What you modeled

  • --Authoritative state: two tables with database-assigned IDs.
  • --Constrained values: an enum for the closed set, a dictionary for the open one - both enforced or encoded by the schema, not by convention.
  • --Derived state: a transactional view that every write keeps current.
  • --Bounded history: a ring buffer whose retention is part of the schema.

Where next