Database
Database stores every Thing in an owner database per namespace, with an analytics copy of every Event and reads at any past moment.
Database stores every Thing a Startup owns in db.sb and serves it through one typed grammar, $.Noun.verb. Relationships, all-or-nothing writes, analytics, past states and migrations use the same grammar.
Contract
A Noun defines a type, and a Thing is one instance of it. Declaring a Noun gives it typed create, get, list, update and delete on every Interface. Every write runs as an Action attributed to its Worker.
| Guarantee | Detail |
|---|---|
| One record | Each namespace has one owner database with a single writer; a read after Thing.update sees the write |
| Typed | Types generate from the Nouns, so a renamed property fails at compile time, never in production |
| Relationships | Declared once on the Nouns, written with Thing.relate and Thing.unrelate, and filtered from either end |
| Find or create | Thing.resolve returns found, created or held; two matches write nothing and open a merge Task |
| All or nothing | Writes inside one $.transaction commit together as one Action; a throw cancels every write in it |
| Analytics | Every Action and Event lands in an append-only analytics store that Collection.query reads with SQL |
| Past states | Nothing is overwritten: get with at returns a Thing as it stood, and listing Actions by Action.thing returns every write to it |
| Migrations | A Noun changes only through a Change; Change.review classifies it before Change.merge |
| Isolation | No query crosses Startups; each Customer's records sit in its Tenant |
| Scale | One owner database carries 500 to 1,000 requests a second; past that, a Collection shards by key under the same ids |
Reads
A list returns one page, newest first, with a cursor. It filters by any property or Relationship of the Noun.
// Leaseline tracks lease renewals. It declares one Noun, Lease.
import { $ } from 'startup-builder'
const page = await $.Lease.list({ where: 'status = active and endsOn < 2027-03-01', sort: 'endsOn', limit: 50 })
const next = await $.Lease.list({ where: 'status = active', after: page.after })Relationships
A Relationship has a name from each end. Leaseline declares that a Contact rents a Lease, read back as rentedBy.
await $.Thing.relate({ thing: 'contact_7', predicate: 'rents', to: 'lease_42' })
const rented = await $.Lease.list({ where: 'rentedBy = contact_7' })Find or create
const { thing, decision } = await $.Thing.resolve({ noun: 'customer', match: { email: 'ana@example.com' }, set: { name: 'Ana Costa' } })
// decision is 'found', 'created' or 'held'All or nothing
await $.transaction(async (tx) => {
const lease = await tx.Lease.create({ unit: '4C', endsOn: '2027-06-30' })
await tx.Thing.relate({ thing: 'contact_7', predicate: 'rents', to: lease.$id })
}) // one Action; a throw before the end cancels both writesAnalytics
Collection.query reads the analytics store with SQL, so a year of rows never slows a write.
const { rows } = await $.Collection.query({ query: "select date_trunc(month, endsOn) as month, count(*) as due from Lease where status = 'active' group by month" })Past states
const then = await $.Lease.get({ id: 'lease_42', at: '2026-10-01T00: 00Z' })
const writes = await $.Action.list({ where: 'thing = lease_42' })On the API, @ and a time or Version name the same read: GET /leases/.
Migrations
To add a property, edit nouns/ and open a Change. Change.review classifies the diff as additive or breaking; a breaking Change opens a new Version, and pinned callers keep the old one.
A type conversion runs as a background Action, and rows that fail it are set aside, never dropped. Compatibility lists every class.
Verbs
| Verb | Object | Event |
|---|---|---|
| Thing.get | Thing | None. |
| Thing.list | Thing | None. |
| Thing.create | Thing | Thing.created |
| Thing.update | Thing | Thing.updated |
| Thing.relate | Thing | Thing.related |
| Thing.unrelate | Thing | Thing.unrelated |
| Thing.resolve | Thing | Thing.resolved |
| Collection.query | Collection | Collection.queried |
Interfaces
Example
# Leaseline counts the leases ending each month, as JSON for a Dashboard.
npx startup-builder do Collection.query --json '{"query": "select date_trunc(month, endsOn) as month, count(*) from Lease group by month"}'