# Database

URL: /code/platform/capabilities/database · Id: `capability: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](/business/nouns/thing) a [Startup](/business/nouns/startup) owns in [db.sb](/code/platform/db-sb) and serves it through one typed grammar, `$.Noun.verb`. [Relationships](/business/nouns/relationship), all-or-nothing writes, analytics, past states and migrations use the same grammar.

## Contract
A [Noun](/business/nouns/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](/business/nouns/interface). Every write runs as an [Action](/business/nouns/action) attributed to its [Worker](/business/nouns/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](/business/nouns/relationship) | 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](/business/nouns/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](/business/nouns/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](/business/nouns/change); `Change.review` classifies it before `Change.merge` |
| Isolation | No query crosses Startups; each [Customer](/business/nouns/customer)'s records sit in its [Tenant](/business/nouns/tenant) |
| Scale | One owner database carries 500 to 1,000 requests a second; past that, a [Collection](/business/nouns/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.

```ts
// 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](/business/nouns/contact) `rents` a `Lease`, read back as `rentedBy`.

```ts
await $.Thing.relate({ thing: 'contact_7', predicate: 'rents', to: 'lease_42' })
const rented = await $.Lease.list({ where: 'rentedBy = contact_7' })
```

### Find or create
```ts
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
```ts
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 writes
```

### Analytics
`Collection.query` reads the analytics store with SQL, so a year of rows never slows a write.

```ts
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
```ts
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](/business/nouns/version) name the same read: `GET /leases/lease_42@2026-10-01`.

### Migrations
To add a property, edit `nouns/Lease.mdx` 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](/code/builders/software/compatibility) lists every class.

## Verbs
| Verb | Object | Event |
| --- | --- | --- |
| [Thing.get](/business/nouns/thing/get) | [Thing](/business/nouns/thing) | None. |
| [Thing.list](/business/nouns/thing/list) | [Thing](/business/nouns/thing) | None. |
| [Thing.create](/business/nouns/thing/create) | [Thing](/business/nouns/thing) | `Thing.created` |
| [Thing.update](/business/nouns/thing/update) | [Thing](/business/nouns/thing) | `Thing.updated` |
| [Thing.relate](/business/nouns/thing/relate) | [Thing](/business/nouns/thing) | [`Thing.related`](/business/events/Thing.related) |
| [Thing.unrelate](/business/nouns/thing/unrelate) | [Thing](/business/nouns/thing) | [`Thing.unrelated`](/business/events/Thing.unrelated) |
| [Thing.resolve](/business/nouns/thing/resolve) | [Thing](/business/nouns/thing) | [`Thing.resolved`](/business/events/Thing.resolved) |
| [Collection.query](/business/nouns/collection/query) | [Collection](/business/nouns/collection) | [`Collection.queried`](/business/events/Collection.queried) |

## Interfaces
- [API](/code/api/reference)
- [SDK](/code/sdk/reference)
- [CLI](/code/cli/reference)
- [MCP](/code/mcp/reference)

## Example
```bash
# 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"}'
```