Projections
Projections declare how events are projected into a queryable read model. The body of a projection block is written in the Projection Declaration Language (PDL) — an embedded sub-grammar. The Screenplay parser delegates the indented body to the PDL parser (see Sub-language Pluggability).
For documentation examples, use pdl fences for projection-only snippets in projections/* (compiled with CompileProjection); use screenplay fences for whole-document examples, including ones on this page. A projection-level key is parsed but does not route events; declare keys on each from (or its events).
The pages in this section document the full projection sub-language — every directive, operation, and expression the PDL supports. The same language is also used standalone by Cratis Chronicle to define projections without writing code; see Chronicle projections for how Chronicle hosts and executes it. Chronicle is also the semantic reference for what a projection means: the executable semantic model mirrors how Chronicle lowers and runs it — see Semantic Model.
One quoted or fenced-text description on the projection header records report-only authoring intent (PLAY0270), printed before file and every builder directive. Variants and nested blocks do not accept it. The description does not satisfy a projection’s directive requirement and adds no executable bytes. Markdown documentation is not supported here.
Syntax
Section titled “Syntax”projection <Name> => <ReadModel> <PDL grammar>Example
Section titled “Example”projection InvoiceDetails => InvoiceDetailsReadModel from InvoiceRegistered key invoiceId customerId = customerId invoiceNumber = invoiceNumber currency = currency status = "draft" registeredAt = $eventContext.occurred from InvoiceSent key invoiceId status = "sent" sentAt = sentAt from InvoicePaid key invoiceId status = "paid" paidAt = paidAt join customer on customerId with CustomerRegistered customerName = name children lineItems identified by lineNumber from InvoiceLineItemAdded key lineNumber parent invoiceId quantity = quantity unitPrice = unitPrice remove with InvoiceLineItemRemoved key lineNumber parent invoiceIdThe PDL vocabulary at a glance
Section titled “The PDL vocabulary at a glance”| Construct | Meaning |
|---|---|
from <EventType> key <expression> | Which value identifies the read model instance for this event. |
from <EventType> | Maps event properties onto the read model. Same-named properties map automatically. |
join <property> on <key> | Joins related state by a key property. |
children <collection> identified by <key> | Projects events into a child collection. |
parent <property> | Identifies the owning parent of a child. |
remove with <EventType> | Removes the instance (or child) when the event occurs. |
increment / decrement <property> | Counters maintained per event. |
clear <property> | Removes the value the property holds. |
$eventContext.occurred | The timestamp of the event being projected. |
variant <Name> / enters on <EventType> | One of several mutually exclusive named read models sharing the projection’s identity. |
An aggregating projection using counters:
projection InvoiceSummary => InvoiceSummaryReadModel from InvoiceRegistered key "global" increment totalCount increment draftCount from InvoiceSent decrement draftCount increment sentCount from InvoiceCancelled decrement totalCountSeveral projections in one slice
Section titled “Several projections in one slice”A slice is one behavior, and a behavior often needs more than one read model. The list a screen binds to is shaped for a reader; the state a command consults before it decides is shaped for a decision. Both are built from the same events by the same slice, so both projection blocks live in it:
slice StateView CustomerPortalReport query GetPortalReport => PortalReportReadModel by customerId CustomerId
projection PortalReport => PortalReportReadModel from PortalInvitationSent invitedAt = $eventContext.occurred
projection RevokedPortalToken => RevokedPortalTokenReadModel from PortalTokenRevoked revokedAt = $eventContext.occurredDeclare as many as the behavior needs. Each names its own read model, and printing writes them back out in the order they were declared. Splitting them across two slices would say the system has two behaviors where it has one.
Projecting to an event
Section titled “Projecting to an event”In an explicitly outbound Translate slice, a projection (or reducer) may name the slice’s public event after => instead of a read model. It builds the public event from private events:
slice Translate PublishOrderShipped direction outbound public event OrderShipped orderId String status String projection OrderShippedPublisher => OrderShipped from OrderPacked status = "packed" from ShipmentDispatched status = "shipped"The target is an event when => names a declared event and no readmodel of that name. Its from and on events must be private local events (PLAY0611), the target must be the slice’s one local public event (PLAY0609, PLAY0613, PLAY0617), and the form is an error anywhere but an explicitly outbound translation (PLAY0618). It selects ESM v9. The reference evaluator folds the private events deterministically and publishes one public instance per target key. A reducer that targets an event has an opaque transition body, so the reference evaluator reports Unsupported for specifications that depend on it. Renaming the event refuses while a => target names it.
Topics
Section titled “Topics”- From Event - Define rules that trigger when events occur
- Property Mapping - Map event data to read model properties
- Auto-Map - Automatically map matching properties
- Keys - Explicit and composite keys for projection instances
- Event Context - Access event metadata like timestamps and correlation IDs
- From Every - Apply rules to all events the projection subscribes to
- From All - Subscribe to all event types without filtering
- Counters - Increment, decrement, and count operations
- Arithmetic - Add and subtract operations
- Joins - Combine data from related events
- Children - Define nested collections
- Nested Objects - Single nullable child objects
- Removal - Remove projection instances based on events
- Variants - Mutually exclusive named read models sharing one projection identity
- Expressions - Understanding expression syntax
- Semantic Model - How projections bind to the executable semantic model, with Chronicle as the semantic reference
- Grammar (EBNF) - Complete formal grammar specification