II Basics

State

A component’s state is a collection of entity objects that only its own operations can change. What an object may hold is declared once, as the entity’s schema, and the runtime keeps every stored object to it.

§The schema

entity:
  properties:
    customer: { type: string }
    status: { type: string, enum: [pending, approved, cancelled] }
    total: { type: integer, minimum: 0 }
  required: [customer, status, total]
  blank:
    status: pending

properties and required are a JSON Schema of one object: every order belongs to a customer, has one of three statuses and a total that is not negative. blank is what an object holds before anything is written to it.

The schema describes state, not storage: there is no table and no storage client in it, and the runtime maps what it describes to the storage the component is given.

The runtime validates state against the schema when it commits. A transition that leaves an order without a customer, or with a negative total, does not commit: the invalid state never reaches storage, whichever operation produced it.

This places the definition of valid state in one declaration instead of in every operation that writes.

§System properties

Every entity carries properties its schema does not declare. The runtime writes them, and an operation reads them like any other.

PropertyHolds
idThe entity’s identity, assigned by the runtime when it is created: a UUIDv7 as 32 hexadecimal characters, so ids sort in the order of creation.
VERSIONA counter that grows with every committed write: what optimistic concurrency control compares.
CREATEDWhen the entity was first written.
UPDATEDWhen it was last written.
DELETEDWhen it was deleted, or nothing while it is not.
REGIONWhich region made the last write.

§Guards

A schema constrains one state. Some rules constrain a change: a total that may only decrease, a status that never leaves cancelled. A guard states such an invariant once, for every operation of the component:

// guards/total.ts
export function guard (order: Entity, origin: Entity | null) {
  return origin === null || order.total <= origin.total
}

The runtime evaluates guards at commit, with the state about to be written and the state it replaces (origin, or null for a new entity). A change that violates a guard is not committed. The invariant holds regardless of which operation attempted the change, including operations inherited from a prototype.

§Associated entities

An associated entity has no id of its own: its id is the id of another entity. A wallet is identified by its customer’s id.

entity:
  associated: true
  properties:
    balance: { type: integer, minimum: 0 }
  required: [balance]
  blank:
    balance: 0

Any id names one. Reading an entity that was never written returns its blank, and the first write creates it: nothing has to create a wallet when a customer appears, and nothing has to handle a wallet that is not there.

§Deletion

Nothing removes an entity from storage. Deletion is a state change: the entity is marked with the time it was deleted, and from then on queries do not find it. The generic prototype’s terminate operation makes that change.

A deleted entity keeps its history and its identity. Other entities that referred to it do not end up pointing at nothing, and the deletion itself is a committed change like any other: it has a version, and it is announced by an event.

Where data really has to be gone — a request under the GDPR, for one — that is arranged in userspace: a TTL index on DELETED removes what was deleted some time after the deletion.

Search
Search the documentation