Skip to content

Things

A thing is a named, versioned entity within a repository. Things are the core data objects in WarmHub — they represent the entities in your domain.

Things are the foundation. Use them for:

  • Entities in your domain — the objects your system reasons about (locations, players, companies, documents)
  • Reference data — stable records that serve as targets for assertions
  • Anything with a single canonical state — if there’s one truth about this entity, it’s a thing

You don’t need assertions just to track changes over time — things are already versioned, so revise gives you full history on its own.

Things are created through writes. Use the CLI shorthand:

Terminal window
wh commit submit --add acme --shape Company --data '{"industry": "fintech", "stage": "series-b"}' -m "Add company"

Or the full operations format:

{
"operation": "add",
"kind": "thing",
"name": "Company/acme",
"data": { "industry": "fintech", "stage": "series-b" }
}

The thing’s wref is Company/acme — the shape name followed by the thing name.

Thing names can contain / for hierarchical organization. See Naming as Navigation for a deep dive on designing effective names — how hierarchy affects agent navigation, scoped queries, and subscription routing.

Location/dungeon/room-1
Location/dungeon/room-2
GameState/round-1/state

The first segment is always the shape name. Everything after the first / is the thing name. Names cannot start or end with /, and cannot contain //.

Every mutation creates a new version. Versions are numbered sequentially starting at v1:

Terminal window
# v1 — initial creation
wh commit submit --add cave --shape Location --data '{"x": 3, "y": 7}'
# v2 — revised data
wh thing revise Location/cave --data '{"x": 5, "y": 3}' -m "Move cave"

Each version is immutable — old versions are preserved and queryable:

Terminal window
# View specific version
wh thing view Location/cave --version 1
# View current (HEAD) version
wh thing view Location/cave

Things have an active flag. By default, things are active. Retracting a thing hides it from HEAD queries and other default queries, but preserves its name, data, and full version history. A retracted thing retains its name, but can still be renamed — when renamed, existing references automatically resolve to the new name.

Terminal window
# Retract (marks inactive, records optional reason)
wh thing retract Location/cave -m "No longer relevant"
wh thing retract Location/cave --reason "superseded by Location/cave-v2" -m "retract"
Terminal window
# Snapshot of active things and assertions.
# System-managed component infrastructure records are hidden by default.
wh thing list
# Filter by shape — shows all things in that shape, including component-seeded ones
wh thing list --shape Location
# Hide all component-owned records (stricter than default)
wh thing list --exclude-components
# View a specific thing
wh thing view Location/cave
# Version history
wh thing history Location/cave --limit 10
# Filtered query
wh thing query --shape Location --limit 50
# Query only component-owned records
wh thing query --component com.acme.research --limit 50

ThingDetail.data is typed as unknown on the SDK so the compiler refuses property access until the caller narrows the payload to its shape. There is no Thing<MyShape> generic — narrow with a cast or a runtime parse before reaching for fields:

import type { ThingDetail } from "@warmhub/sdk-ts";
type LocationData = { x: number; y: number; label?: string };
const detail = await client.thing.get("acme", "world", "Location/cave");
if (!detail) throw new Error("not found");
// Option 1 — trust the shape and cast (cheapest)
const data = detail.data as LocationData;
console.log(data.x, data.y);
// Option 2 — validate at the boundary (e.g. with zod)
// const data = LocationSchema.parse(detail.data);
// History items carry the same `data?: unknown` field on each version row.
const history = await client.thing.history("acme", "world", "Location/cave");
const v1 = history.versions[0];
const previous = v1?.data as LocationData | undefined;

If the shape is known statically, prefer the cast for ergonomics. If the payload is untrusted or shape evolution is in play, parse it through a schema (zod, valibot, ajv) so the runtime check stays close to the read.

Under the hood, WarmHub stores all entities in a single things table, distinguished by kind:

KindDescription
shapeSchema definition — fields and types
thingNamed entity with data
assertionClaim about another thing (has about reference)

This unified model means shapes, things, and assertions are all versioned, all have wrefs, and all flow through the same commit pipeline.