Skip to content

CommandsFactsViews

Capabilities

Atlas has three concepts for people:

Concept Meaning
Command A request to do something. It may be rejected.
Fact Something that happened. Past tense, immutable.
Capability One file that owns some records and decides what happens to them.

That is the whole vocabulary. Policies, views and sagas are not new concepts: they are capabilities that react to facts. Reads for callers are queries over owned records or over views (read models built from facts).

A capability is created with capability({...}) and extended with handlers:

import { capability, t } from '@ts-atlas/core/runtime';
export default capability({
name: 'orders',
intent: 'Customers place orders for themselves and cancel their own, at most once. The customer is the actor, never the payload.',
owns: { orders: Order }, // records only this capability writes
emits: { OrderPlaced, OrderCancelled }, // facts it may emit
// sends: { ... } // commands it sends to other capabilities
// views: { ... } // read models it projects from facts
// consumer: 'old-name' // its delivery identity (default: name)
})
.command('PlaceOrder', { access, input, reads, decide, examples })
.on('StockReservationFailed', { needs, reads, decide, examples }) // a reaction
.project('OrderPlaced', { needs, rows, examples }) // a projection into a view
.query('MyOrders', { access, input, view, where, order, examples }); // a declared read
Part Rule
name Kebab-case, unique. The file is discovered by listing the capabilities folder.
intent One sentence on what it decides and which rule it protects. atlas map and atlas context print it.
owns Collections only this capability writes (ATLAS013, ATLAS017).
emits / sends Every fact and sent command has a shape; consumers are checked against it (ATLAS003).
Handlers .command, .on, .project, .query. Every handler carries examples (ATLAS008, ATLAS009).
  1. One file, one capability. Intent, contracts, reads, decisions and examples live in one file. Understanding it costs exactly one read.
  2. Nothing magic. No decorators, no DI container, no reflection. Wiring is data the tool can print.
  3. Every boundary has a shape. Command inputs, emitted facts, sent commands, owned records and foreign reads are declared as runtime shapes. No capability imports another; contracts are checked, not imported.
  4. Decisions are pure; the runtime does all I/O. A decision receives input, state and time, and returns reject(reason) or accept({ write, emit, send }), so the runtime can retry it on a concurrency conflict for free.
  5. Examples are the spec. Each handler carries state + input -> expected outcome.
  6. Errors are instructions. Every diagnostic says what broke, where, why, and the exact fix.
  7. Generate, don’t write. Boilerplate comes from atlas new.

Capability files import only the runtime (@ts-atlas/core/runtime in an app, ../runtime/index.ts inside Atlas) and shared shapes from the sibling ../shapes/ folder (ATLAS006). A shapes module imports only the runtime and other shapes modules, and exports only shapes (ATLAS018).

250 lines per capability, not counting the lines inside examples: [...] arrays (ATLAS012, a warning). Over it, split by owner of records and communicate through facts. Whatever must be atomic belongs in one capability.

The repository’s src/capabilities/ folder is a small working system:

  • orders.ts: commands, reads, an ownership check, examples (the canonical one).
  • cancel-unfulfillable.ts: a policy with a foreign read and sends.
  • order-summary.ts: a view, two projections and an actor-scoped view query (MyOrders).
  • stock.ts: reserves all lines of a placed order or none.