Skip to content

Types

A concept names a single primitive value. A type names a shape — the structured child records real events routinely carry: the lines of an invoice, the answers of a questionnaire, the contact behind a billing address.

Without it, a document can reference lines InvoiceLine[] but never say what an InvoiceLine is. The reader is left guessing, and the concepts that only ever live inside such a record — a PersonName, an EmailAddress — have no reason to be declared at all, so the document understates the application’s own compliance surface. A type closes both gaps.

type <Name>
[description "<text>"]
[file <path>]
<property> <TypeRef>
...

The optional file line names the repository relative file this declaration is realized by, so a document can be navigated back to the code it describes. It is additive - it never stands in for any part of the declaration. See File references.

A type declares at least one property. The property line is the same one events and commands use — a name, a type reference, and the optional [] and ? suffixes — so everything you know about one applies to the other.

concept ProductName : String
concept Quantity : Int
concept Money : Decimal
concept DiscountPercentage : Decimal
type InvoiceLine
description "A single billed line of an invoice"
lineNumber Int
productName ProductName
quantity Quantity
unitPrice Money
discount DiscountPercentage?

The type is then referenced by name from anywhere a type reference is allowed — an event property, a command property, or another type:

event InvoiceRegistered
invoiceId InvoiceId
lines InvoiceLine[]

A type may reference another type, which is how a nested shape is expressed — by reference rather than by nesting the declaration:

concept PersonName : String @pii
concept EmailAddress : String @pii
type BillingContact
name PersonName
email EmailAddress
type Invoice
contact BillingContact
lines InvoiceLine[]

Where types earn their keep — compliance

Section titled “Where types earn their keep — compliance”

This is the reason to reach for a type rather than leaving a shape undeclared. A [PII] value buried inside a child record is still personal data, and Chronicle still encrypts and erases it. But if the shape is never declared, nothing in the document says so — and a reader auditing the model sees a smaller PII surface than the application actually has.

Declaring BillingContact gives PersonName and EmailAddress a home in the document, so @pii and its reason travel with them where a reader can see them.

The compiler resolves every property type reference against the primitives, the declared concepts, the declared types and the imports. A reference to something the document never declares is a warning, not an error — a runtime may well resolve the name, but a document that silently depends on a shape living outside it is exactly what the warning is there to surface:

Unknown type 'InvoiceLine' on 'lines' of event 'InvoiceRegistered' -
declare it with 'concept InvoiceLine : <Primitive>' or 'type InvoiceLine'

Concept and type names share one namespace — declaring both a concept InvoiceLine and a type InvoiceLine is an error.

UseWhen
conceptThe value is a primitive with a domain meaning — an id, a name, an amount, a code.
typeThe value is a shape made of several such values.

Reach for a concept first. A type exists for the shapes a single primitive cannot express — and its properties should themselves be concepts, so the strong typing goes all the way down.