StateLens StateLens

Documentation Example manifests

Example manifests

Ready-to-edit manifests, one per framework the tool has been shown to read — each proved by a test that seeds a real store through that framework's own library and drives the pages over it. Copy one to the root of the zip you upload as statelens.json, beside the assemblies it names, and change the four lines that are yours, and add a fifth if a team of its own reads it:

Line Change it to
name What the service is called. Its address is made from it: Orders is browsed at /orders
description A sentence for the service's sheet, or delete the line
assemblies The file names of the assemblies in the zip that hold the domain types — events, streams, aggregates, projections and their ids. Only these are scanned
connectionString The name of an entry under ConnectionStrings in the tool's configuration, never the string itself
roles.read Optional: who may read this service beside the global roles — claim values your provider sends, or role names when the tool keeps the accounts. Leave it out for the global roles alone — see Roles

Everything else in the file describes the framework's conventions and tables, and stays as it is. Each file is offered for download at the foot of this page, and each is a preset in the manifest builder, to change in a form rather than by hand. The Settings page's sheet for the service says all of it back in words once the zip is installed, so a manifest read other than as meant is caught there. Where the framework keeps metadata beside each event, the manifest names that column too, and the event's Json view shows it under the payload.

memoria.json — a store Memoria wrote

For a service built on Memoria over its Entity Framework Core store: the DomainEvents, DomainAggregates and DomainProjections tables, on PostgreSQL, SQL Server or SQLite.

Set "dcb": true only when the same database also holds the dynamic consistency boundary tables (DcbEvents, DcbEventTags, DcbSnapshots) — a service that called AddMemoriaDcbEntityFrameworkCore. That is the one thing the manifest cannot describe, and it switches the DCB pages on. Leave it false for a streamed-only store, or those pages open on tables that are not there.

Two things worth knowing:

  • Do not zip a Memoria* assembly. The types bind to the ones the tool already loaded; a copy built against another version loads and then registers nothing.
  • The connection string only needs to read. The tool writes nothing to any store.
Back to top

marten.json — a store Marten wrote

For a service built on Marten with string stream identity: the mt_events and mt_streams tables, and one mt_doc_* table per aggregate Marten snapshots inline. The example has two aggregates, an order and a loyalty balance; add or remove a pair of lines per aggregate of your own. Three things are the application's:

  • The stream types. Marten keeps a stream key as a string and nothing else, so what a key is made of is the domain's to say: a record per kind of stream whose ToString() writes the key — OrderId writing order-{number} in the example — named under streams and as its aggregate's id. That is what lets the tool tell one kind of stream from another and work a key back to its values.
  • The names. Marten names an event type by its class in snake case and an aggregate's document table by its class in lower case (mt_doc_order), which is what the two naming rules say. An application that registered other names with Marten says those instead.
  • The wrapped applies. A Marten aggregate may take an event as IEvent<OrderPlaced> rather than bare, to read its version or its time. The fold.wrapper block is what lets the tool call such a method: it constructs Marten's own JasperFx.Events.Event<T> around each event — the zip carries JasperFx.Events.dll beside the domain — and fills its Version, Sequence, Timestamp and StreamKey from the stored row. Methods taking the bare event work as before, with or without the block; a domain with no wrapped applies can drop it. Marten's older Marten.Events.IEvent<T> (before Marten 8) is the same shape under Marten.Events.Event.
  • The snapshots. Each inline snapshot is a document table, joined to mt_streams for the stream's version and creation. Marten writes the aggregate's Version into the document, which is where the version expression reads it; the stream's version is how far the fold reached. A Marten application that keeps no inline snapshots leaves snapshots out, or declares its own projection tables the way the Eventuous example does.

Archived events are left out by the where; drop it to see them. Marten's timestamp column carries a zone, so it is read as it is. Marten writes no metadata unless the application opted in through Events.MetadataConfig; an application that enabled headers adds "metadata": "headers" to events, and one that enabled correlation and causation alone can show both with an expression such as "metadata": "jsonb_build_object('correlation_id', correlation_id, 'causation_id', causation_id)".

Back to top

eventuous.json — a store Eventuous wrote

For a service built on Eventuous over its PostgreSQL store: the messages and streams tables under the schema the application configured (eventuous unless it chose another — change both names in the sql if it did). Beside the four lines above, three things are the application's:

  • The stream type. Eventuous names a stream {Aggregate}-{id} in its StreamNameMap rather than in a type. Give the domain a record whose ToString() writes that name — BookingStream in the example — and name it under streams; that is what lets the tool tell one kind of stream from another and work a stored name back to its id.
  • The state. Name the state record — BookingState : State<BookingState> — under aggregates, with the aggregate's Id record as its id. The fold calls When, which the framework's State<T> declares, and takes the state each call returns.
  • The read model. Eventuous keeps no snapshots: an aggregate is always folded from its stream, and what the application keeps in tables is whatever its projectors write. Declare each such table under snapshots, with typeName naming the state it holds, or leave snapshots out for a store with none. The booking_summary in the example is one such table, with the state as JSON, the count of events folded, the last stream position, and when.

Events are named by Eventuous's own [EventType("V1.RoomBooked")], which is what the naming rule reads. created is a timestamp without a zone, so the sql puts UTC on it.

Back to top

messagedb.json — a store Message DB wrote

For a service built on Message DB — Eventide's PostgreSQL message store — where everything is one table, message_store.messages, and streams are told apart by their category. Beside the four lines above, the manifest names the entity's category, account in the example, in three places: the where on the events, the snapshot SELECT, and the stream name the SELECT puts back together. Three things are the application's:

  • The stream type. Eventide composes a stream name as {category}-{id}, and Message DB keeps the rule in its category() and id() functions rather than in a type. Give the domain a record whose ToString() writes the name — AccountStream in the example — and name it under streams.
  • The events. Eventide writes a message under its class name, which is what class-name reads, and category(stream_name) = 'account' keeps the entity's messages apart from its snapshot stream's. time has no zone, so the manifest puts UTC on it in place.
  • The snapshots. Message DB has no table of them. An Eventide component records an entity's snapshot as one more message, of type Recorded, in the entity's snapshot stream — account:snapshot-123 beside account-123 — carrying the entity's data and the version it was at. The snapshot SELECT takes the latest Recorded message per snapshot stream, works the entity's stream and id back out of the name through Message DB's own functions, and reads the version and the data out of the message. Change the two JSON paths if the application records its snapshots under other names, or leave snapshots out for a component that records none.
Back to top

sqlstreamstore.json — a store SQL Stream Store wrote

For a service built on SQL Stream Store over its PostgreSQL store: the messages and streams tables under the schema the application configured (public unless it chose another — change both names in the sql if it did). SQL Stream Store is events only: it keeps no snapshots and projects nothing, so the manifest has no snapshots at all, and the pages that list them say the store holds none. Two things are the application's:

  • The stream type. The library keeps a stream id as a string and nothing else. Give the domain a record whose ToString() writes it — OrderId writing order-{number} in the example — and name it under streams and as the aggregate's id.
  • The type strings. The library stores whatever string the application passed for each message's type; the example's application passed the class name, which is what class-name reads. An application that passed other strings uses a map.

The messages table holds a stream number, so the sql joins the streams table for the id the application wrote; the library's own streams, named with a leading $, are left out; and the zoneless created_utc is given its zone.

See What to put in a zip for every key.

Back to top

sqlserver.json — a home-grown store on SQL Server

For a service with no framework at all: plain records for the events, an aggregate with Apply methods and a Version, and tables the application designed itself on SQL Server. The example's tables are dbo.Events, one snapshot table per aggregate and a summary table; rename the columns to yours, and the domain types to yours. The application calls its read models summaries rather than projections, and labels says so — one summary, many summaries, given as both because the plural is not the word with an s — so the tool's pages say Summaries where they would say Projections; delete the line to keep the tool's words. Unlike the other examples this one is not proved by a container test — the suite has no SQL Server — so it is checked only as a manifest the tool accepts; the same manifest over SQLite is what MappedStoreTests proves. Three things worth knowing on this engine:

  • Dates must come back with a zone. The tool reads written, created and updated as datetimeoffset. A column of that type is read as it is; a datetime2 holding UTC is wrapped, as the example does with TODATETIMEOFFSET(AppendedAt, 0). A column with no zone left bare is read as nothing.
  • Identifiers are bracketed for you. A plain name — dbo.Events, StreamName — is quoted part by part in SQL Server's own brackets; anything that is not a plain name is taken as an expression and passed through as written.
  • Text is nvarchar(max). The payload, the type and the stream id are cast to it whatever column type holds them, so a varchar or a JSON column both read.

See What to put in a zip for every key.

Back to top

kurrentdb.json — a store in KurrentDB

For a service that writes to KurrentDB (formerly EventStoreDB), with or without a framework: a store with no tables, so the block is of another kind, kurrentdb, and the connection string is the client's own URL, esdb://host:2113?tls=false or kurrentdb://…. Proved by a test that seeds the last EventStoreDB image through the client and drives the pages over it. Three things are the application's:

  • The stream names. The events source names the prefix, or prefixes, the service's streams are named with — order- in the example, so order-42 is one order — and the domain gives a record whose ToString() writes such a name, named under streams and as the aggregate's id.
  • The type strings. KurrentDB stores whatever string the application passed for each event's type; the example passed the class name, which is what class-name reads. An application that passed other strings uses a map.
  • The snapshots, if any. An application that snapshots writes each model's state as an event in a stream of its own — snapshot-order-42 in the example — and the source says where in that event the version and the state are, and how the model's own stream is named from the id. An application that keeps no snapshots leaves snapshots out; its aggregates page then says the store holds none, and the events and streams read all the same. The snapshot prefix must not be a prefix of the event streams' names, or the snapshots would be listed among the events.

See A store in KurrentDB for every key.

Back to top

cosmos.json — a store in Cosmos DB

For a service that keeps its events as documents in Cosmos DB, with or without a framework: the same block as a relational store with "kind": "cosmos", the database, and a container in place of each table. Proved by a test that seeds the Cosmos DB emulator through the SDK with a home-grown layout — a document per event with its payload as a nested document, a snapshot document per order in a second container — and drives the pages over it. Three things are the application's:

  • The properties. Each column is a property of the document, plain or as a path, or an expression in Cosmos's SQL over c. The example's events keep the payload in body, a nested document, and the metadata in meta; a payload kept as a JSON string reads the same.
  • The dates. at, createdAt and updatedAt are ISO text in the example. A store that relies on Cosmos's own _ts writes "written": "c._ts", which is read as seconds since the epoch.
  • The snapshots. A container of the latest state per model, with the version and the sequence read up to as properties. A store that keeps events and snapshots in one container tells them apart with a where, as the next example does.
Back to top

cosmos-memoria.json — a store Memoria wrote to Cosmos DB

For a service built on Memoria's own Cosmos store: one container, Domain in the database Memoria by default, holding events, aggregates and projections told apart by documentType. The domain block is memoria.json's; the store block names the one container three times, each time with the where that picks its kind of document. Change database and container to what the application configured.

See A store in Cosmos DB for every key.

Back to top

Files

Back to top