zio.blocks.projection
Members list
Packages
Type members
Classlikes
Helpers for global aggregate projections.
Helpers for global aggregate projections.
Global projections share a single underlying store file (global/.db) and receive events from all sources regardless of entity routing. The entity identifier acts as the grouping key (for example a date string "2025-08-26").
The store layer guarantees that Increment / Decrement / Max / Min FieldUpdates translate to atomic SQL (COALESCE(col,0)+?, MAX(...), MIN(...)) and that a missing aggregate row is materialised on-the-fly via INSERT OR IGNORE before the UPDATE. Upsert uses the SQLite INSERT ... ON CONFLICT DO UPDATE form (falls back to INSERT OR REPLACE when the driver does not advertise conflict support).
Attributes
- Source
- AggregateProjection.scala
- Supertypes
- Self type
-
AggregateProjection.type
This object was generated by sbt-buildinfo.
This object was generated by sbt-buildinfo.
Attributes
- Source
- BuildInfo.scala
- Supertypes
-
trait Singletontrait Producttrait Mirrortrait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
- Self type
-
BuildInfo.type
Attributes
- Companion
- trait
- Source
- EntityPath.scala
- Supertypes
- Self type
-
EntityPath.type
Describes where a projection entity A lives on disk and which field is its identifier.
Describes where a projection entity A lives on disk and which field is its identifier.
basePath is the folder name (e.g. users derived from userId), entityIdField is the field name that holds the entity id.
Attributes
- Companion
- object
- Source
- EntityPath.scala
- Supertypes
An event together with the metadata an EventStore records about it.
An event together with the metadata an EventStore records about it.
Envelopes are what an EventStore returns from readFrom and readAll, and what it publishes on its subscribe hub when a new event is appended. The ProjectionEngine reads them to run a projection's handlers: it passes event to the handler, along with a ProjectionContext built from entityId, timestamp and seq.
The type is covariant in E, so an EventEnvelope[UserCreated] can be used where an EventEnvelope[Any] is expected.
Type parameters
- E
-
the event type
Value parameters
- entityId
-
the entity the event was recorded under, which a handler sees as
ctx.entityId - event
-
the event itself
- seq
-
the position of the event in its store, increasing with each append. The engine saves the last
seqit processed and resumes after it on restart - tag
-
the name that identifies the kind of event. The SQLite store uses the case name or the number from
@eventTag, and replaces an old tag with its current one when tag aliases are configured. The in-memory test store uses the event class's simple name - timestamp
-
when the event was recorded
Attributes
- Source
- EventEnvelope.scala
- Supertypes
An append-only log of the events of one event type E.
An append-only log of the events of one event type E.
Each appended event gets the next sequence number, which also gives the order of the events. The store streams the events it holds from any position, and publishes every new event to a Hub, so that a reader can catch up on the history and then follow the new events. A ProjectionEngine reads each source of a projection from one event store.
SQLiteEventStore keeps the events in a SQLite file.
Type parameters
- E
-
the event type, usually a sealed trait with one case for each kind of event
Attributes
- Source
- EventStore.scala
- Supertypes
- Known subtypes
-
class SQLiteEventStore[E]
Scala 3 macro implementations that turn a field selector such as _.userCount into the name of the field, checked at compile time.
Scala 3 macro implementations that turn a field selector such as _.userCount into the name of the field, checked at compile time.
They back the selector-based methods of FieldUpdate (increment, decrement, setValue, maxValue, minValue) and the HandlerBuilder.update macro, so that a misspelled field is a compile error and not a runtime failure. The selector must be a lambda that reads a field, such as _.userCount. Only the last field name of the selector is used, so _.stats.userCount gives userCount. A selector that is not a lambda, or that reads no field, such as x => x, is rejected with a compile error.
Field names are mapped to SQL snake_case column names with SqlNameMapper.SnakeCase, for example userCount becomes user_count. The name is worked out at compile time and appears as a string literal in the expanded code, so no reflection happens at runtime.
Attributes
- Source
- FieldSelectorMacros.scala
- Supertypes
- Self type
-
FieldSelectorMacros.type
Attributes
- Companion
- enum
- Source
- FieldUpdate.scala
- Supertypes
- Self type
-
FieldUpdate.type
A sealed enumeration of field-level update operations.
A sealed enumeration of field-level update operations.
Each variant describes how a single field should be modified during an Update action on a ProjectionAction.
Provides two APIs:
- '''String-based''' (direct):
FieldUpdate.Increment("user_count", 1L) - '''Selector-based''' (macro):
FieldUpdate.increment[User](_.userCount)which mapsuserCount→user_countviaSqlNameMapper.SnakeCase
Attributes
- Companion
- object
- Source
- FieldUpdate.scala
- Supertypes
-
trait Enumtrait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
- Known subtypes
Chooses how the events of one named source are routed. It is returned by Projection.from, and each method returns the Projection with a new SourceBinding added. Handlers are attached afterwards with on.
Chooses how the events of one named source are routed. It is returned by Projection.from, and each method returns the Projection with a new SourceBinding added. Handlers are attached afterwards with on.
Attributes
- Source
- Projection.scala
- Supertypes
Says what to do when an event of type E arrives: a function from the event and its ProjectionContext to a ProjectionAction on the projected type A.
Says what to do when an event of type E arrives: a function from the event and its ProjectionContext to a ProjectionAction on the projected type A.
Handlers are created by HandlerBuilder, not by hand. They contain no effects: a handler only describes the change, and the engine applies it.
Value parameters
- eventClass
-
the runtime class used to recognise events this handler is for
- fn
-
turns an event into the action to apply
- schema
-
the schema of the event type
- tag
-
the name of the event type, used in messages
Attributes
- Source
- Projection.scala
- Supertypes
Says what an event of type E does to the projected type A. It is returned by Projection.on and is finished by calling one of its methods, each of which returns the Projection with the new handler added.
Says what an event of type E does to the projected type A. It is returned by Projection.on and is finished by calling one of its methods, each of which returns the Projection with the new handler added.
The methods fall into three groups:
- whole rows:
insert,delete - field changes:
aggregate,update,updateWithField,updateField - anything else:
custom
Attributes
- Source
- Projection.scala
- Supertypes
Attributes
- Companion
- class
- Source
- Projection.scala
- Supertypes
- Self type
-
Projection.type
A declarative description of a read model A built from events: which sources to listen to, how to route their events, and what each kind of event does to A.
A declarative description of a read model A built from events: which sources to listen to, how to route their events, and what each kind of event does to A.
A Projection is immutable data and does nothing by itself. Every builder call returns a new Projection, and an engine (such as ProjectionEngine) later reads it to process events. Handlers only describe changes as ProjectionActions, so they cannot perform effects.
Create one with Projection[A](name) (one row per entity) or Projection.global[A](name) (shared totals), then chain from, a routing call, and on:
val greetings = Projection[Greeting]("greetings")
.from("people")
.routeToSelf
.on[PersonGreeted]
.insert((e, ctx) => Greeting(ctx.entityId, s"Hello, ${e.name}!"))
Further from(...) calls add more sources, and further on[...] calls add more handlers to the latest source. An on[...] with no from(...) before it creates a default source named "_default" that routes to the entity itself.
Call validate before starting an engine to catch an empty projection.
Value parameters
- bindings
-
the sources with their routing and handlers, in the order they were added
- entityPath
-
where the store lives and which field of
Ais its ID. It isNonefor global projections - isGlobal
-
whether this is a global projection
- name
-
the projection's name, which also names its store file
- schema
-
the schema of the projected type
A
Attributes
- Companion
- object
- Source
- Projection.scala
- Supertypes
Attributes
- Companion
- enum
- Source
- ProjectionAction.scala
- Supertypes
- Self type
-
ProjectionAction.type
A sealed enumeration of actions that can be applied to a projection.
A sealed enumeration of actions that can be applied to a projection.
ProjectionAction[+A] is covariant in A, allowing actions on subtypes to be used where supertype actions are expected.
Each variant represents a distinct operation on projected data:
Insert: Add a new entityUpsert: Add or update an entityUpdate: Modify specific fields of an existing entityDelete: Remove an entity (sentinel — no payload)Truncate: Remove all entities (sentinel — no payload)Noop: No operation (sentinel — no payload)
Type parameters
- A
-
The entity type this action operates on
Attributes
- Companion
- object
- Source
- ProjectionAction.scala
- Supertypes
-
trait Enumtrait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
- Known subtypes
Contextual metadata associated with a projection action.
Contextual metadata associated with a projection action.
Value parameters
- entityId
-
the identifier of the entity this action targets
- seq
-
a monotonically increasing sequence number for ordering
- sourceEntityId
-
optional identifier of the entity in the source system
- timestamp
-
the time the action was produced
Attributes
- Companion
- object
- Source
- ProjectionContext.scala
- Supertypes
Attributes
- Companion
- class
- Source
- ProjectionContext.scala
- Supertypes
- Self type
-
ProjectionContext.type
Runs projections: it reads events from EventStores, applies each Projection's handlers, and keeps the results in ProjectionStores that can be queried.
Runs projections: it reads events from EventStores, applies each Projection's handlers, and keeps the results in ProjectionStores that can be queried.
A Projection only describes what should happen. The engine is the part that does it. Each projection gets a primary store, which is one SQLite file per projection on the JVM or an in-memory map in tests.
Lifecycle: start first compares each projection's stored schema hash with the current one, and brings stale stores up to date unless lazyRebuild is on. Then, for each source of each projection, it catches up on the events after the projection's saved position, reading them in batches with readFrom, and only then starts a fiber that follows the new events from the source's hub. start returns once every source has caught up. query first applies any pending lazy rebuild.
Routing: for each event the engine builds a ProjectionContext from the EventEnvelope, runs the matching handler, and applies the resulting ProjectionAction to the store chosen by the projection's ProjectionScope: the primary store for per-entity and global projections, and a shard per routing key for cross-entity ones.
Failures: an event whose handler throws, or that the store rejects, is logged as an error and skipped; an event that no handler matches is skipped too, and logged at debug level. Processing continues with the next event. The saved position is updated after each event that succeeds, so a skipped event is not retried once a later one succeeds.
Persistence: the stores that make, makeWithConfig and makeWithStoresVarargs create are SQLite files, so sqlite-jdbc must be on the runtime classpath and the folders of the files must exist. A store opens its file only when it is first used, so a missing driver or folder shows up as a failure then, not when the engine is created.
Create an engine with the factories on the companion object. make and makeWithConfig give it no event stores, so it has nothing to read; to give it event stores, create it with makeWithStores or makeWithStoresVarargs, or use TestEngine in tests.
Value parameters
- cache
-
shared TransactorCache for SQLite connections
- config
-
the settings the engine was created with
- eventStores
-
per-source event stores keyed by source name
- evolutionSem
-
semaphore guarding rebuilds
- migrationRegistry
-
registered schema migrations
- pendingRebuildRef
-
specs pending lazy rebuild
- primaryStores
-
per-spec primary stores keyed by spec name
- shardsRef
-
sharded stores for cross-entity routing
- specs
-
list of projections this engine manages
Attributes
- Companion
- object
- Source
- ProjectionEngine.scala
- Supertypes
Attributes
- Companion
- class
- Source
- ProjectionEngine.scala
- Supertypes
- Self type
-
ProjectionEngine.type
Attributes
- Companion
- class
- Source
- ProjectionEngine.scala
- Supertypes
- Self type
Settings for a ProjectionEngine.
Settings for a ProjectionEngine.
Value parameters
- batchSize
-
the most events read together while catching up on history. Each event of a batch is still applied, and its position saved, on its own.
- batchTimeout
-
how long catch-up waits for a batch to fill before it applies what it has
- evolution
-
settings for SchemaEvolution. The engine does not read them: it always tries the migration shortcut when a migration is registered for the projection, and uses its own
lazyRebuildandrebuildParallelism. - lazyRebuild
-
if
true, a projection whose schema changed is only marked as pending at startup, and rebuilt on its first query or by the background pre-warm. Iffalse, the default, it is rebuilt duringstart. - rebuildParallelism
-
the size of the groups in which the background pre-warm rebuilds pending projections when
lazyRebuildis on. The groups, and the projections in them, are rebuilt one after the other. - ringCapacity
-
the capacity of the bounded hub that carries live events, where a hub is created from the config, as
TestEnginedoes. An event store made withSQLiteEventStore.makehas its own hub of 4,096 events.
Attributes
- Companion
- object
- Source
- ProjectionEngine.scala
- Supertypes
Attributes
- Source
- ProjectionMacros.scala
- Supertypes
- Self type
-
ProjectionMacros.type
How a Projection's data is split up. It is never set directly: see Projection.scope for how it is derived from the way the projection was built. The engine uses it to choose the store an event goes to and the file the store lives in.
How a Projection's data is split up. It is never set directly: see Projection.scope for how it is derived from the way the projection was built. The engine uses it to choose the store an event goes to and the file the store lives in.
Attributes
- Companion
- object
- Source
- Projection.scala
- Supertypes
- Known subtypes
Attributes
- Companion
- trait
- Source
- Projection.scala
- Supertypes
- Self type
-
ProjectionScope.type
Where the rows of one read model A are kept.
Where the rows of one read model A are kept.
A store holds the rows, each under the entity ID read from its ID field (see EntityPath), and two values the engine keeps next to them: the watermark, which is the sequence number of the last event applied to the rows, and the schema hash, a fingerprint of the shape of A that tells the engine when the rows are out of date.
The engine applies the ProjectionAction that a handler returns with one of these methods, and reads a row back with findById. SQLiteProjectionStore keeps the rows in a SQLite file, and testing.InMemoryProjectionStore keeps them in memory, for tests.
Type parameters
- A
-
the type of a row: a case class with an ID field
Attributes
- Source
- ProjectionStore.scala
- Supertypes
- Known subtypes
-
class InMemoryProjectionStore[A]class SQLiteProjectionStore[A]
Decides which row or shard of a projection an incoming event is applied to.
Decides which row or shard of a projection an incoming event is applied to.
A routing mode belongs to a SourceBinding and is chosen on FromBuilder with routeToSelf, routeToAll or routedBy. The mode produces a ''routing key'' for each event (see Projection.routingKey), and it also determines the projection's ProjectionScope.
Attributes
- Companion
- object
- Source
- Projection.scala
- Supertypes
- Known subtypes
Attributes
- Companion
- trait
- Source
- Projection.scala
- Supertypes
- Self type
-
RoutingMode.type
An EventStore kept in a SQLite database.
An EventStore kept in a SQLite database.
Each event is a row of the events table, with the columns seq, tag, payload, ts and entityId. seq is the primary key and gives the sequence number. tag is the @eventTag number of the event's case, or else the case's name; for an event type that is not a sealed trait, it is the type's own @eventTag number or name. payload is the event encoded as JSON, and ts is the time it was appended, in milliseconds. An index on tag and seq serves filtered reads.
The store knows the tags of E through a TagInfo. When a case was renamed, a read that asks for the new tag also returns the events saved under the old one, decodes them as the renamed case, and reports them under the new tag.
A read opens its own connection and pulls the rows through a cursor as the stream is consumed. An event whose payload cannot be decoded fails the stream.
The constructor is private: create a store with the factories of the companion object, which also create the table and its index if they are missing.
Value parameters
- subscribe
-
the hub that every appended event is published to
- tagInfo
-
the tags of
E, and the old tags that were renamed to them
Attributes
- Companion
- object
- Source
- EventStore.scala
- Supertypes
Attributes
- Companion
- class
- Source
- EventStore.scala
- Supertypes
- Self type
-
SQLiteEventStore.type
Attributes
- Companion
- class
- Source
- SQLiteProjectionStore.scala
- Supertypes
- Self type
A ProjectionStore kept in a SQLite file. ProjectionEngine.make creates one for each projection; with ProjectionEngine.makeWithStores you create them yourself.
A ProjectionStore kept in a SQLite file. ProjectionEngine.make creates one for each projection; with ProjectionEngine.makeWithStores you create them yourself.
The rows are kept in a table named after A, with one column for each field, in snake_case, and a unique index on the ID column. The watermark and the schema hash are kept in a table called _projection_meta, in a row named after the rows' table. The tables are created the first time the store is used, not when it is made.
upsert uses INSERT ... ON CONFLICT DO UPDATE, and falls back to INSERT OR REPLACE if SQLite rejects that statement. updateFields checks every field name against the table's columns before it starts, then runs INSERT OR IGNORE for the default row and one UPDATE for each field update, in one transaction.
The constructor is private: create a store with SQLiteProjectionStore.make.
Type parameters
- A
-
the type of a row, with an EntityPath that names its ID field
Attributes
- Companion
- object
- Source
- SQLiteProjectionStore.scala
- Supertypes
Detects schema drift via SchemaHash and rebuilds or migrates projection stores.
Detects schema drift via SchemaHash and rebuilds or migrates projection stores.
Attributes
- Source
- SchemaEvolution.scala
- Supertypes
- Self type
-
SchemaEvolution.type
Attributes
- Companion
- class
- Source
- SchemaEvolution.scala
- Supertypes
- Self type
Controls schema-evolution behavior (rebuild vs migration shortcut).
Controls schema-evolution behavior (rebuild vs migration shortcut).
Attributes
- Companion
- object
- Source
- SchemaEvolution.scala
- Supertypes
SHA-256 hash of a schema's structure for evolution detection.
SHA-256 hash of a schema's structure for evolution detection.
Records recurse into field schemas (not just type names) so nested changes affect the hash.
Attributes
- Source
- SchemaHash.scala
- Supertypes
- Self type
-
SchemaHash.type
The rules a Projection applies to one named event source: how its events are routed, and which handlers react to them.
The rules a Projection applies to one named event source: how its events are routed, and which handlers react to them.
A binding is added by from(sourceName) followed by a routing call, and each on[E] handler is then attached to the most recently added binding. The type parameter E is not used by any field.
Value parameters
- handlers
-
the handlers for this source, tried in the order they were added
- routing
-
how events from this source are routed
- sourceName
-
the name of the event source, as given to
Projection.from
Attributes
- Source
- Projection.scala
- Supertypes
Which event tags exist for an event type, and how old tags map to current ones.
Which event tags exist for an event type, and how old tags map to current ones.
Event tags are stored as strings, and a renamed case leaves old events in the store under its old tag. A TagInfo records which tags are current, which are old aliases of them, and the migration that rewrites old events. An EventStore uses it to read old events as if they carried the current tag.
Build one with TagResolver rather than by hand.
Value parameters
- aliases
-
map from an old tag to the current tag it was renamed to. Chains of renames are already followed, so every old tag maps straight to its final name
- allTags
-
every tag that may legitimately be in the store: the current tags plus all old ones
- currentTags
-
the tags the event type's schema produces now
- migration
-
the migration used to transform old events
Attributes
- Source
- TagResolver.scala
- Supertypes
Works out the tags of an event type and the aliases left behind by renamed cases. It reads the current tags from Schema[E], and the old ones from the RenameCase actions of an optional Migration[E, E].
Works out the tags of an event type and the aliases left behind by renamed cases. It reads the current tags from Schema[E], and the old ones from the RenameCase actions of an optional Migration[E, E].
Without a migration there are no aliases. With one, each renamed case becomes an alias, so old events can still be read and queried. The names are used exactly as written in the migration.
val migration: Migration[UserEvent, UserEvent] =
Migration.newBuilder[UserEvent, UserEvent]
.renameCase("UserCreated", "UserRegistered")
.build
val info = TagResolver.resolve[UserEvent](migration)
// info.aliases == Map("UserCreated" -> "UserRegistered")
The event stores call these functions when they are created, so applications normally only pass a migration and do not call them directly.
Attributes
- Companion
- trait
- Source
- TagResolver.scala
- Supertypes
- Self type
-
TagResolver.type
A view of the TagInfo for an event type E, with its parts as methods.
A view of the TagInfo for an event type E, with its parts as methods.
Instances are made by implementing tagInfo. Most code uses the functions on the companion object to get a TagInfo directly.
Attributes
- Companion
- object
- Source
- TagResolver.scala
- Supertypes
Attributes
- Companion
- trait
- Source
- TransactorCache.scala
- Supertypes
- Self type
-
TransactorCache.type
Cache of SQLite Transactors keyed by file path with LRU eviction.
Cache of SQLite Transactors keyed by file path with LRU eviction.
Each distinct file path maps to a single Transactor instance. When the cache reaches maxSize the least-recently-used entry is evicted.
Attributes
- Companion
- object
- Source
- TransactorCache.scala
- Supertypes
Attributes
- Companion
- class
- Source
- TransactorCache.scala
- Supertypes
- Self type
Configuration for TransactorCache.
Configuration for TransactorCache.
Attributes
- Companion
- object
- Source
- TransactorCache.scala
- Supertypes
Gives an event case a numeric tag in place of its default name-based tag.
Gives an event case a numeric tag in place of its default name-based tag.
Every event an EventStore appends is stored with a tag that identifies its kind, and reads can be filtered by tag. By default the tag is the case name, so renaming a case changes the tag that new events are stored with. Annotating the case with @eventTag(n) makes the tag the string form of n instead, for example "1", which stays the same when the case is renamed.
Put the annotation on each case class of a sealed event type, with a different number for each case. The numbers are not checked for uniqueness. It is also honoured on a single, non-sealed event type.
sealed trait UserEvent
@eventTag(1)
case class Created(userId: String, name: String) extends UserEvent
@eventTag(2)
case class Deleted(userId: String) extends UserEvent
The tag is the event's identity in the store's tag column and in EventEnvelope. It does not change how the event's payload is encoded.
Value parameters
- value
-
the number to use as the tag
Attributes
- Source
- EventTag.scala
- Supertypes
Attributes
- Source
- EntityPath.scala
- Supertypes