Chronicle
A librarian registers an author. You want that registration kept as a fact, and you want the author list on screen to update from it. Without an integration, every command opens a Chronicle client, picks the event store namespace for the caller’s tenant, appends inside handle(), turns constraint violations into validation results, and takes care never to append when validation has already failed.
@cratis/arc.chronicle removes that plumbing. A command returns the event, and Arc appends it after authorization, validation, and provide() have passed, in the namespace of the tenant Arc already resolved. Arc does not require event sourcing: a command can do its work through any service. Use this integration when you want commands to record facts in Chronicle, the event-sourcing database.
To try it, Add event sourcing starts a Chronicle kernel on your machine and adds a first event-sourced slice to the application from Create an application.
How the pieces fit
Section titled “How the pieces fit”Arc and Chronicle meet at one loop. A command returns an event, Chronicle appends it and projects it into a read model, and an Arc query serves that read model back to the client.
The Library sample walks that loop. Registration returns the event:
@eventType()export class AuthorRegistered { @field(AuthorName) name: AuthorName; constructor(name: AuthorName = new AuthorName('')) { this.name = name; }}
@command()@roles('Librarian')export class RegisterAuthor { @key() @field(AuthorId) id!: AuthorId; @field(AuthorName) name!: AuthorName;
handle(): AuthorRegistered { return new AuthorRegistered(this.name); }}The listing projects that event into a read model and serves it:
@readModel()@fromEvent(AuthorRegistered)export class Author { @field(AuthorId) id!: AuthorId; @field(AuthorName) name!: AuthorName;
@query({ observable: true }, service(ChronicleReadModels)) static allAuthors(models: ChronicleReadModels): Observable<Author[]> { return models.observeAll(Author, author => author.id.toString()); }}The @key() field names the event source, so the registration lands in that author’s stream. @fromEvent(AuthorRegistered) asks Chronicle to copy the event’s matching properties into Author, keyed by the event source. The observable query then pushes a new author list to the browser whenever the projection changes. Appending and projecting are separate steps inside Chronicle, so a successful command can return before the list has caught up.
What the integration adds
Section titled “What the integration adds”- Returned events are appended.
handle()returns one event, several, or events beside a response. See Returning events. - Metadata comes from the command. The event source, stream, subject, and causation are resolved from the command and the request. See Event metadata.
- Tenants map to namespaces. Every append and read uses the tenant of the current execution as the Chronicle namespace.
- Rejections become validation results. A constraint or concurrency violation answers 400, and nothing is appended.
- Nested commands share one batch. Returned events from nested commands are appended together, or not at all. See Transactional commands.
- Current state is a parameter. A command can take its own read model or a rehydrated aggregate as a
handle()argument. See Read models in commands and Aggregates. - Reactors can return commands. A Chronicle reactor returns an Arc command, and Arc runs it through the full command pipeline. See Reactors.
Find your way
Section titled “Find your way”| Page | Use it when you want to |
|---|---|
| Add event sourcing | Start a local kernel, record a first event from a command, and query its projection |
| Registration options | Look up the withChronicle options, configuration keys, and client ownership |
| The Cratis package | Register Arc and Chronicle with one call from @cratis/cratis |
| Returning events | Return one event, a batch, or events next to a response |
| Event metadata | See what each appended event carries and where every value comes from |
| Resolving the event source ID | Choose which event source an event is appended to, and route it |
| Subject | Record whose personal data an event carries |
| Concurrency | Reject an append when the stream moved |
| Causation and auditing | See what the permanent causation chain records, and keep secrets out |
| Transactional commands | Understand the batch across nested commands and its failure rules |
| Read models | Serve projected state from queries |
| Read models in commands | Decide or validate from the state projected for the command’s key |
| When read model resolution fails | Understand a rejected command that loads a read model |
| Aggregates | Decide from one event source’s full history |
| Reactors | Run a follow-up command when an event is recorded |
| Compliance | Know what the integration does, and does not do, for personal data |
| Code analysis | See which .NET ARCCHR diagnostics apply in TypeScript |
| Testing Chronicle commands | Assert returned events without a kernel |
| Testing against a kernel | Check projections and constraints against a live Chronicle kernel |
| Read consistency | Select passive reads or wait for observer completion after a write |
Where it stops
Section titled “Where it stops”- A command’s events go to one event log in one event store. There is no transaction across other stores or external calls.
- An event appended directly through the SDK inside
handle()is outside the command’s batch. - Read models you read through Chronicle arrive already decrypted. Arc releases encrypted personal data at its query edge only for a protected Chronicle read model read directly from MongoDB and returned by a query, either as an instance of its exact class or as a raw
MongoReadModelsdocument typed withreadModel. Untyped raw documents, derived subtypes, and mapped objects are served as stored unless you callreadModels.releaseyourself. See Compliance. - SDK 6.9.0 and later replay reactors by default. Mark non-replayable effects with
@onceOnly(), but keep returned commands safe for failed-partition re-delivery. See Reactors. - No
ARCCHRanalyzers exist for TypeScript. See Code analysis.
Start with Add event sourcing, or run the Library sample to see a finished application.
Related
Section titled “Related”- Chronicle TypeScript client, where the SDK is developed
- CQRS without event sourcing
- Capability reference